Can a DbContext enforce a filter policy?
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);
}
}
}
Entity Framework 4.1 - Override Entity (DBSet) with Filter
Try exposing DbSet<Assignee>
and IQueryable<Assignee>
with different names
public partial class MyEntities: DbContext
{
public MyEntities()
: base("name=MyEntities")
{
}
public DbSet<Assignee> AssigneesSet { get; set; }
public IQueryable<Assignee> Assignees
{
get
{
return AssigneesSet.Where(z => z.IsActive == true);
}
}
}
MultiTenancy with DbContext and TenantId - Interceptors, Filters, EF Code-First
I would like to suggest the following approach,
1. Create a column with the name tenant ID for each of the table that contains core business data this is not required for any mapping table.
- Use the approach B, by creating an extension method that returns an
IQueryable
. This method can be an extension of the dbset so that anyone writing a filter clause, can just call this extension method followed by the predicate. This would make the task easier for developers to write code without bothering about tenant ID filter. This particular method will have the code to apply the filter condition for the tenant ID column based on the tenant context in which this query is being executed.
Samplectx.TenantFilter().Where(....)
Instead of relying upon the http context you can have tenant ID passed in all of your service methods so that it will be easy for handling the tenant contacts in both the web and the web job applications. This makes a call free from contacts and more easily testable. The multi tenant entity interface approach looks good and we do have a similar limitation in our application which works fine so far.
Regarding adding index you would be required to add an index for tenant ID column in the tables that have tenant ID and that should take care of the DB side query indexing part.
Regarding the authentication part, I would recommend to use asp.net identity 2.0 with the owin pipeline. The system is very extensible customisable and easy to integrate with any external identity providers if need be in future.
Please do take a look at the repository pattern for entity framework which enables you to write lesser code in a generic fashion. This would help us get rid of code duplication and redundancy and very easy to test from unit test cases
Add filter to all query entity framework
You can implement IHasCompanyId
interface in such entities. And then implement repository pattern as:
public class MyRepository<T>
{
public MyRepository(DbContext dbContext, int companyID)
{
if (dbContext == null)
throw new ArgumentNullException("Null DbContext");
DbContext = dbContext;
DbSet = DbContext.Set<T>();
CompanyID = companyID;
}
protected DbContext DbContext { get; set; }
protected int CompanyID { get; set; }
protected DbSet<T> DbSet { get; set; }
// Add filter here
public virtual IQueryable<T> GetAll()
{
if(typeof(IHasCompanyID).IsAssignableFrom(typeof(T)))
return DbSet.Where(x => x.CompanyID == CompanyID);
else
return DbSet;
}
}
And initialize _financeDal
as:
var _financeDal = new MyRepository<TEntity>(dbContext, companyID);
Related Topics
Rx: How to Respond Immediately, and Throttle Subsequent Requests
How to Create a Directory on Ftp Server Using C#
Do Interfaces Derive from System.Object? C# Spec Says Yes, Eric Says No, Reality Says No
How to Open Outlook New Mail Window C#
Embedding a File Explorer Instance in a Windows Forms Application Form
How to Return 401 Instead of 302 in ASP.NET Core
How to Connect to a Usb Webcam in .Net
Flip the Graphicspath That Draws the Text/String
Differencebetween Streamwriter.Flush() and Streamwriter.Close()
How to Solve Circular Reference
Why Method Overloading Is Not Allowed in Wcf
How to Get Mx Records for a Dns Name with System.Net.Dns
C# Keep Session Id Over Httpwebrequest
How to Determine the Length (I.E. Duration) of a .Wav File in C#
How to Check If a Property Exists on a Dynamic Anonymous Type in C#