¿Puede un DbContext aplicar una política de filtro?

Me gustaría pasar un valor al ctor de un DbContext y luego hacer que ese valor imponga el “filtrado” en los DbSets relacionados. ¿Es esto posible … o hay un mejor enfoque?

El código podría verse así:

class Contact { int ContactId { get; set; } int CompanyId { get; set; } string Name { get; set; } } class ContactContext : DbContext { public ContactContext(int companyId) {...} public DbSet 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; } 

Decidí implementar un IDbSet personalizado para tratar con esto. Para usar esta clase, transfiere un DbContext, una expresión de filtro y (opcionalmente) una Acción para inicializar nuevas entidades para que cumplan con los criterios de filtro.

He probado la enumeración del conjunto y el uso de las funciones agregadas de recuento. Ambos modifican el SQL que se genera, por lo que deberían ser mucho más eficientes que el filtrado en el cliente.

 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 : IDbSet, IOrderedQueryable, IOrderedQueryable, IQueryable, IQueryable, IEnumerable, IEnumerable, IListSource where TEntity : class { private readonly DbSet Set; private readonly IQueryable FilteredSet; private readonly Action InitializeEntity; public FilteredDbSet(DbContext context) : this(context.Set(), i => true, null) { } public FilteredDbSet(DbContext context, Expression> filter) : this(context.Set(), filter, null) { } public FilteredDbSet(DbContext context, Expression> filter, Action initializeEntity) : this(context.Set(), filter, initializeEntity) { } private FilteredDbSet(DbSet set, Expression> filter, Action initializeEntity) { Set = set; FilteredSet = set.Where(filter); MatchesFilter = filter.Compile(); InitializeEntity = initializeEntity; } public Func 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() where TDerivedEntity : class, TEntity { var entity = Set.Create(); 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); } ///  /// Returns the items in the local cache ///  ///  /// It is possible to add/remove entities via this property that do NOT match the filter. /// Use the  method before adding/removing an item from this collection. ///  public ObservableCollection Local { get { return Set.Local; } } IEnumerator IEnumerable.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 no tiene ninguna función de “filtro”. Puede tratar de lograr algo así heredando un DbSet personalizado, pero creo que seguirá siendo problemático. Por ejemplo, DbSet implementa directamente IQueryable, por lo que probablemente no haya forma de cómo incluir una condición personalizada.

Esto requerirá un contenedor que manejará estos requisitos (puede ser un repository):

  • Condición en seleccionar se puede manejar mediante el método de envoltura alrededor de DbSet, que agregará la condición Where
  • El inserto también se puede manipular mediante el método de envoltura
  • La actualización debe manejarse anulando SaveChanges y usando context.ChangeTracker para obtener todas las entidades actualizadas. Luego puede verificar si las entidades fueron modificadas.

Por envoltorio no me refiero a la implementación personalizada de DbSet , que es demasiado compleja:

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