【问题标题】:EntityFramework and ReadOnlyCollectionEntityFramework 和 ReadOnlyCollection
【发布时间】:2015-08-16 04:52:42
【问题描述】:

我使用 EntityFramewotk 和代码优先方法。所以,我这样描述我的模型:

class Person
{
    public long Id { get;set; }
    public string Name { get;set; }
    public ICollection<Person> Parents { get;set; }
}

但是,我的域逻辑不允许修改父母集合(添加、删除),它必须是只读的(仅作为示例)。 EntityFramework 要求所有 Collections 都有ICollection&lt;T&gt; 接口,它有Add 方法(实现结果)和Remove 方法等。 我可以通过显式实现接口来创建自己的集合:

public class ParentsCollection : ICollection<Person>
{
    private readonly HashSet<Person> _collection = new HashSet<Person>();
    void ICollection<Person>.Add(Person item)
    {
        _collection.Add(item);
    }

    bool ICollection<Person>.Remove(Person item)
    {
        return _collection.Remove(item);
    }

    //...and others
}

这隐藏了AddRemove 方法,但根本不保护。因为我总是可以转换为 ICollection 并调用禁止的方法。

所以,我的问题是:

  • 有没有办法在 EntityFramework 中使用只读集合?

【问题讨论】:

标签: c# entity-framework


【解决方案1】:

在 EF Core 中,您可以使用 backing fields 封装集合并实现真正的域建模。 因此,您可以将您的集合定义为私有字段,并将其公开为公共只读属性,如下所示:_parentsParents

class Person
{
    public long Id { get;set; }
    public string Name { get;set; }
    private List<Person> _parents = new List<Person>();
    public IReadOnlyCollection<Person> Parents => _parents.AsReadOnly();
    public void AddParent(Parent parent){
        _parents.Add(parent); 
    }
}

如您所见,Parents 是一个只读集合,不允许消费者对其进行修改。

请注意,_parents 被 ef core 的约定发现为支持字段。

【讨论】:

  • 这似乎是完美的解决方案,值得更多投票。不可能改变类之外的集合(即使通过强制转换)。它仍然可以使用Include(p =&gt; p.Parents) 进行预加载。 PS:我还必须在我的DbContext.OnModelCreating 中添加一些代码才能满足我的特殊需求=> modelBuilder.Entity&lt;Person&gt;().HasMany(g =&gt; g.Parents)
【解决方案2】:

您可以向 EF 公开私有集合属性,允许映射和查询,同时仍保持域对象的成员和关系正确封装。这有点乱,但它有效:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }

    public IEnumerable<Order> Orders
    {
        get { return _orders.AsEnumerable(); }
    }

    private List<Order> _orders { get; set; }

    public Customer()
    {
        _orders = new List<Order>();
    }

    public static Expression<Func<Customer, ICollection<Order>>> OrderMapping
    {
        get { return c => c._orders; }
    }
}

然后映射使用:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<Customer>().HasMany(Customer.OrderMapping);
}

这里进一步描述了这种方法: http://ardalis.com/exposing-private-collection-properties-to-entity-framework

【讨论】:

    【解决方案3】:

    简短回答:。你可以这样做会很奇怪(嗯,NHibernate 可以设置私有类字段,这意味着你可以使用将字段封装为只读集合的​​公共属性来公开它......好吧,你可以解决这个问题EF 的情况也是如此:Entity Framework Many to many through containing object。顺便说一句,我不建议您使用这种方法,因为 如果它是私有财产,您如何添加新父母?

    无论如何,我认为域对象应该是可读写的,因为归根结底,域对象描述了域内的实体,您应该能够访问和修改它。

    另一种解决方案是设计一个接口以将Parents 公开为IReadOnlyList&lt;Person&gt;,以及一个IPerson,其中包含除Parents 之外的所有Person 成员,并将Person 作为IPerson 返回:

    public interface IHasParents
    {
        IReadOnlyList<Person> Parents { get; }
    }
    
    public interface IPerson : IHasParents
    {
        long Id { get; set; }
        string Name { get; set; }
    }
    

    并在Person 上隐式实现IPerson,但Parents显式实现。当您需要在某处返回Person 时,您返回IPerson 而不是Person

    public IPerson CreatePerson(string name, IEnumerable<Persons> parents)
    {
        Person person = new Person { Name = name, Parents = parents };
    
        // Persistence stuff
    
        return person;
    }
    

    您可以争辩说您可以将IPerson 降级为Person,但在这一点上,我会告诉您您需要遵循自己的编码约定:如果您定义永远不会返回Person但是IPerson 那么我会在整个代码库中这样做,如果你需要一个可写的Parents 属性,那么你就返回Person(这样你以后就可以避免演员了!)。

    【讨论】:

    • @Backs 如果有人添加另一个答案,您可以检查您的问答加班;)顺便说一句,我相信 EF 在某些领域非常有限,这是其局限性的一个很好的例子......
    【解决方案4】:

    嗯,有办法。它并不漂亮,因为它在您的域模型上添加了额外的东西,但我刚刚检查了一下,它可以工作。

    所有功劳归欧文·克雷格所有。

    http://owencraig.com/mapping-but-not-exposing-icollections-in-entity-framework/

    简单示例

    假设我们已经建立了组织 => 员工的现有模型。

    要应用这种技术,我们需要稍微改变一下组织模型:

    // this is the main collection that will be persisted, mark it as protected
    protected virtual ICollection<Employee> EmployeesInternal { get; private set; } = new List<Employee>();
    
    // this will expose collection contents to public, seemingly unneccessary `Skip` statement will prevent casting back to Collection
    public IEnumerable<Employee> Employees => EmployeesInternal.Skip(0);
    
    // this is property accessor that will be used to define model and/or in `Include` statements, could be marked as internal if your domain/persistance/services are in the same assembly
    public static Expression<Func<Organization, ICollection<Employee>>> EmployeeAccessor = f => f.EmployeesInternal;
    

    在您的数据库上下文中更改流利的配置:

    modelBuilder.Entity<Organization>().HasMany(Organization.EmployeeAccessor).WithRequired();
    

    如果您不使用 LazyLoading,请更改任何 Include 语句

    var organizations = organizationRepository.GetAll().Include(Organization.EmployeeAccessor)
    

    DDD 快乐!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-15
      • 1970-01-01
      • 2017-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多