【问题标题】:MongoDB Composite Key: InvalidOperationException: {document}.Identity is not supportedMongoDB 组合键:InvalidOperationException:{document}.Identity 不受支持
【发布时间】:2017-08-08 23:34:13
【问题描述】:

我在对包含复合 ID 的类进行补水时遇到问题,该复合 ID 又具有基类,我收到一条错误消息,提示 InvalidOperationException: {document}.Identity is not supported.

我要写入数据库的类如下:

public class Product : IEntity<Product>
{
    public readonly Sku Sku;
    public string Name { get; private set; }
    public string Description { get; private set; }
    public bool IsArchived { get; private set; }
    public Identity<Product> Identity => Sku;

    public Product(Sku sku, string name, bool isArchived)
    {
        Sku = sku;
        Name = name;
        IsArchived = isArchived;
    }
}

public interface IEntity<T>
{
    Identity<T> Identity { get; }
}

依次具有 ID Sku,它是由以下复合值(VendorIdSku 中的本地 Value)组成的类:

public class Sku : Identity<Product>
{
    public readonly VendorId VendorId;
    public readonly string Value;

    public Sku(VendorId vendorId, string value)
    {
        VendorId = vendorId;
        Value = value;
    }

    protected override IEnumerable<object> GetIdentityComponents()
    {
        return new object[] {VendorId, Value};
    }
}

public class VendorId : Identity<Vendor>
{
    public readonly string Value;

    public VendorId(string value)
    {
        Value = value;
    }

    protected override IEnumerable<object> GetIdentityComponents()
    {
        return new object[] {Value};
    }
}

我的实体Identity 有一个基类,我在我的 DDD 库中使用它,本质上,这里的 ToString() 输出可以用作 ID,如果这样可以简化事情的话:

public abstract class Identity<T> : IEquatable<Identity<T>>
{
    public override bool Equals(object obj) { /* snip */ }
    public bool Equals(Identity<T> other) { /* snip */ }
    public override int GetHashCode() { /* snip */ }

    public override string ToString()
    {
        var id = string.Empty;

        foreach (var component in GetIdentityComponents())
        {
            if (string.IsNullOrEmpty(id))
                id = component.ToString(); // first item, dont add a divider
            else
                id += "." + component;
        }

        return id;
    }

    protected abstract IEnumerable<object> GetIdentityComponents();
}

我在应用启动时注册了映射:

// rehydrate readonly properties via matched constructor
// https://stackoverflow.com/questions/39604820/serialize-get-only-properties-on-mongodb
ConventionRegistry
    .Register(nameof(ImmutablePocoConvention), new ConventionPack { new ImmutablePocoConvention() }, _ => true);

BsonClassMap.RegisterClassMap<Product>(cm =>
{
    cm.AutoMap();
    cm.MapIdMember(c => c.Sku);
});

BsonClassMap.RegisterClassMap<Vendor>(cm =>
{
    cm.AutoMap();
    cm.MapIdMember(c => c.Id);
});

但是当我去写作时,我得到InvalidOperationException: {document}.Identity is not supported.

// my respositoru method
public void Upsert<T>(T entity) where T : IEntity<T>
{
    this.Database
        .GetCollection<T>(product.GetType().FullName)()
        .ReplaceOneAsync(x=>x.Identity.Equals(entity.Identity), entity, new UpdateOptions() {IsUpsert = true})
        .Wait();
}

var product = new Product(new Sku(new VendorId("dell"), "12434" ),"RAM", false );
myProductRepo.Upsert(product);

不确定这是否因为我直接从我的实体层坚持而变得过于复杂(或者我是否只使用自动映射器和更简单的 POCO)......或者我是否缺少一些映射指令。

感谢任何帮助或指点。

