MongoDb ID 生成器不工作

MongoDb id generator is not working

我有一个简单的需求generate string ID if field is null before inserting。如果 属性 有名称 Id,它工作正常,否则它没有。

我关注class:

public abstract class CampaignBase
{
   [BsonId(IdGenerator = typeof(StringObjectIdGenerator))]
   [BsonRepresentation(BsonType.ObjectId)]
   public string CampaignId { get; set; }
}

public class Campaign : CampaignBase {}

现在,当我在数据库中插入 MyData 时,我得到 null 而不是生成的 ID。似乎这些属性只是没有应用,因为如果 属性 有 Id 名称那么如果工作正常并且属性可以更改实际数据布局(string/objectid/等) .

我是这样保存的:

campaigncampaignBase引用的是同一个对象,不用介意。

其中更新选项:

protected static UpdateOptions UpdateOptions => new UpdateOptions
{
    IsUpsert = true
};

这里是:null 到达:

我是不是漏掉了什么?

我只是将命令分解成它在做什么,而不是试图混淆问题:

var myItem = new MyItem() {Name = "Bob"};

if (myItem.MyId == null)
{
    mongoCollection.InsertOne(myItem);
}
else
{
    mongoCollection.ReplaceOne(x => x.MyId == myItem.MyId, myItem);
}

替换 null 的 ID 只会插入 null 作为文档的 _id

我得到了以下扩展:

public static void Save<T, TProperty>(this IMongoCollection<T> collection, T item, Expression<Func<T, TProperty>> idFunc) where TProperty : class
{
    var id = idFunc.Compile()(item);
    if (id == null)
    {
        collection.InsertOne(item);
    }
    else
    {
        var expression = Expression.Lambda<Func<T, bool>>(Expression.Equal(idFunc.Body, Expression.Constant(id, typeof(TProperty))), idFunc.Parameters);
        collection.ReplaceOne(expression, item);
    }
}

示例用法:

CampaignsCollection.Save(campaign, c => c.CampaignId);

感谢@KevinSmith 的想法


这里的实现有点复杂,但它更灵活,由于缓存,它具有更好的客户端界面和性能

public static class MongoExtensions
{
    public static void Save<T>(this IMongoCollection<T> collection, T item)
    {
        if (item == null)
            throw new ArgumentNullException(nameof(item));

        if (MongoSaveCommandHelper<T>.ShouldInsert(item))
        {
            collection.InsertOne(item);
        }
        else
        {
            var expression = MongoSaveCommandHelper<T>.GetIdEqualityExpression(item);
            collection.ReplaceOne(expression, item);
        }
    }

    private static class MongoSaveCommandHelper<T>
    {
        private static readonly Expression<Func<T, bool>> IdIsEqualToDefaultExpression;
        private static readonly Func<T, object> GetId;
        public static Func<T, bool> ShouldInsert { get; }

        static MongoSaveCommandHelper()
        {
            var members = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
            var idProperty = members.SingleOrDefault(x => x.IsDefined(typeof(BsonIdAttribute)))
                             ?? members.FirstOrDefault(m => m.Name.Equals("id", StringComparison.OrdinalIgnoreCase));
            if (idProperty == null)
                throw new InvalidOperationException("Id property has not found");

            var idPropertyType = idProperty.PropertyType;
            var parameter = Expression.Parameter(typeof(T));
            var idPropertyAccess = Expression.MakeMemberAccess(parameter, idProperty);
            var getIdFuncExpression = Expression.Lambda<Func<T, object>>(Expression.Convert(idPropertyAccess, typeof(object)), parameter);

            GetId = getIdFuncExpression.Compile();
            IdIsEqualToDefaultExpression = Expression.Lambda<Func<T, bool>>(Expression.Equal(idPropertyAccess, Expression.Default(idPropertyType)), getIdFuncExpression.Parameters);
            ShouldInsert = IdIsEqualToDefaultExpression.Compile();
        }

        public static Expression<Func<T, bool>> GetIdEqualityExpression(T item) => 
            (Expression<Func<T, bool>>)new IdConstantVisitor(GetId(item)).Visit(IdIsEqualToDefaultExpression);
    }

    private class IdConstantVisitor : ExpressionVisitor
    {
        private readonly object _value;
        public IdConstantVisitor(object value) => _value = value;
        protected override Expression VisitDefault(DefaultExpression node) => Expression.Constant(_value, node.Type);
    }
}

一般情况下,如果Id字段等于default(PropertyType),则插入一个项目,否则替换具有指定id的项目。

下面是我们如何使用它:

CampaignsCollection.Save(campaign);

这段代码为我们处理了一切。没有错误的无效 ID 列,没有额外输入,只是保存它,句点:)