EntityFramework and ReadOnlyCollection

本文关键字:ReadOnlyCollection and EntityFramework | 更新日期: 2023-09-27 18:08:20

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

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

但是,我的域逻辑不允许修改Parents集合(添加,删除),它必须是只读的(只是举个例子)。EntityFramework要求所有的collection都有ICollection<T>接口,它有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中使用只读集合?

EntityFramework and ReadOnlyCollection

在EF Core中,您可以通过使用后备字段封装集合并实现真正的领域建模。因此,您可以将集合定义为私有字段,并将其公开为公共只读属性,如_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的约定被发现的。

您可以向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

短答:没有。很奇怪的是,你可以(), NHibernate可以设置私有类字段,这意味着你可以使用一个公共属性将其封装为只读集合…好吧,你也可以在EF中解决这个问题:实体框架多对多通过包含对象。顺便说一句,我不建议你这样做,因为如果它是一个私有属性,你怎么能添加新的父母呢?)

无论如何,我认为域对象应该是读写的,因为在一天结束时,域对象描述了域内的实体,您应该能够访问和修改它。

另一种解决方案是设计一个接口,将Parents暴露为IReadOnlyList<Person>,也将IPerson暴露为Person成员(Parents除外),并将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代替(你以后避免了强制转换!)。

好吧,有一个方法。它不是很漂亮,因为它在你的领域模型上增加了额外的东西,但我刚刚检查过,它可以工作。

所有功劳归于Owen Craig。

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;

更改数据库上下文中的fluent配置:

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

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

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

DDD快乐!

相关文章:
  • 没有找到相关文章