This project has moved and is read-only. For the latest updates, please go here.

GetById using IEntity with BsonRepresentation attribute

Aug 2, 2013 at 2:13 PM
GetById requires that the class be derived from Entity.
Could this be amended to just check it implements IEntity?
Aug 6, 2013 at 2:55 PM
Edited Aug 6, 2013 at 2:58 PM
ColinM wrote:
GetById requires that the class be derived from Entity.
It doesn't...

The sample code below demonstrates a "Foo" object implementing only IEntity and shows GetById works just fine:

using MongoRepository;
using System;

class Program
{
    static void Main(string[] args)
    {
        var repo = new MongoRepository<Foo>();

        //Create a foo
        var foo = new Foo()
        {
            Id = "123abc",
            Description = "Test",
            FooDate = DateTime.Now
        };

        //Add it to the repo
        repo.Add(foo);

        //Get by Id
        var result = repo.GetById("123abc");

        //Show the description of the retrieved foo; it should be "Test"
        Console.WriteLine(result.Description);
    }
}

//Foo class implementing IEntity (note: not inheriting from Entity!)
class Foo : IEntity
{
    //Id required by IEntity interface
    public string Id { get; set; }
    //Sample properties
    public string Description { get; set; }
    public DateTime FooDate { get; set; }
}
If you still are experiencing problems, could you elaborate on what exactly you're trying to do or achieve?
Aug 6, 2013 at 4:31 PM
Edited Aug 6, 2013 at 4:32 PM
Hi Rob,

The problem seems to be when I'm using the [BsonRepresentation(BsonType.ObjectId)] attribute.
I'm not inheriting from the Entity base class as the Serializable tag causes problems with WebApi.
using System;
using MongoRepository;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson;
using System.Diagnostics;

class Program
{
    static void Main(string[] args)
    {
        var repo = new MongoRepository<Foo>();

        //Create a foo
        var foo = new Foo()
        {
            Id = "51e987d57355c9ad2a58f6d0",
            Description = "Test",
            FooDate = DateTime.Now
        };

        //Add it to the repo
        repo.Add(foo);

        //Get by Id
        var result = repo.GetById("123abc");

        Debug.Assert(result != null);
        //Show the description of the retrieved foo; it should be "Test"
        Console.WriteLine(result.Description);
        Console.ReadLine();
    }
}

//Foo class implementing IEntity (note: not inheriting from Entity!)
class Foo : IEntity
{
    //Id required by IEntity interface
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }
    //Sample properties
    public string Description { get; set; }
    public DateTime FooDate { get; set; }
}

Aug 6, 2013 at 6:08 PM
Edited Aug 6, 2013 at 6:09 PM
I'm not on a computer with Visual Studio right now, but right of the bat I see this problem:
        //Create a foo
        var foo = new Foo()
        {
            Id = "51e987d57355c9ad2a58f6d0",
        };

        //Get by Id
        var result = repo.GetById("123abc");
You add a Foo with Id 51e987d57355c9ad2a58f6d0 and then try to retrieve 123abc. I assume this is a copy/paste mistake? Or is this your actual code?
Aug 7, 2013 at 9:41 AM
Hi,

Sorry about that last example (I screwed it up), but even with the correct code I still get unexpected results.
Maybe I'm not understanding the BsonRepresentation, but I don't think it should have this effect.
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoRepository;
using System;
using System.Diagnostics;

class Program
{
    static void Main(string[] args)
    {
        var repo = new MongoRepository<Foo>();

        // Delete everything to ensure known state.
        repo.DeleteAll();

        string guid = "51e987d57355c9ad2a58f6d0";

        //Create a foo
        var foo = new Foo()
        {
            Id = guid,
            Description = "Test",
            FooDate = DateTime.Now
        };
        //Add it to the repo
        repo.Add(foo);


        // will be true, so no problem.
        var exists = repo.Exists(x => x.Id == guid);
        Console.WriteLine("Exists matches?:{0}", exists);

        //Get by Id
        // Shouldn't be null!
        var result = repo.GetById(guid);
        Console.WriteLine("GetById is null?:{0}", result == null);

        Console.ReadLine();
    }
}

//Foo class implementing IEntity (note: not inheriting from Entity!)
class Foo : IEntity
{
    //Id required by IEntity interface
    [BsonId]
    // COMMENT OUT BsonRepresentation Attribute to get expected result.
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }
    //Sample properties
    public string Description { get; set; }
    public DateTime FooDate { get; set; }
}

Aug 7, 2013 at 11:35 AM
Edited Aug 7, 2013 at 11:38 AM
I think this can be easily fixed; I'd like to hear your opinion on the proposed changes:

Current code:
    public T GetById(string id)
    {
        if (typeof(T).IsSubclassOf(typeof(Entity)))
        {
            return this.collection.FindOneByIdAs<T>(new ObjectId(id));
        }

        return this.collection.FindOneByIdAs<T>(id);
    }

    public void Delete(string id)
    {
        if (typeof(T).IsSubclassOf(typeof(Entity)))
        {
            this.collection.Remove(Query.EQ("_id", new ObjectId(id)));
        }
        else
        {
            this.collection.Remove(Query.EQ("_id", id));
        }
    }
