DbContext可以执行filter策略吗?

我想传递一个值给DbContext的ctor,然后让这个值在相关的DbSets上强制执行“过滤”。 这是可能的…还是有更好的方法?

代码可能如下所示:

class Contact { int ContactId { get; set; } int CompanyId { get; set; } string Name { get; set; } } class ContactContext : DbContext { public ContactContext(int companyId) {...} public DbSet<Contact> Contacts { get; set; } } using (var cc = new ContactContext(123)) { // Would only return contacts where CompanyId = 123 var all = (from i in cc.Contacts select i); // Would automatically set the CompanyId to 123 var contact = new Contact { Name = "Doug" }; cc.Contacts.Add(contact); cc.SaveChanges(); // Would throw custom exception contact.CompanyId = 456; cc.SaveChanges; } 

我决定实现一个自定义的IDbSet来处理这个问题。 要使用这个类,你需要传入一个DbContext,一个filterexpression式,以及(可选的)一个Action来初始化新实体,以使它们符合过滤标准。

我testing了枚举集合并使用Count聚合函数。 他们都修改生成的SQL,所以他们应该比在客户端上过滤更有效。

 using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Data.Entity; using System.Linq; using System.Linq.Expressions; namespace MakeMyPledge.Data { class FilteredDbSet<TEntity> : IDbSet<TEntity>, IOrderedQueryable<TEntity>, IOrderedQueryable, IQueryable<TEntity>, IQueryable, IEnumerable<TEntity>, IEnumerable, IListSource where TEntity : class { private readonly DbSet<TEntity> Set; private readonly IQueryable<TEntity> FilteredSet; private readonly Action<TEntity> InitializeEntity; public FilteredDbSet(DbContext context) : this(context.Set<TEntity>(), i => true, null) { } public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter) : this(context.Set<TEntity>(), filter, null) { } public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity) : this(context.Set<TEntity>(), filter, initializeEntity) { } private FilteredDbSet(DbSet<TEntity> set, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity) { Set = set; FilteredSet = set.Where(filter); MatchesFilter = filter.Compile(); InitializeEntity = initializeEntity; } public Func<TEntity, bool> MatchesFilter { get; private set; } public void ThrowIfEntityDoesNotMatchFilter(TEntity entity) { if (!MatchesFilter(entity)) throw new ArgumentOutOfRangeException(); } public TEntity Add(TEntity entity) { DoInitializeEntity(entity); ThrowIfEntityDoesNotMatchFilter(entity); return Set.Add(entity); } public TEntity Attach(TEntity entity) { ThrowIfEntityDoesNotMatchFilter(entity); return Set.Attach(entity); } public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, TEntity { var entity = Set.Create<TDerivedEntity>(); DoInitializeEntity(entity); return (TDerivedEntity)entity; } public TEntity Create() { var entity = Set.Create(); DoInitializeEntity(entity); return entity; } public TEntity Find(params object[] keyValues) { var entity = Set.Find(keyValues); if (entity == null) return null; // If the user queried an item outside the filter, then we throw an error. // If IDbSet had a Detach method we would use it...sadly, we have to be ok with the item being in the Set. ThrowIfEntityDoesNotMatchFilter(entity); return entity; } public TEntity Remove(TEntity entity) { ThrowIfEntityDoesNotMatchFilter(entity); return Set.Remove(entity); } /// <summary> /// Returns the items in the local cache /// </summary> /// <remarks> /// It is possible to add/remove entities via this property that do NOT match the filter. /// Use the <see cref="ThrowIfEntityDoesNotMatchFilter"/> method before adding/removing an item from this collection. /// </remarks> public ObservableCollection<TEntity> Local { get { return Set.Local; } } IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator() { return FilteredSet.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return FilteredSet.GetEnumerator(); } Type IQueryable.ElementType { get { return typeof(TEntity); } } Expression IQueryable.Expression { get { return FilteredSet.Expression; } } IQueryProvider IQueryable.Provider { get { return FilteredSet.Provider; } } bool IListSource.ContainsListCollection { get { return false; } } IList IListSource.GetList() { throw new InvalidOperationException(); } void DoInitializeEntity(TEntity entity) { if (InitializeEntity != null) InitializeEntity(entity); } } } 

EF没有任何“filter”function。 你可以尝试通过inheritance自定义的DbSet来达到类似的目的,但我认为这仍然是有问题的。 例如DbSet直接实现IQueryable,所以可能没有办法如何包含自定义条件。

这将需要一些包装器来处理这些要求(可以是存储库):

  • select中的条件可以通过在DbSet周围的wrap方法来处理,这将添加Where条件
  • 插入也可以通过包装方法来处理
  • 更新必须通过覆盖SaveChanges并使用context.ChangeTracker获取所有更新的实体来处理。 然后你可以检查实体是否被修改。

通过包装我不是指定制的DbSet实现 – 这太复杂:

 public class MyDal { private DbSet<MyEntity> _set; public MyDal(DbContext context) { _set = context.Set<MyEntity>(); } public IQueryable<MyEntity> GetQuery() { return _set.Where(e => ...); } // Attach, Insert, Delete }