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
}
这隐藏了Add
和Remove
方法,但根本不保护。因为我总是可以强制转换为ICollection并调用禁止的方法。
那么,我的问题是:
- 是否有一种方法可以在EntityFramework中使用只读集合?
在EF Core中,您可以通过使用后备字段封装集合并实现真正的领域建模。因此,您可以将集合定义为私有字段,并将其公开为公共只读属性,如_parents和Parents。
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快乐!