MongoDB 复合键:InvalidOperationException:{document}。不支持身份
MongoDB Composite Key: InvalidOperationException: {document}.Identity is not supported
我在水化 class 时遇到问题,它由一个复合 ID 组成,而复合 ID 又具有基数 class,我收到一条错误消息 InvalidOperationException: {document}.Identity is not supported.
我正在尝试写入数据库的class如下:
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
,它是由以下复合值(VendorId
和 Sku
中的本地 Value
组成的 class :
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
的基础 class,我在我的 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
//
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)现在是否过于复杂......或者我是否缺少一些映射指令。
感谢任何帮助或指点。
我正在通过构造函数 post 查看水合作用,这是通过 GetProperties
完成的。
所以 public readonly Sku Sku;
不会通过 classMap.ClassType.GetTypeInfo().GetProperties(_bindingFlags)
显示,因为它只能作为成员字段访问。
您可以将其更改为 public Sku Sku { get; }
,以便通过 GetProperties
通过构造函数对其进行水合,并将所有只读字段(Sku - VendorId, Value
和 VendorId - Value
字段)更改为具有 属性 getter 方法.
此外,您必须添加 cm.MapProperty(c => c.Identity)
以便 x=>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);
});
这是我使用的代码:
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);
}
}
我在水化 class 时遇到问题,它由一个复合 ID 组成,而复合 ID 又具有基数 class,我收到一条错误消息 InvalidOperationException: {document}.Identity is not supported.
我正在尝试写入数据库的class如下:
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
,它是由以下复合值(VendorId
和 Sku
中的本地 Value
组成的 class :
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
的基础 class,我在我的 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
//
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)现在是否过于复杂......或者我是否缺少一些映射指令。
感谢任何帮助或指点。
我正在通过构造函数 post 查看水合作用,这是通过 GetProperties
完成的。
所以 public readonly Sku Sku;
不会通过 classMap.ClassType.GetTypeInfo().GetProperties(_bindingFlags)
显示,因为它只能作为成员字段访问。
您可以将其更改为 public Sku Sku { get; }
,以便通过 GetProperties
通过构造函数对其进行水合,并将所有只读字段(Sku - VendorId, Value
和 VendorId - Value
字段)更改为具有 属性 getter 方法.
此外,您必须添加 cm.MapProperty(c => c.Identity)
以便 x=>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);
});
这是我使用的代码:
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);
}
}