c# - 避免将私有集合属性暴露给实体框架。DDD 原则
问题描述
我尝试在 C# 集合上遵守 DDD 原则,在此处查看更多信息
我注意到初始种子HasData 的模型构建器方法依赖于 ICollection 的 Add 方法。当从数据库更新/迁移过程中调用时,有什么方法可以规避或欺骗该方法?
到目前为止,我所做的一切都是为了欺骗它遵循这条道路。
1) 在名为ReadOnlyKollection的 ICollection 周围创建一个包装器
2)模型上有一个私有的ICollection,避免将集合暴露给外界。
3) 公开使 Add 过时的包装器和一些其他方法,如果使用这些方法将引发 NotImplementedException。
然而,尽管有过时的警告,仍然可以使用 Add 方法,因为它仍然是公共的,并且需要用于更新/迁移数据库过程的种子 HasData 方法。
我正在考虑至少从包装类的 Add 方法中限制调用方法。
当 HasData 将运行并仅允许此方法处理并为任何其他方法抛出异常时,我可能会很高兴知道调用成员。
请注意,不能使用 CallerMethodName 编译类型功能,因为这会破坏 IColletoion 接口契约。
有什么想法可以避免遵循 DDD 原则将私有集合属性暴露给实体框架吗?(并且仍然有 HasData 方法的增强来更新/迁移数据库过程)。看下面的一些代码..
public interface IReadOnlyKollection<T> : ICollection<T>
{
}
public class ReadOnlyKollection<T> : IReadOnlyKollection<T>
{
private readonly ICollection<T> _collection;
public ReadOnlyKollection(ICollection<T> collection)
{
_collection = collection;
}
public int Count => _collection.Count;
public bool IsReadOnly => _collection.IsReadOnly;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<T> GetEnumerator() => _collection.GetEnumerator();
public bool Contains(T item) => _collection.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => _collection.CopyTo(array, arrayIndex);
[Obsolete]
public void Add(T item) => _collection.Add(item); // CallerMethodName trick to be applied here or ??
[Obsolete]
public void Clear() => throw new NotImplementedException();
[Obsolete]
public bool Remove(T item) => throw new NotImplementedException();
}
public class StateProvince
{
public StateProvince() //EF Constructor
{
}
public StateProvince(string id, string name)
: this(name)
{
Id = id;
}
public string Id { get; protected set; }
public string Name { get; protected set; }
public string CountryRegionId { get; protected set; }
public virtual CountryRegion CountryRegion { get; protected set; }
}
public class CountryRegion
{
public CountryRegion() //EF Constructor
{
}
public CountryRegion(string id, string name)
: this(name)
{
Id = id;
}
public string Id { get; protected set; }
public string Name { get; protected set; }
private readonly ICollection<StateProvince> _stateProvinces = new List<StateProvince>(); // Private collection for DDD usage
public IReadOnlyKollection<StateProvince> StateProvinces => new ReadOnlyKollection<StateProvince>(_stateProvinces); // Public like read only collection public immutable exposure
}
EntityTypeBuilder<StateProvince> // Code reduced for brevity
builder.HasIndex(e => e.CountryRegionId);
builder.Property(e => e.Id).IsUnicode(false).HasMaxLength(3).ValueGeneratedNever();
builder.Property(e => e.CountryRegionId).IsRequired().IsUnicode(false).HasMaxLength(3);
builder.Property(e => e.Name).IsRequired().HasMaxLength(50);
EntityTypeBuilder<CountryRegion> builder // Code reduced for brevity
builder.Property(e => e.Id).IsUnicode(false).HasMaxLength(3).ValueGeneratedNever();
builder.Property(e => e.Name).IsRequired().HasMaxLength(50);
builder.HasMany(e => e.StateProvinces)
.WithOne(e => e.CountryRegion)
.HasForeignKey(e => e.CountryRegionId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasData(GetData())
private static object[] GetData()
{
return new object[]
{
new { Id = "AF", Name = "Afghanistan", IsDeleted = false, LastModified = DateTimeOffset.UtcNow },
new { Id = "AL", Name = "Albania", IsDeleted = false, LastModified = DateTimeOffset.UtcNow },
new { Id = "DZ", Name = "Algeria", IsDeleted = false, LastModified = DateTimeOffset.UtcNow },
new { Id = "AS", Name = "American Samoa", IsDeleted = false, LastModified = DateTimeOffset.UtcNow },
解决方案
链接的帖子适用于 EF6,而HasData
方法表示 EF Core。而在 EF Core 中,事情要简单得多,在这方面不需要任何技巧。
EF Core 不需要
ICollection<T>
集合导航属性。任何返回或派生接口/类的公共属性IEnumerable<T>
都按照约定作为集合导航属性被发现。因此,您可以安全地将您的集合公开为IEnumerable<T>
、IReadOnlyCollection<T>
等IReadOnlyList<T>
。EF Core 不需要属性设置器,因为它可以配置为直接使用支持字段。
此外,不需要特殊的“EF 构造函数”,因为 EF Core 支持带参数的构造函数。
话虽如此,您不需要自定义集合接口/类。示例模型可能是这样的:
public class CountryRegion
{
public CountryRegion(string name) => Name = name;
public CountryRegion(string id, string name) : this(name) => Id = id;
public string Id { get; protected set; }
public string Name { get; protected set; }
private readonly List<StateProvince> _stateProvinces = new List<StateProvince>(); // Private collection for DDD usage
public IReadOnlyCollection<StateProvince> StateProvinces => _stateProvinces.AsReadOnly(); // Public like read only collection public immutable exposure
}
public class StateProvince
{
public StateProvince(string name) => Name = name;
public StateProvince(string id, string name) : this(name) => Id = id;
public string Id { get; protected set; }
public string Name { get; protected set; }
public string CountryRegionId { get; protected set; }
public virtual CountryRegion CountryRegion { get; protected set; }
}
并添加以下任一(最简单 - 对于所有实体的所有属性)
modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Field);
或对于所有属性CountryRegion
builder.UsePropertyAccessMode(PropertyAccessMode.Field);
或仅用于该导航属性
builder.HasMany(e => e.StateProvinces)
.WithOne(e => e.CountryRegion)
.HasForeignKey(e => e.CountryRegionId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict)
.Metadata.PrincipalToDependent.SetPropertyAccessMode(PropertyAccessMode.Field);
就这样。您将能够使用所有 EF Core 功能,例如Include
/ ThenInclude
、在 LINQ to Entities 查询等内部“导航”(包括HasData
)。支持字段允许 EF Core 在需要时添加/删除元素,甚至替换整个集合(以防该字段不是只读的)。
推荐阅读
- python - 重复循环步骤,直到满足条件
- rasa - rasa webchat : 使用 kubernetes 将 rasa x 和 rasa 开源连接到网站
- c# - 对于 Visual Studio 2022,IVsProject3.OpenItemWithSpecific 返回 -2147024809
- javascript - 从 Rest 调用数据填充 Material 下拉菜单
- html - 最大宽度/高度而不拉伸较小的图像?
- amazon-web-services - 来自不同 VPC 的 Lambda RDS 代理连接
- python - 为什么这个反向 for 循环会错过最后一项?
- python - Python:将列表附加到列表
- android - 从 Django 服务器监听 React Native 应用程序的通知
- windows - .txt 和 .text 文件有什么区别