【问题讨论】:

  • 不确定toString() 应该在这里做什么。您可以将entity 对象添加到帖子中吗?我正在通过GetProperties 完成的构造函数帖子查看水合作用。所以public readonly Sku Sku; 不会通过classMap.ClassType.GetTypeInfo() .GetProperties(_bindingFlags) 显示出来,因为它只能作为成员字段访问。您可以将其更改为public Sku Sku { get; },以便通过GetProperties 的构造函数对其进行水合,并将所有只读(Sku - VendorId, ValueVendorId - Value 字段)更改为具有属性getter 方法。
  • 也不确定为什么你有额外的Identity&lt;Product&gt; Identity =&gt; Sku 字段,可能你可以更改为ReplaceOneAsync(x=&gt;x.Sku.Equals(entity.Sku), entity, new UpdateOptions() {IsUpsert = true})。在进行所有提到的更改后,我能够将复合键 ID 与其他字段序列化。
  • 感谢 Veeram 花时间提供帮助 :) 我正在尝试创建一个通用存储库基类。我的 DDD 框架的其他部分使用了 Identity 属性。已在代码中包含 IEntity 类型。如果我使用基础存储库类使其过于复杂,我可以放弃它并坚持插入,因为您提到的是在每个存储库的基础上工作。
  • Np。你的方法看起来不错。您可以尝试添加cm.MapProperty(c =&gt; c.Identity) so x=&gt;x.Identity.Equals(entity.Identity) 可以在用作表达式时序列化 bcoz Identity 不能通过ImmutablePocoConvention 进行水合和注册,因为它不是构造函数arg。

标签: c# mongodb composite-primary-key


【解决方案1】:

我正在通过 GetProperties 完成的构造函数帖子查看水合作用。

所以public readonly Sku Sku; 不会通过classMap.ClassType.GetTypeInfo().GetProperties(_bindingFlags) 显示,因为它只能作为成员字段访问。

您可以将其更改为 public Sku Sku { get; },以便通过构造函数通过 GetProperties 对其进行水合,并将所有只读字段(Sku - VendorId, ValueVendorId - Value 字段)更改为具有属性 getter 方法。

另外,您必须添加cm.MapProperty(c =&gt; c.Identity),因此x=&gt;x.Identity.Equals(entity.Identity) 在用作表达式时可以序列化,因为Identity 不能通过ImmutablePocoConvention 进行水合和注册,因为它不是自动映射逻辑运行时的构造函数arg。

代码更改:

public class Sku : Identity<Product>
{
    public VendorId VendorId { get; }
    public string Value { get; }
}

public class VendorId : Identity<Vendor>
{
    public string Value { get; }
}

BsonClassMap.RegisterClassMap<Product>(cm =>
{
   cm.AutoMap();
   cm.MapIdMember(c => c.Sku);
   cm.MapProperty(c => c.Identity);
});

【讨论】:

  • 谢谢!您帮我整理了代码,进行了一些调整,最终用于产品映射cm.MapCreator(c =&gt; new Product(c.Sku, c.Name, c.IsArchived)); 和供应商ID 映射cm.MapCreator(c =&gt; new VendorId(c.VendorShortname)); 和Sku 映射cm.MapCreator(c =&gt; new Sku(new VendorId(c.VendorId.VendorShortname), c.SkuValue)); 运行良好。最后,通用基础存储库不值得,保存方法减少到一行(获取集合后)。
  • 已经弹出我的代码作为答案,这样你就可以看到我做了什么。再次感谢您的帮助!
【解决方案2】:

这是我使用的代码:

public class ProductMongoRepository : IProductRepository
{
    public ICollection<Product> SearchBySkuValue(string sku)
    {
        return ProductsMongoDatabase.Instance.GetEntityList<Product>();
    }

    public Product GetBySku(Sku sku)
    {
        var collection = ProductsMongoDatabase.Instance.GetCollection<Product>();

        return collection.Find(x => x.Sku.Equals(sku)).First();
    }

    public void SaveAll(IEnumerable<Product> products)
    {
        foreach (var product in products)
        {
            Save(product);
        }
    }

    public void Save(Product product)
    {
        var collection = ProductsMongoDatabase.Instance.GetCollection<Product>();

        collection
            .ReplaceOneAsync(
                x => x.Sku.Equals(product.Sku), 
                product,
                new UpdateOptions() { IsUpsert = true })
            .Wait();
    }
}

在这里设置映射并通过构造函数支持只读字段,对于更复杂的场景和手动 POCO 映射我们可以使用BsonSerializer.RegisterSerializer(typeof(DomainEntityClass), new CustomerSerializer());

public sealed class ProductsMongoDatabase : MongoDatabase
{
    private static volatile ProductsMongoDatabase instance;
    private static readonly object SyncRoot = new Object();

