Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# + MongoDB - ObjectId without using MongoDB DataTypes/Attributes

Using MongoDB as my data store makes me to have ObjectID type as primary key by Default. It also can be changed by using Guid with [BsonId] attribute. Which is also defined in MongoDB C# Driver library. I would like to have my Entities independent from Data layer. Can I just use name Id for the property to identify primary key? What else I can try?

like image 420
skalinkin Avatar asked Jan 14 '16 18:01

skalinkin


2 Answers

You can use BsonClassMap instead of using attributes to keep your classes "clean".

// 'clean' entity with no mongo attributes
public class MyClass 
{
    public Guid Id { get; set; }
}

// mappings in data layer
BsonClassMap.RegisterClassMap<MyClass>(cm => 
{
    cm.AutoMap();
    cm.MapIdMember(c => c.Id).SetIdGenerator(CombGuidGenerator.Instance);
});
like image 88
Peter Avatar answered Sep 30 '22 19:09

Peter


OPTION 1: Stick with BsonId and use the Facade Pattern

The [BsonId] property is what you'd use to indicate that the _id property should be linked to a specific property. There isn't a way around that (short of ignoring _id entirely in your crud operations which seems like a bad idea).

So, if you want to separate your "entity" object from your "data layer" then just use a poco class.

-- Use a poco class as a substitute for a record. That class is only for data storage: a quick way to get data in/out of mongo, and a great alternative to working with bson documents.

-- Use a facade on top of that poco class for your entity layer. I don't find it useful to re-invent the wheel, so I typically ask our devs have the entity interface inherit the data-layer (poco) interface, but you can do it however you'd like

Breaking up a sample MyObject class

IMyObjectRecord (declared at the dal and contains only properties and mongo-specific attributes)

IMyObject:IMyObjectRecord (declared at the entity level and may include added properties and methods)

MyObjectRecord:IMyObjectRecord (declared inside the dal, contains mongo-specific attributes. Could be declared internal if you wanted to be really strict about separation).

MyObject:IMyObject (could be, for example, a facade on top of the IMyObjectRecord class you pull from the dal).

Now - you get all the benefits of the facade, and you have a hard-coded link between the properties BUT, you get to keep Bson attributes contained in your dal.

OK, fine. But I really really really HATE that answer.

Yeah. I can accept that. OK, so how about a Convention Pack? If you ABSOLUTELY PROMISE that you'll call your Id's "Id" and you SWEAR that you'll type them as strings (or -- use some other convention that is easy to identify), then we could just use a convention pack like the one I stole from here

namespace ConsoleApp {
    class Program {

        private class Foo {
            // Look Ma!  No attributes!
            public string Id { get; set; }
            public string OtherProperty { get; set; }
        }

        static void Main(string[] args) {

            //you would typically do this in the singleton routine you use 
            //to create your dbClient, so you only do it the one time.
            var pack = new ConventionPack();   
            pack.Add(new StringObjectIdConvention());
            ConventionRegistry.Register("MyConventions", pack, _ => true);
            // Note that we registered that before creating our client...
            var client = new MongoClient();

            //now, use that client to create collections
            var testDb = client.GetDatabase("test");
            var fooCol = testDb.GetCollection<Foo>("foo");
            fooCol.InsertOne(new Foo() { OtherProperty = "Testing", Id="TEST" });

            var foundFoo = fooCol.Find(x => x.OtherProperty == "Testing").ToList()[0];
            Console.WriteLine("foundFooId: " + foundFoo.Id);
       }

        //obviously, this belongs in that singleton namespace where
        //you're getting your db client.
        private class StringObjectIdConvention : ConventionBase, IPostProcessingConvention {
            public void PostProcess(BsonClassMap classMap) {
                var idMap = classMap.IdMemberMap;
                if (idMap != null && idMap.MemberName == "Id" && idMap.MemberType == typeof(string)) {
                    idMap.SetIdGenerator(new StringObjectIdGenerator());
                }
            }
        }
    }
}

What's a Convention Pack

It's a little set of mongo "rules" that get applied during serialize/deserialize. You register it once (when you setup your engine). In this case, the sample pack is telling mongo "if you see a field called 'Id', then save it as a string to _id, please."

These can get really complex and fun. I'd dig into convention packs if you really really really hate the other approach. It's a good way to force all your mongo "attribute driven" logic into one self-contained location.

like image 26
bri Avatar answered Sep 30 '22 17:09

bri