Search code examples
c#mongodbinversion-of-control

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?


Solution

  • 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.