    private ProductsMongoDatabase()
    {
        BsonClassMap.RegisterClassMap<Sku>(cm =>
        {
            cm.MapField(c => c.VendorId);
            cm.MapField(c => c.SkuValue);
            cm.MapCreator(c => new Sku(new VendorId(c.VendorId.VendorShortname), c.SkuValue));
        });

        BsonClassMap.RegisterClassMap<VendorId>(cm =>
        {
            cm.MapField(c => c.VendorShortname);
            cm.MapCreator(c => new VendorId(c.VendorShortname));
        });

        BsonClassMap.RegisterClassMap<Product>(cm =>
        {
            cm.AutoMap();
            cm.MapIdMember(c => c.Sku);
            cm.MapCreator(c => new Product(c.Sku, c.Name, c.IsArchived));
        });

        BsonClassMap.RegisterClassMap<Vendor>(cm =>
        {
            cm.AutoMap();
            cm.MapIdMember(c => c.Id);
            cm.MapCreator(c => new Vendor(c.Id, c.Name));
        });
    }

    public static ProductsMongoDatabase Instance
    {
        get
        {
            if (instance != null)
                return instance;

            lock (SyncRoot)
            {
                if (instance == null)
                    instance = new ProductsMongoDatabase();
            }
            return instance;
        }
    }
}

上面的实现(它是一个单例)派生自下面的基础(任何查询或写入都在父实现中完成):

public abstract class MongoDatabase
{
    private readonly IConfigurationRepository _configuration;
    private readonly IMongoClient Client;
    private readonly IMongoDatabase Database;

    protected MongoDatabase()
    {
        //_configuration = configuration;
        var connection = "mongodb://host:27017";
        var database = "test";
        this.Client = new MongoClient();
        this.Database = this.Client.GetDatabase(database);
    }

    public List<T> GetEntityList<T>()
    {
        return GetCollection<T>()
                .Find(new BsonDocument()).ToList<T>();
    }        

    public IMongoCollection<T> GetCollection<T>()
    {
        return this.Database.GetCollection<T>(typeof(T).FullName);
    }
}

我的 Sku 域模型:

public class Sku : Identity<Product>
{
    public readonly VendorId VendorId;
    public readonly string SkuValue;

    public Sku(VendorId vendorId, string skuValue)
    {
        VendorId = vendorId;
        SkuValue = skuValue;
    }

    protected override IEnumerable<object> GetIdentityComponents()
    {
        return new object[] {VendorId, SkuValue};
    }
}

我的产品领域模型:

public class Product : IEntity<Product>
{
    public readonly Sku Sku;
    public string Name { get; private set; }
    public bool IsArchived { get; private set; }

    public Product(Sku sku, string name, bool isArchived)
    {
        Sku = sku;
        Name = name;
        IsArchived = isArchived;
    }

    public void UpdateName(string name)
    {
        Name = name;
    }

    public void UpdateDescription(string description)
    {
        Description = description;
    }

    public void Archive()
    {
        IsArchived = true;
    }

    public void Restore()
    {
        IsArchived = false;
    }

    // this is used by my framework, not MongoDB
    public Identity<Product> Identity => Sku;
}

我的供应商 ID:

public class VendorId : Identity<Vendor>
{
    public readonly string VendorShortname;

    public VendorId(string vendorShortname)
    {
        VendorShortname = vendorShortname;
    }

    protected override IEnumerable<object> GetIdentityComponents()
    {
        return new object[] {VendorShortname};
    }
}

然后我有我的实体和身份类型:

public interface IEntity<T>
{
    Identity<T> Identity { get; }
}

public abstract class Identity<T> : IEquatable<Identity<T>>
{
    private const string IdentityComponentDivider = ".";
    public override bool Equals(object obj)
    {
        if (ReferenceEquals(this, obj)) return true;
        if (ReferenceEquals(null, obj)) return false;
        if (GetType() != obj.GetType()) return false;
        var other = obj as Identity<T>;
        return other != null && GetIdentityComponents().SequenceEqual(other.GetIdentityComponents());
    }

    public override string ToString()
    {
        var id = string.Empty;

        foreach (var component in GetIdentityComponents())
        {
            if (string.IsNullOrEmpty(id))
                id = component.ToString(); // first item, dont add a divider
            else
                id += IdentityComponentDivider + component;
        }

        return id;
    }

    protected abstract IEnumerable<object> GetIdentityComponents();

    public override int GetHashCode()
    {
        return HashCodeHelper.CombineHashCodes(GetIdentityComponents());
    }

    public bool Equals(Identity<T> other)
    {
        return Equals(other as object);
    }
}

【讨论】:

    猜你喜欢
    • 2018-06-08
    • 2022-01-23
    • 2013-07-03
    • 1970-01-01
    • 2012-05-20
    • 1970-01-01
    • 2020-07-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多