C# + MongoDB - ObjectId without using MongoDB Data

2020-02-12 13:08发布

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?

4条回答
闹够了就滚
2楼-- · 2020-02-12 13:54

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);
});
查看更多
霸刀☆藐视天下
3楼-- · 2020-02-12 13:58

I have stumbled on the same problem myself, and I didn't want to have mongo attributes inside my classes.

I have created a small wrapper example to show how I save and find elements without having an Id property on the data classes of my business logic.

The wrapper class:

public static class Extensions
{
    public static T Unwrap<T>(this MongoObject<T> t)
    {
        return t.Element;
    }
}
public class MongoObject<T>
{
    [BsonId]
    private ObjectId _objectId;
    public T Element { get; }

    public MongoObject(T element)
    {
        Element = element;
        _objectId = new ObjectId();
    }
}

I have also added an extension method to easily unwrap.

Saving an element is simple

public void Save<T>(T t)
{
    _collection.InsertOne(new MongoObject<T>(t));
}

To find an element we can do a linq-like query:

Say we have a data class:

public class Person
{
    public string Name { get; set; }
}

then we can find such an element by

 public Person FindPersonByName(string name)
 {
     return _collection.AsQueryable().FirstOrDefault(
               personObject => personObject.Element.Name == name).Unwrap();
 }

We can also generalize by making MongoObject implement IQueryable<T> and this would make the use of the wrapper even more convenient

查看更多
▲ chillily
4楼-- · 2020-02-12 14:00

If i understand correctly. You want to put your entity to other layer without attribute.

I think you can try this

 public object Id { get; set; }

after that you can put your Id which is coming from mongodb without attribute

查看更多
Viruses.
5楼-- · 2020-02-12 14:02

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.

查看更多
登录 后发表回答