Proposed code:
    public T GetById(string id)
    {
        ObjectId objid;
        if (ObjectId.TryParse(id, out objid))
        {
            return this.collection.FindOneByIdAs<T>(objid);
        }

        return this.collection.FindOneByIdAs<T>(id);
    }

    public void Delete(string id)
    {
        ObjectId objid;
        if (ObjectId.TryParse(id, out objid))
        {
            this.collection.Remove(Query.EQ("_id", objid));
        }
        else
        {
            this.collection.Remove(Query.EQ("_id", id));
        }
    }
This does introduce a potential problem: a string that looks like an ObjectID might be parsed into an ObjectId even though it isn't.

Another, IMHO better, option would be to create overloads:
    public T GetById(string id) {
        ...
    }

    public T GetById(ObjectId id) {
        ...
    }

    public T Delete(string id) {
        ...
    }

    public T Delete(ObjectId id) {
        ...
    }
One advantage of this is that the overload with ObjectId won't be shown as long as the MongoDB namespace isn't included and another advantage is that it takes away complexity; where we currently check the type with IsSubclassOf(...) etc. we can now simple remove the conditional statements and move them into the corresponding overloaded methods.

Neither option would break the interface as far as I can see/judge from the code right now (although I'm not sure I want to include the ObjectId-overloads of the methods in the interface; I don't think so since that would be a leaky abstraction...).
Aug 7, 2013 at 1:21 PM
Edited Aug 7, 2013 at 1:26 PM
I don't know the right answer, but if the interface doesn't expose the overloads, then I can't use within my current setup.
I have my controller instantiated with the interface from my repository.
Something like below:
public class SetRepository : MongoRepository<Set>, ISetRepository{
...
}
public interface ISetRepository: IRepository<Set>{
...
}

public class Controller{
ISetRepository repos;

public Set Get(string objectId)
  {
    return repos.GetById(objectId);
  }
}
edit: Actually would this just work using public T GetById(string id) method?
Aug 7, 2013 at 2:05 PM
Apart from the problem you're expierincing: I wonder why you are using an abstraction like MongoRepository but then "insist" on using implementation-specific things like ObjectId? Is there a (compelling) reason to require this?
Aug 7, 2013 at 2:59 PM
I'm just using string for Id in my model classes.
    public class Set : IEntity
    {
        [BsonRepresentation(BsonType.ObjectId)]
        public string Id { get; set; }
        public string Name { get; set; }
        ....
    }
This is my first time using MongoDb & MongoRepository, so maybe I'm doing it wrong!
I thought mongodb required an _id primary key of ObejctId type.
Aug 7, 2013 at 6:08 PM
Edited Aug 8, 2013 at 1:12 PM
I thought mongodb required an _id primary key of ObejctId type.
My first example will create an _id and index just fine ;-) It's just not of ObjectId type (and this is not required*).

* I̶ ̶d̶o̶ ̶b̶e̶l̶i̶e̶v̶e̶ ̶i̶n̶ ̶s̶o̶m̶e̶ ̶s̶c̶e̶n̶a̶r̶i̶o̶s̶ ̶i̶t̶ ̶i̶s̶ ̶r̶e̶q̶u̶i̶r̶e̶d̶ ̶b̶u̶t̶ ̶I̶'̶m̶ ̶n̶o̶t̶ ̶s̶u̶r̶e̶ ̶w̶h̶i̶c̶h̶ ̶e̶x̶a̶c̶t̶l̶y̶.̶ ̶I̶'̶d̶ ̶h̶a̶v̶e̶ ̶t̶o̶ ̶l̶o̶o̶k̶ ̶t̶h̶a̶t̶ ̶u̶p̶.̶ Never mind, AFAIK it's never required.

Having said that; I will still change MongoRepository to use the suggested overloaded methods; I'm just not quite sure on whether to include the ObjectId versions in the interface... For now I won't because I'd have to [Obsolete] them again once we decide against it.
Aug 7, 2013 at 7:00 PM
Edited Aug 7, 2013 at 7:01 PM
I just released version 1.5.2 which should help you resolve your problem. I kinda had to "emergency release" this because a new mongo-csharp-driver was just released causing 1.5.1 to fail unless binding redirects are added. For people encountering this problem and not willing to update to 1.5.2: add the following to your app.config:
<configuration>
  ...
  ...
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="MongoDB.Driver"
          publicKeyToken="f686731cfb9cc103"
          culture="neutral" />

        <bindingRedirect oldVersion="1.8.1.20" newVersion="1.8.2.34" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="MongoDB.Bson"
          publicKeyToken="f686731cfb9cc103"
          culture="neutral" />

        <bindingRedirect oldVersion="1.8.1.20" newVersion="1.8.2.34" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>
Instead of adding this to your app.config you should consider updating to 1.5.2.

