避免将私有集合属性暴露给实体框架。 DDD原则 [英] Avoid Exposing Private Collection Properties to Entity Framework. DDD principles

查看:84
本文介绍了避免将私有集合属性暴露给实体框架。 DDD原则的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试在C#集合上遵守DDD原则在此处查看更多信息

I try do adhere DDD principles on C# collections see more here

我注意到初始种子 HasData的模型构建器方法依赖于ICollection的Add方法

到目前为止,我所做的所有诱骗方法都遵循这种方法。

All I had done until now to trick it follows this path.

p>

1)在名为 ReadOnlyKollection

2)有一个私有的在模型上使用ICollection,以避免将集合暴露给外界。

2) Have a private ICollection on the model, to avoid exposing to the outside world the collection.

3)公开包装器,使过时的Add和其他一些方法(如果使用的话,将引发NotImplementedException。

3) Expose the wraper making obsolete Add and some other methods that will trow NotImplementedException if used.

尽管警告过时,但仍然可以使用Add方法,因为它仍然是公共的,并且是更新/迁移数据库过程中使用的种子HasData方法所必需的。

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方法内部限制调用方法。

I am thinking on at least restrict the calling methods from inside the Add method of the wrapper class.

我可能会很好知道何时运行HasData的调用方成员,并且仅允许此Method处理并为任何其他对象抛出异常r。

I could be good to know the calling member when HasData would run and allow only this Method to process and throw an exception for any other.

请注意,不能使用CallerMethodName编译类型功能,因为这会破坏ICollectoion接口合同。.

Notice that CallerMethodName compile-type feature can't be used since will break the ICollectoion interface contract..

有什么想法可以避免遵循DDD原则将私有集合属性暴露给实体框架? (并且仍然具有增强的HasData方法来更新/迁移数据库过程)。

Any ideas to avoid Exposing Private Collection Properties to Entity Framework following DDD principles? (and still have the enhancement of HasData method to update / migrate database process). see some code below..

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中,事情要简单得多,不需要任何技巧。

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不需要 ICollection< T> 用于集合导航属性。按照惯例,任何返回 IEnumerable< T> 的公共属性或派生的接口/类都将被发现为集合导航属性。因此,您可以安全地将集合公开为 IEnumerable< T> IReadOnlyCollection< T> IReadOnlyList< T> 等。

  • 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不需要属性设置器,因为可以将其配置为使用直接支持字段

EF Core does not require property setter because it can be configured to use the backing field directly.

此外,不需要特殊的 EF构造函数,因为EF Core支持带参数的构造函数

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; }
}

并添加以下内容(最简单-对于所有实体的所有属性)

and add either the following (simplest - for all properties of all entities)

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中导航到实体查询(包括 HasData )。提交的后备文件允许EF Core在需要时添加/删除元素,甚至替换整个集合(以防字段不是只读的情况)。

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).

这篇关于避免将私有集合属性暴露给实体框架。 DDD原则的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