alh*_*hpe 1 c# entity-framework entity-framework-core
I try do adhere DDD principles on C# collections see more here
And I notice that the model builder method for initial seed HasData relies on the Add Method of ICollection. There is any way to circumvent or trick that method when is called from database update / migrate process?
All I had done until now to trick it follows this path.
1) Create a wrapper around the ICollection named ReadOnlyKollection
2) Have a private ICollection on the model, to avoid exposing to the outside world the collection.
3) Expose the wraper making obsolete Add and some other methods that will trow NotImplementedException if used.
However still the Add method despite of the obsolete warning could be used since is still public and needed for the seed HasData method used on update / migrate database process.
我正在考虑至少从包装类的Add方法内部限制调用方法。
我可能很高兴知道HasData将在何时运行并仅允许此Method处理并为其他任何异常抛出异常的调用成员。
请注意,由于将破坏ICollectoion接口协定,因此无法使用CallerMethodName编译类型功能。
有什么想法可以避免遵循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 },
Run Code Online (Sandbox Code Playgroud)
The linked post is for EF6, while HasData method indicates EF Core. And in EF Core the things are much simpler and do not need any tricks in that regard.
EF Core does not require ICollection<T> for collection navigation property. Any public property returning IEnumerable<T> or derived interface / class is discovered by convention as collection navigation property. Hence you can safely expose your collections as IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T> etc.
EF Core does not require property setter because it can be configured to use the backing field directly.
Additionally, there is no need of special "EF Constructor" because EF Core supports constructors with parameters.
With that being said, you don't need a custom collection interface / class. The sample model could be like this:
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; }
}
Run Code Online (Sandbox Code Playgroud)
and add either the following (simplest - for all properties of all entities)
modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Field);
Run Code Online (Sandbox Code Playgroud)
or for all properties of CountryRegion
builder.UsePropertyAccessMode(PropertyAccessMode.Field);
Run Code Online (Sandbox Code Playgroud)
or just for that navigation property
builder.HasMany(e => e.StateProvinces)
.WithOne(e => e.CountryRegion)
.HasForeignKey(e => e.CountryRegionId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict)
.Metadata.PrincipalToDependent.SetPropertyAccessMode(PropertyAccessMode.Field);
Run Code Online (Sandbox Code Playgroud)
And that's all. You'll be able to use all EF Core functionality like Include / ThenInclude, "navigating" inside LINQ to Entities queries etc. (including HasData). The backing filed allows EF Core to add/remove elements when needed, or even replace the whole collection (in case the field is not readonly).
| 归档时间: |
|
| 查看次数: |
201 次 |
| 最近记录: |