I still need to figure out how to make VS accept newer versions. The nuget package should be okay (using version="[1.8.2,1.9.0)" in the .nuspec file) , "specific version" is set to false on the references (but the driver is strong-named). When I get around to it I'll try to find a decent fix for this...
Jan 4, 2014 at 6:10 PM
Edited Jan 4, 2014 at 6:11 PM
Would it be required in cases like:
public class App : IEntity
{
     public string Id {get;set;}
     
     // direct pointed to Report ...
     public string ReportId {get;set;}
}

public class Report : IEntity
{
     public string Id {get;set;}
}
or am i doing it wrong?

Also weird thing, the GET seems to work but the Delete etc does not when using the [BsonRepresentation(BsonType.ObjectId)] decorator attribute.
Jan 7, 2014 at 9:53 AM
Edited Apr 22, 2014 at 2:31 PM
You really need to start giving more (relevant!) information. Your code has no hint to what you're trying to do, your post doesn't point out what your actual problem is or what seems to fail / what exceptions. And you're referring to GET and DELETE (which, I assume, is an ASP.Net project?) "does nothing"; did you set breakpoints? Is the method called? Are the required parameters passed in correctly? I'm very sorry but I'm not clearvoyant ;)
Apr 17, 2014 at 7:39 PM
Edited Apr 17, 2014 at 7:40 PM
I also have the same problem.

If you implement IEntity instead of inheriting from the Entity class, then here are some examples of what will work and not (that I found so far).

Not working:
var check1 = _customersRepository.GetById(SelectedCustomer.Customer.Id); (returns null)
_customersRepository.Delete(SelectedCustomer.Customer.Id);

Working:
var check2 = _customersRepository.Where(x => x.Id == SelectedCustomer.Customer.Id);
var check3 = _customersRepository.Single(x => x.Id == SelectedCustomer.Customer.Id);
_customersRepository.Delete(ObjectId.Parse(SelectedCustomer.Customer.Id));

The workaround for me is to use one of the working examples above.

It would really be nice to be able to pass in the Id directly without creating the ObjectId first every time. This forces me to reference some unnecessary data access objects for MongoDB.

For info, my implementation of IEntity looks like this. The reason I have to use IEntity is that I already inherit from PropertyChangedBase so I cannot inherit from Entity as well.

public abstract class ModelBase : PropertyChangedBase, IEntity
{
    [BsonRepresentation(BsonType.ObjectId)]
    public virtual string Id { get; set; }
}
Hope you can have a look at this issue because I really like the library :) I guess this doesn't only affect the above, but probably anything that takes Id as parameter.
Apr 22, 2014 at 2:31 PM
Edited Apr 22, 2014 at 2:33 PM
It would really be nice to be able to pass in the Id directly without creating the ObjectId first every time.
Could you please post a (representative, acurate, but stripped-down to the bare essentials) testcase? I really don't understand the problem nor am I able to reproduce it.
using MongoDB.Bson.Serialization.Attributes;
using MongoRepository;

class Program
{
    static void Main(string[] args)
    {
        var repo = new MongoRepository<Customer>();
        var testcustomer = new Customer { Id = "12345", Name = "Test customer 1" };

        repo.Add(testcustomer);

        //GetById works fine
        var testretrievedcustomer = repo.GetById("12345");
    }
}

class PropertyChangedBase { }

class Customer : PropertyChangedBase, IEntity
{
    public virtual string Id { get; set; }
    public string Name { get; set; }
}

If you want to use ObjectId's represented as string then you can easily implement your own repository:
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoRepository;

class Program
{
    static void Main(string[] args)
    {
        var repo = new CustomerRepository();
        var testcustomer = new Customer { Name = "Test customer 1" };

        repo.Add(testcustomer);

        //GetById works fine
        var testretrievedcustomer = repo.GetById(testcustomer.Id);
    }
}

class CustomerRepository : MongoRepository<Customer>
{
    public override Customer GetById(string id)
    {
        return this.collection.FindOneByIdAs<Customer>(new ObjectId(id));
    }
}

class PropertyChangedBase { }

class Customer : PropertyChangedBase, IEntity
{
    [BsonRepresentation(BsonType.ObjectId)]
    public virtual string Id { get; set; }
    public string Name { get; set; }
}
Also using an Id of type ObjectId works fine:
using MongoDB.Bson;
using MongoRepository;

class Program
{
    static void Main(string[] args)
    {
        var repo = new MongoRepository<Customer, ObjectId>();
        var testcustomer = new Customer { Name = "Test customer 1" };

        repo.Add(testcustomer);

        //GetById works fine
        var testretrievedcustomer = repo.GetById(testcustomer.Id);
    }
}

class PropertyChangedBase { }

class Customer : PropertyChangedBase, IEntity<ObjectId>
{
    public virtual ObjectId Id { get; set; }
    public string Name { get; set; }
}
Apr 22, 2014 at 2:57 PM
If you're referring to a GetById(ObjectId id) overload: see this commit.