Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a DbContext enforce a filter policy?

I would like to pass a value to the ctor of a DbContext and then have that value enforce "filtering" on the related DbSets. Is this possible...or is there a better approach?

Code might look like this:

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; } 
like image 312
Doug Clutter Avatar asked Apr 15 '11 11:04

Doug Clutter


People also ask

What is the difference between DbSet and DbContext?

Intuitively, a DbContext corresponds to your database (or a collection of tables and views in your database) whereas a DbSet corresponds to a table or view in your database. So it makes perfect sense that you will get a combination of both!

What is Filter in Entity framework?

Entity Framework Filters Filters are used to define a predicate that will be applied to every entity in a DbContext, without a developer needing to remember to include it for every query. Common applications include: Security. Multi-tenancy. Logical data partitioning.

Is DbSet part of DbContext?

Definition. A DbSet represents the collection of all entities in the context, or that can be queried from the database, of a given type. DbSet objects are created from a DbContext using the DbContext.

What is Entity Framework SQL?

Entity Framework Core is a modern object-database mapper for . NET. It supports LINQ queries, change tracking, updates, and schema migrations. EF Core works with many databases, including SQL Database (on-premises and Azure), SQLite, MySQL, PostgreSQL, and Azure Cosmos DB.


2 Answers

I decided to implement a custom IDbSet to deal with this. To use this class, you pass in a DbContext, a filter expression, and (optionally) an Action to initialize new entities so they meet the filter criteria.

I've tested enumerating the set and using the Count aggregate functions. Both of them modify the SQL that is generated so they should be much more efficient than filtering on the client.

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);         }     } } 
like image 145
Doug Clutter Avatar answered Sep 19 '22 22:09

Doug Clutter


EF doesn't have any "filter" feature. You can try to achive something like that by inheriting custom DbSet but I think it will still be problematic. For example DbSet directly implements IQueryable so there is probably no way how to include custom condition.

This will require some wrapper which will handle these requirements (can be repository):

  • Condition in select can be handled by wrapping method around DbSet which will add Where condition
  • Insert can be handled by wrapping method as well
  • Update must be handled by overriding SaveChanges and using context.ChangeTracker to get all updated entities. Then you can check if entities were modified.

By wrapper I do not mean custom DbSet implementation - that is too complex:

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 } 
like image 27
Ladislav Mrnka Avatar answered Sep 19 '22 22:09

Ladislav Mrnka