ef core - multi tenancy using global filters - multi-tenant

OnModelCreating is called once per db context. This is a problem since the tenant Id is set per request.
How do I re-configure the global filter everytime I create an new instance of the dbcontext?
If I can't use global filter, what is the alternative way?
Update:
I needed to provide a generic filter with an expression like e => e.TenantId == _tenantId. I am using the following expression:
var p = Expression.Parameter(type, "e");
Expression.Lambda(
Expression.Equal(
Expression.Property(p, tenantIdProperty.PropertyInfo),
Expression.Constant(_tenantId))
p);
Since this is run once, _tenantId is fixed. So even if I update it, the first value is captured in the linq expression.
So my question, what is the proper way to set the right side of that equality.

With EF.Core you can actually use the following filter and syntax
protected void OnModelCreating(ModelBuilder modelBuilder)
{
var entityConfiguration = modelBuilder.Entity<MyTenantAwareEntity>();
entityConfiguration.ToTable("my_table")
.HasQueryFilter(e => EF.Property<string>(e, "TenantId") == _tenantProvider.GetTenant())
[...]
The _tenantProvider is the class responsible to get your tenant, in your case from the HttpRequest, to do it you can use HttpContextAccessor.

This is fixed with the following as the right expression
Expression.MakeMemberAccess(
Expression.Constant(this, baseDbContextType),
baseDbContextType.GetProperty("TenantId")
I use a base class for all my db contexts.
GetProperty() works as is because TenantId is public property.

and..
If you use it with soft delete, the solution is; --for ef core 3.1
internal static void AddQueryFilter<T>(this EntityTypeBuilder
entityTypeBuilder, Expression<Func<T, bool>> expression)
{
var parameterType = Expression.Parameter(entityTypeBuilder.Metadata.ClrType);
var expressionFilter = ReplacingExpressionVisitor.Replace(
expression.Parameters.Single(), parameterType, expression.Body);
var currentQueryFilter = entityTypeBuilder.Metadata.GetQueryFilter();
if (currentQueryFilter != null)
{
var currentExpressionFilter = ReplacingExpressionVisitor.Replace(
currentQueryFilter.Parameters.Single(), parameterType, currentQueryFilter.Body);
expressionFilter = Expression.AndAlso(currentExpressionFilter, expressionFilter);
}
var lambdaExpression = Expression.Lambda(expressionFilter, parameterType);
entityTypeBuilder.HasQueryFilter(lambdaExpression);
}
Usage:
if (typeof(ITrackSoftDelete).IsAssignableFrom(entityType.ClrType))
modelBuilder.Entity(entityType.ClrType).AddQueryFilter<ITrackSoftDelete>(e => IsSoftDeleteFilterEnabled == false || e.IsDeleted == false);
if (typeof(ITrackTenant).IsAssignableFrom(entityType.ClrType))
modelBuilder.Entity(entityType.ClrType).AddQueryFilter<ITrackTenant>(e => e.TenantId == MyTenantId);
Thanks to YZahringer

Related

Is it Possible to use reflection on LINQ to Entities to query a dynamic table?

There are many tables in the database that are used as "lookup" tables. All the tables have the same structure, other than the ID column name.
I have found that I can use reflection to open a table and enumerate through the records. The method takes a string (tableName).
Uri serviceUri = new Uri("http://localhost/MyDataService/WcfDataService.svc");
var context = new MyEntities(serviceUri);
var eTable = typeof(MyEntities).GetProperty(tableName).GetValue(context, null) as IEnumerable<object>
foreach (object o in eTable)
...
This works fine, but I want to add a WHERE clause to the query. For example, where InactiveDate == null.
Can I do this? I have been unable to figure this one out.
How about this?
var eTable = (typeof(MyEntities).GetProperty(tableName).GetValue(context, null) as IEnumerable<object>).Where(obj => obj.GetType().GetProperty("InactiveDate").GetValue(obj) == null);
foreach (object o in eTable)
I would suggest to use generics and maybe an interface over reflection.
public function Xyz<TEntity>(Func<MyEntities, IDbSet<TEntity>> dbSetGetter, Expression<Func<TEntity, Boolean>> filter)
{
var serviceUri = new Uri("http://localhost/MyDataService/WcfDataService.svc");
using (var context = new MyEntities(serviceUri))
{
foreach (var entity in dbSetGetter(context).Where(filter))
{
DoSomethingWith(entity);
}
}
}
Usage would be like this
Xyz(context => context.Foo, foo => foo.Bar == 42);
assuming you have an entity Foo with an integer property Bar. The obvious difference to your code is that you have to know the entity type a compile time and I am not sure if you know it then.

Custom IComparer in LINQ OrderBy Lambda expression

I have a custom comparer I want to use with OrderBy. I am trying to build a LINQ expression to make it work. So in essence, I am trying to put together an IComparer, OrderBy inLinq expression.
The expression I am trying to build should look something like:
source => source.OrderBy(lambdaParameter => lambdaParameter.Name, new Parsers.NumericComparer()).
With the code below the expression
'{source => source.OrderBy(lambdaParameter => lambdaParameter.Name)}'
is built and I am trying to add this custom Icomparable to this expression
new Parsers.NumericComparer().
This is because I need to do a natural sort. Can someone please help me on how to include this expression. I am trying to read several threads for the past few hours but I have not done understood LINQ expressions well enough yet to implement this. Thanks!
private void CreateOrderByMethod(PropertyDescriptor prop, string orderByMethodName, string cacheKey)
{
/*
Create a generic method implementation for IEnumerable<T>.
Cache it.
*/
var sourceParameter = Expression.Parameter(typeof(List<T>), "source");
var lambdaParameter = Expression.Parameter(typeof(T), "lambdaParameter");
var accesedMember = typeof(T).GetProperty(prop.Name);
var propertySelectorLambda =
Expression.Lambda(Expression.MakeMemberAccess(lambdaParameter, accesedMember), lambdaParameter);
var orderByMethod = typeof(Enumerable).GetMethods()
.Where(a => a.Name == orderByMethodName &&
a.GetParameters().Length == 2)
.Single()
.MakeGenericMethod(typeof(T), prop.PropertyType);
var orderByExpression = Expression.Lambda<Func<List<T>, IEnumerable<T>>>(
Expression.Call(orderByMethod,
new Expression[] { sourceParameter,
propertySelectorLambda }),
sourceParameter);
cachedOrderByExpressions.Add(cacheKey, orderByExpression.Compile());
}
To create an expression that creates a new instance of an object, use Expression.New.
var newParser = Expression.New(typeof(Parsers.NumericComparer));
Then I suggest you use this overload of Expression.Call instead, so that you don't have to go and manually grab the MethodInfo:
var orderByCall = Expression.Call(
typeof(Enumerable),
"OrderBy",
new [] { typeof(T), prop.PropertyType },
sourceParameter, propertySelectorLambda, newParser);

Determine the root object of a MemberExpression in a LINQ Expression Tree

I'm currently working in a project that involves the encryption of a few columns in an existing database. There is quite a lot of code already written against the current schema, a lot of which is in the form of custom linq-to-sql queries. The number of queries is in the neighbourhood of a 5 figure number, so modifying and re-testing each and everyone of them would be way too expensive.
An alternative we found is to keep the DB schema the same --only altering the columns length slightly, which mean we don't need to change our current entity class definitions-- and instead, changing the expression trees on-the-fly, before they reach the l2sql IQueryProvider, and apply a decryption function on the columns I need. I do this by wrapping the pertinent Table<TEntity> properties of my DataContext with a custom IQueryable<TEntity> implementation, which allows me to preview every single query in the system.
In my current implementation, say I've got this query:
var mydate = new DateTime(2013, 1, 1);
var context = new DataContextFactory.GetClientsContext();
Expression<Func<string>> foo = context.MyClients.First(
c => c.BirthDay < mydate).EncryptedColumn;
but when I catch the query, I change it to read:
Expression<Func<string>> foo = context.Decrypt(
context.MyClients.First(c => c.BirthDay < mydate).EncryptedColumn);
I do this using the ExpressionVisitor class. In the VisitMember method, I check and see whether the current MemberExpression refers to an encrypted column. If it does, I substitute the expression for a method call:
private const string FuncName = "Decrypt";
protected override Expression VisitMember(MemberExpression ma)
{
if (datactx != null && IsEncryptedColumnReference(ma))
return MakeCallExpression(ma);
}
return base.VisitMember(ma);
}
private static bool IsEncryptedColumnReference(MemberExpression ma)
{
return ma.Member.Name == "EncryptedColumn"
&& ma.Member.DeclaringType == typeof(MyClient);
}
private Expression MakeCallExpression(MemberExpression ma)
{
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
var mi = typeof(MyDataContext).GetMethod(FuncName, flags);
return Expression.Call(datactx, mi, ma);
}
datactx is an instance variable with a reference to the expression pointing at the current datacontext (which I look up in a previous pass).
My problem is that if I have a query such as:
var qbeClient = new MyClient { EncryptedColumn = "FooBar" };
Expression<Func<MyClient>> dbquery = () => context.MyClients.First(
c => c.EncryptedColumn == qbeClient.EncryptedColumn);
I want it to be turned into:
Expression<Func<MyClient>> dbquery = () => context.MyClients.First(c =>
context.Decrypt(c.EncryptedColumn) == qbeClient.EncryptedColumn);
instead, what I'm getting is this:
Expression<Func<MyClient>> dbquery = () => context.MyClients.First(c =>
context.Decrypt(c.EncryptedColumn) == context.Decrypt(qbeClient.EncryptedColumn));
Which I don't want, because when I've got an in-memory object, the data is already unencrypted (besides, I don't want a nasty db function call against my objects!)
So, that's basically my question: Having a MemberExpression instance, how can I determine whether it refers to an in-memory object or a row in the database?
Thanks in advance
Edit:
#Shlomo's code actually solves the case I posted, but now one of my previous tests got broken:
var context = new DataContextFactory.GetClientsContext();
Expression<Func<string>> expr = context.MyClients.First().EncryptedColumn;
Expression<Func<string>> expected = context.Decrypt(
context.MyClients.First().EncryptedColumn);
var actual = MyVisitor.Visit(expr);
Assert.AreEqual(expected.ToString(), actual.ToString());
In this case, the reference to EncryptedColumn isn't a parameter, but it should definitely be taken into account by the visitor!
A MemberExpression representing a DB row will be a descendent of a ParameterExpression. In-Memory objects will not, they'll most likely come from some form of a FieldExpression.
In your case, something like this will work for most cases (adding one method to your code, and revising your VisitMember method:
private bool IsFromParameter(MemberExpression ma)
{
if(ma.Expression.NodeType == ExpressionType.Parameter)
return true;
if(ma.Expression is MemberExpression)
return IsFromParameter(ma.Expression as MemberExpression);
return false;
}
protected override Expression VisitMember(MemberExpression ma)
{
if (datactx != null && IsEncryptedColumnReference(ma) && IsFromParameter(ma))
return MakeCallExpression(ma);
}
return base.VisitMember(ma);
}

Subsonic Single WHERE clause

Is it possible to apply a WHERE clause on a SubSonic query?
For example, I get get a single based on id...
db.Single<Storage>(id);
But how can I get a single based on a simple WHERE clause?
db.Single<Storage>(WHERE columnname == "value");
that's possible:
// Will return a Storage instance with property IsNew = true, if record does not exist
// since an object created with new never can be null
var storage1 = new Storage(1); // id = 1
var storage1 = new Storage(Storag.Columns.ColumnName, "value");
// Will return 0 if record not found (subsonic3 only)
var storage3 = (from s in Storage
where s.ColumnName == "value"
select s).SingleOrDefault();
// Will throw an exception if record not found (subsonic3 only)
var storage3 = (from s in Storage
where s.ColumnName == "value"
select s).Single();
Since db is a partial class you can extend it. Just create a new File within the same namespace (but another folder in your solution). This applies to subsonic 2 but will be similar to subsonic 3, I think.
public static partial class DB
{
public static T Single<T>(String columName, Object columnValue) where T: RecordBase<T>, new()
{
return Select().From<T>()
.Where(columnName).IsEqualTo(columnValue)
.ExecuteSingle<T>();
}
}
Thanks for the above, this was a help and eventually I simplified this to the below...
db.Single<Storage>(s => s.ColumnName == "value");

How to dynamically add OR operator to WHERE clause in LINQ

I have a variable size array of strings, and I am trying to programatically loop through the array and match all the rows in a table where the column "Tags" contains at least one of the strings in the array. Here is some pseudo code:
IQueryable<Songs> allSongMatches = musicDb.Songs; // all rows in the table
I can easily query this table filtering on a fixed set of strings, like this:
allSongMatches=allSongMatches.Where(SongsVar => SongsVar.Tags.Contains("foo1") || SongsVar.Tags.Contains("foo2") || SongsVar.Tags.Contains("foo3"));
However, this does not work (I get the following error: "A lambda expression with a statement body cannot be converted to an expression tree")
allSongMatches = allSongMatches.Where(SongsVar =>
{
bool retVal = false;
foreach(string str in strArray)
{
retVal = retVal || SongsVar.Tags.Contains(str);
}
return retVal;
});
Can anybody show me the correct strategy to accomplish this? I am still new to the world of LINQ :-)
You can use the PredicateBuilder class:
var searchPredicate = PredicateBuilder.False<Songs>();
foreach(string str in strArray)
{
var closureVariable = str; // See the link below for the reason
searchPredicate =
searchPredicate.Or(SongsVar => SongsVar.Tags.Contains(closureVariable));
}
var allSongMatches = db.Songs.Where(searchPredicate);
LinqToSql strange behaviour
I recently created an extension method for creating string searches that also allows for OR searches. Blogged about here
I also created it as a nuget package that you can install:
http://www.nuget.org/packages/NinjaNye.SearchExtensions/
Once installed you will be able to do the following
var result = db.Songs.Search(s => s.Tags, strArray);
If you want to create your own version to allow the above, you will need to do the following:
public static class QueryableExtensions
{
public static IQueryable<T> Search<T>(this IQueryable<T> source, Expression<Func<T, string>> stringProperty, params string[] searchTerms)
{
if (!searchTerms.Any())
{
return source;
}
Expression orExpression = null;
foreach (var searchTerm in searchTerms)
{
//Create expression to represent x.[property].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
var containsExpression = BuildContainsExpression(stringProperty, searchTermExpression);
orExpression = BuildOrExpression(orExpression, containsExpression);
}
var completeExpression = Expression.Lambda<Func<T, bool>>(orExpression, stringProperty.Parameters);
return source.Where(completeExpression);
}
private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd)
{
if (existingExpression == null)
{
return expressionToAdd;
}
//Build 'OR' expression for each property
return Expression.OrElse(existingExpression, expressionToAdd);
}
}
Alternatively, take a look at the github project for NinjaNye.SearchExtensions as this has other options and has been refactored somewhat to allow other combinations
There is another, somewhat easier method that will accomplish this. ScottGu's blog details a dynamic linq library that I've found very helpful in the past. Essentially, it generates the query from a string you pass in. Here's a sample of the code you'd write:
Dim Northwind As New NorthwindDataContext
Dim query = Northwind.Products _
.Where("CategoryID=2 AND UnitPrice>3") _
.OrderBy("SupplierId")
Gridview1.DataSource = query
Gridview1.DataBind()
More info can be found at scottgu's blog here.
Either build an Expression<T> yourself, or look at a different route.
Assuming possibleTags is a collection of tags, you can make use of a closure and a join to find matches. This should find any songs with at least one tag in possibleTags:
allSongMatches = allSongMatches.Where(s => (select t from s.Tags
join tt from possibleTags
on t == tt
select t).Count() > 0)

Resources