Related
Before I forget it, my execution context, I'm using .Net 5 with the packages:
Microsoft.EntityFrameworkCore.Design 5.0.6
Microsoft.EntityFrameworkCore.Relational 5.0.6
MySql.EntityFrameworkCore 5.0.3.1
My main goal was to remove the repetitive task of doing expressions when I need to retrieve entities, something like:
public class GetListEntity
{
property int QueryProperty { get; set }
}
public class Entity
{
property int Property { get; set }
}
public async Task<ActionResult> List(GetListEntity getListEntity)
{
var restrictions = new List<Expression<Func<Entity>
if (model.QueryProperty != null)
{
restrictions.Add(e => e.Property == model.QueryProperty);
}
nonTrackedQueryableEntities = this.dbContext.Set<Entity>()
.AsNoTracking();
var expectedEntity = restrictions.Aggregate((sr, nr) => sr.And(nr)); //The And method is below as an extension
var expectedNonTrackedQueryableEntities = nonTrackedQueryableEntities.Where(expectedEntity);
// I will get the total first because the API was meant to paginate the responses.
var total = await expectedNonTrackedQueryableEntities.CountAsync();
}
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> selfExpression, Expression<Func<T, bool>> otherExpression)
{
return selfExpression.Compose(otherExpression, Expression.OrElse);
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> selfExpression, Expression<Func<T, bool>> otherExpression)
{
return selfExpression.Compose(otherExpression, Expression.AndAlso);
}
private static InvocationExpression Casting<T>(this Expression<Func<T, bool>> selfExpression, Expression<Func<T, bool>> otherExpression)
{
return Expression.Invoke(otherExpression, selfExpression.Parameters.Cast<Expression>());
}
private static Expression<Func<T, bool>> Compose<T>(this Expression<Func<T, bool>> selfExpression, Expression<Func<T, bool>> otherExpression, Func<Expression, Expression, Expression> merge)
{
var invocationExpression = selfExpression.Casting(otherExpression);
return Expression.Lambda<Func<T, bool>>(merge(selfExpression.Body, invocationExpression), selfExpression.Parameters);
}
}
I've managed to achieve what I wanted but let's say... partially, because if I try to Query the Database at least two times in a row I get this exception:
System.ArgumentException: An item with the same key has already been added. Key: e
at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareLambda(LambdaExpression a, LambdaExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareBinary(BinaryExpression a, BinaryExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareLambda(LambdaExpression a, LambdaExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareUnary(UnaryExpression a, UnaryExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareExpressionList(IReadOnlyList`1 a, IReadOnlyList`1 b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareMethodCall(MethodCallExpression a, MethodCallExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareExpressionList(IReadOnlyList`1 a, IReadOnlyList`1 b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareMethodCall(MethodCallExpression a, MethodCallExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareExpressionList(IReadOnlyList`1 a, IReadOnlyList`1 b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareMethodCall(MethodCallExpression a, MethodCallExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.Equals(Expression x, Expression y)
at Microsoft.EntityFrameworkCore.Query.CompiledQueryCacheKeyGenerator.CompiledQueryCacheKey.Equals(CompiledQueryCacheKey other)
at Microsoft.EntityFrameworkCore.Query.RelationalCompiledQueryCacheKeyGenerator.RelationalCompiledQueryCacheKey.Equals(RelationalCompiledQueryCacheKey other)
at MySql.EntityFrameworkCore.Query.Internal.MySQLCompiledQueryCacheKeyGenerator.MySQLCompiledQueryCacheKey.Equals(MySQLCompiledQueryCacheKey other)
at MySql.EntityFrameworkCore.Query.Internal.MySQLCompiledQueryCacheKeyGenerator.MySQLCompiledQueryCacheKey.Equals(Object obj)
at System.Collections.Concurrent.ConcurrentDictionary`2.TryGetValue(TKey key, TValue& value)
at Microsoft.Extensions.Caching.Memory.MemoryCache.TryGetValue(Object key, Object& result)
at Microsoft.Extensions.Caching.Memory.CacheExtensions.TryGetValue[TItem](IMemoryCache cache, Object key, TItem& value)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.CountAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)'
Following the trace I managed to discover that the ORM is caching for some reason my expressions (and putting the parameter name, in this case 'e') and failing to detect a key collision the second time it has a similar expression to query the database. I said for some reason because, it's not the main deal but at least is odd that cache is involved in a non tracked query, maybe I'm missing something in the middle.
To undenrstand how i got here i will put the code below.
First an interface to implement in every model related with querying a list of entities and expose the extension method ListRestrictions (almost at the bottom).
public interface IEntityFilter<TEntity>
{
}
The next step was to define Attributes to summarize the action to do with the property and generate a partial expression to use in the extension method:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public abstract class FilterByPropertyAttribute : Attribute
{
protected string FirstPropertyPath { get; }
protected IEnumerable<string> NPropertyPath { get; }
public FilterByPropertyAttribute(string firstPropertyPath, params string[] nPropertyPath)
{
this.FirstPropertyPath = firstPropertyPath;
this.NPropertyPath = nPropertyPath;
}
protected MemberExpression GetPropertyExpression(ParameterExpression parameterExpression)
{
var propertyExpression = Expression.Property(parameterExpression, this.FirstPropertyPath);
foreach (var propertyPath in this.NPropertyPath)
{
propertyExpression = Expression.Property(propertyExpression, propertyPath);
}
return propertyExpression;
}
public abstract Expression GetExpression(ParameterExpression parameterExpression, object propertyValue);
}
And to avoid comparisons with nullable structs
public abstract class NonNullableValuePropertyFilterAttribute : FilterByPropertyAttribute
{
public NonNullableValuePropertyFilterAttribute(string firstPropertyPath, params string[] nPropertyPath)
: base(firstPropertyPath, nPropertyPath)
{
}
public override Expression GetExpression(ParameterExpression parameterExpression, object propertyValue)
{
var propertyExpression = this.GetPropertyExpression(parameterExpression);
return this.GetExpression(propertyExpression, this.GetConvertedConstantExpression(propertyExpression, Expression.Constant(propertyValue)));
}
protected abstract Expression GetExpression(MemberExpression memberExpression, UnaryExpression unaryExpression);
private UnaryExpression GetConvertedConstantExpression(MemberExpression memberExpression, ConstantExpression constantExpression)
{
var convertedConstantExpression = Expression.Convert(constantExpression, memberExpression.Type);
return convertedConstantExpression;
}
}
An Attribute with a defined role would be:
public class EqualPropertyFilterAttribute : NonNullableValuePropertyFilterAttribute
{
public EqualPropertyFilterAttribute(string firstPropertyPath, params string[] nPropertyPath)
: base(firstPropertyPath, nPropertyPath)
{
}
protected override Expression GetExpression(MemberExpression memberExpression, UnaryExpression unaryExpression)
{
return Expression.Equal(memberExpression, unaryExpression);
}
}
And last, the extension itself:
public static class EntityFilterExtensions
{
public static List<Expression<Func<TEntity, bool>>> ListRestrictions<TEntity>(this IEntityFilter<TEntity> entityFilter)
{
var entityFilterType = entityFilter.GetType();
var propertiesInfo = entityFilterType.GetProperties()
.Where(pi => pi.GetValue(entityFilter) != null
&& pi.CustomAttributes.Any(ca => ca.AttributeType
.IsSubclassOf(typeof(FilterByPropertyAttribute))));
var expressions = Enumerable.Empty<Expression<Func<TEntity, bool>>>();
if (propertiesInfo.Any())
{
var entityType = typeof(TEntity);
var parameterExpression = Expression.Parameter(entityType, "e");
expressions = propertiesInfo.Select(pi =>
{
var filterByPropertyAttribute = Attribute.GetCustomAttribute(pi, typeof(FilterByPropertyAttribute)) as FilterByPropertyAttribute;
var propertyValue = pi.GetValue(entityFilter);
var expression = filterByPropertyAttribute.GetExpression(parameterExpression, propertyValue);
return Expression.Lambda<Func<TEntity, bool>>(expression, parameterExpression);
});
}
return expressions.ToList();
}
}
A usage would be:
public class GetListEntity : IEntityFilter<Entity>
{
[EqualPropertyFilter(nameof(Entity.Property))]
property int QueryProperty { get; set }
}
public class Entity
{
property int Property { get; set }
}
public async Task<ActionResult> List(GetListEntity getListEntity)
{
var restrictions = getListEntity.ListRestrictions();
nonTrackedQueryableEntities = this.dbContext.Set<Entity>()
.AsNoTracking();
var expectedEntity = restrictions.Aggregate((sr, nr) => sr.And(nr));
var expectedNonTrackedQueryableEntities = nonTrackedQueryableEntities .Where(expectedEntity);
// I will get the total first because the API was meant to paginate the responses.
var total = await expectedNonTrackedQueryableEntities.CountAsync();
}
And to be discarded, if I Aggregate a non dynamic expression of a list of expressions, the ORM works fine, when I do it with the dynamic ones I get the exception at the beginning.
I found a workaround, changing in the extension method this line:
var parameterExpression = Expression.Parameter(entityType, "e");
For this one:
var parameterExpression = Expression.Parameter(entityType, $"{entityType.Name}{entityFilter.GetHashCode()}");
I wanna know why this happens and maybe if there is another way to fix it.
I posted here before opening a thread in any Github repository because I'm still curious if is a fault of mine for missing something in the way or a bug.
From the explanations it was pretty clear that there is some issue with ParameterExpressions of the dynamically built predicates. And at the end it was in the one of the custom expression extension methods used.
While technically it could be considered ORM bug/issue, they have to solve very complex things during the expression tree transformation, so we must be tolerant and fix our code when possible.
There are some important things you need to be aware of when building dynamically query expression trees.
First, the name of the used ParameterExpressions doesn't matter - they are identified by reference. It's perfectly fine to have all parameters with one and the same name (something that C# compiler won't allow you to create at compile time) as soon as they are separate instances properly referenced by the other expressions.
Second, some things which make sense when creating expression trees for compiling and executing as code (like in LINQ to Objects) are not good for expression trees which are supposed to be transformed to something else (they are valid, but make the transformation harder and lead to bugs/issues). Specifically (what was causing the issue in question) is "calling" lambda expressions. Yes, there is a dedicated Expression.Invoke, but it is causing issues with almost all IQueryable implementations, so it is better to emulate it by "inlining" it, which means replacing parameter instances inside the body with actual expressions.
Here is the modified version of your ExpressionExtensions class applying the aforementioned principle:
public static partial class ExpressionExtensions
{
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
=> Combine(left, right, ExpressionType.AndAlso);
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
=> Combine(left, right, ExpressionType.OrElse);
private static Expression<Func<T, bool>> Combine<T>(Expression<Func<T, bool>> left, Expression<Func<T, bool>> right, ExpressionType type)
{
if (left is null) return right;
if (right is null) return left;
bool constValue = type == ExpressionType.AndAlso ? false : true;
if ((left.Body as ConstantExpression)?.Value is bool leftValue)
return leftValue == constValue ? left : right;
if ((right.Body as ConstantExpression)?.Value is bool rightValue)
return rightValue == constValue ? right : left;
return Expression.Lambda<Func<T, bool>>(Expression.MakeBinary(type,
left.Body, right.Invoke(left.Parameters[0])),
left.Parameters);
}
public static Expression Invoke<T, TResult>(this Expression<Func<T, TResult>> source, Expression arg)
=> source.Body.ReplaceParameter(source.Parameters[0], arg);
}
which uses the following little helpers for parameter replacing:
public static partial class ExpressionExtensions
{
public static Expression ReplaceParameter(this Expression source, ParameterExpression parameter, Expression value)
=> new ParameterReplacer { Parameter = parameter, Value = value }.Visit(source);
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Parameter;
public Expression Value;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Parameter ? Value : node;
}
}
As confirmed in the comments, this solves the issue in question.
Now, unrelated, but as a bonus. Another thing which makes sense for expressions supposed to be compiled is the usage of ConstantExpressions - they are evaluated once and then used in potentially many places.
However for expression trees which are supposed to be transformed to SQL or similar, using ConstantExpressions makes each query different, thus non cacheable. For performance reasons, it is better to use expression type which is treated as variable, thus allowing the cache the transformation and parameterizing the generated SQL query, so both client and database query processors can reuse the "compiled" query/execution plan.
Doing so is quite easy. It does not require changing the type of the predicate or the way you generate. All you need is to replace the ConstantExpression with member (property/field) of a ConstantExpression. In your case it's a matter of replacing
var propertyValue = pi.GetValue(entityFilter);
with
var propertyValue = Expression.Property(Expression.Constant(entityFilter), pi);
and of course adjusting the signatures/implementation (in general try to not use specific expression types if they are not essential for the method), e.g.
FilterByPropertyAttribute class:
public abstract Expression GetExpression(ParameterExpression parameter, Expression value);
NonNullableValuePropertyFilterAttribute class:
public override Expression GetExpression(ParameterExpression parameter, Expression value)
{
var property = this.GetPropertyExpression(parameter);
if (value.Type != property.Type)
value = Expression.Convert(value, property.Type);
return this.GetExpression(property, value);
}
protected abstract Expression GetExpression(MemberExpression member, Expression value);
EqualPropertyFilterAttribute class:
protected override Expression GetExpression(MemberExpression member, Expression value)
=> Expression.Equal(member, value);
All other things, including the usage remain the same. But the result would be nicely parameterized query as if it was created at compile time.
Editing this question in the hope to make it clearer.
We have entity framework code first setup. I've simplified two classes for the purposes of example, in reality there are around 10+ more classes similar to the 'Record', where Item is a navigational property/foreign key.
Item class:
public class Item
{
public int Id { get; set; }
public int AccountId { get; set; }
public List<UserItemMapping> UserItemMappings { get; set; }
public List<GroupItemMapping> GroupItemMappings { get; set; }
}
Record class:
public class Record
{
public int ItemId { get; set; }
public Item Item { get; set; }
}
this.User is an injected user object into each repo and is contained on the repository base.
We have an Item repository with the following code:
var items = this.GetAll()
.Where(i => i.AccountId == this.User.AccountId);
I created the follow expression on the repository base to easily filter on that (in the hope of re-use). We cannot use static extension methods due to how LINQ to entities works (System.NotSupportedException "LINQ to Entities does not recognize the method X and this method cannot be translated into a store expression.").
protected Expression<Func<Item, bool>> ItemIsOnAccount()
{
return item => item.AccountId == this.User.AccountId;
}
I have solved the case of the above, by doing this:
var items = this.GetAll().Where(this.ItemIsOnAccount());
We have additional filtering based on user permissions within that account (again, another case where I do not want to repeat this code in every repo we have):
protected Expression<Func<Item, bool>> SubUserCanAccessItem()
{
return item => this.User.AllowAllItems
|| item.UserItemMappings.Any(d => d.UserId.Value == this.User.Id)
|| item.GroupItemMappings.Any(vm =>
vm.Group.GroupUserMappings
.Any(um => um.UserId == this.User.Id));
}
Which I am able to use as follows:
var items = this.GetAll().Where(this.SubUserCanAccessItem());
However, what we also need, in the Record repository is a way to solve the following:
var records = this.GetAll()
.Where(i => i.Item.AccountId == this.User.AccountId);
Because Item is a single navigational property, I do not know how to apply the expressions I have created to this object.
I want to reuse the expression I created in the repo base on all of these other repos, so that my 'permission based' code is all in the same place, but I cannot simply throw it in because the Where clause in this case is of Expression< Func < Record,bool >>.
Creating an interface with a method of:
Item GetItem();
on it and putting it on the Record class does not work because of LINQ to entities.
I cannot also create a base abstract class and inherit from it, because there could be other objects than Item that need to be filtered on. For instance a Record could also have a 'Thing' on it that has permission logic. Not all objects will require to be filtered by 'Item' and 'Thing', some by only one, some by another, some by both:
var items = this.GetAll()
.Where(this.ItemIsOnAccount())
.Where(this.ThingIsOnAccount());
var itemType2s = this.GetAll().Where(this.ThingIsOnAccount());
var itemType3s = this.GetAll().Where(this.ItemIsOnAccount());
Due to this having a single parent class would not work.
Is there a way in which I can reuse the expressions I have already created, or at least create an expression/modify the originals to work across the board within the OTHER repos that of course return their own objects in a GetAll, but all have a navigation property to Item? How would I need to modify the other repos to work with these?
Thanks
The first step for expression reusability is to move the expressions to a common static class. Since in your case they are tied to User, I would make them User extension methods (but note that they will return expressions):
public static partial class UserFilters
{
public static Expression<Func<Item, bool>> OwnsItem(this User user)
=> item => item.AccountId == user.AccountId;
public static Expression<Func<Item, bool>> CanAccessItem(this User user)
{
if (user.AllowAllItems) return item => true;
return item => item.UserItemMappings.Any(d => d.UserId.Value == user.Id) ||
item.GroupItemMappings.Any(vm => vm.Group.GroupUserMappings.Any(um => um.UserId == user.Id));
}
}
Now the Item repository would use
var items = this.GetAll().Where(this.User.OwnsItem());
or
var items = this.GetAll().Where(this.User.CanAccessItem());
In order to be reusable for entities having Item reference, you would need a small helper utility for composing lambda expressions from other lambda expressions, similar to Convert Linq expression "obj => obj.Prop" into "parent => parent.obj.Prop".
It's possible to implement it with Expression.Invoke, but since not all query providers support for invocation expressions (EF6 doesn't for sure, EF Core does), as usual we'll use a custom expression visitor for replacing a lambda parameter expression with another arbitrary expression:
public static partial class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
=> new ParameterReplacer { Source = source, Target = target }.Visit(expression);
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Source ? Target : node;
}
}
And the two composing functions are as follows (I don't like the name Compose, so sometimes I use the name Map, sometimes Select, Bind, Transform etc., but functionally they do the same. In this case I'm using Apply and ApplyTo, with the only difference being the transformation direction):
public static partial class ExpressionUtils
{
public static Expression<Func<TOuter, TResult>> Apply<TOuter, TInner, TResult>(this Expression<Func<TOuter, TInner>> outer, Expression<Func<TInner, TResult>> inner)
=> Expression.Lambda<Func<TOuter, TResult>>(inner.Body.ReplaceParameter(inner.Parameters[0], outer.Body), outer.Parameters);
public static Expression<Func<TOuter, TResult>> ApplyTo<TOuter, TInner, TResult>(this Expression<Func<TInner, TResult>> inner, Expression<Func<TOuter, TInner>> outer)
=> outer.Apply(inner);
}
(Nothing special there, code provided for completeness)
Now you could reuse the original filters by "applying" them to a expression which selects Item property from another entity:
public static partial class UserFilters
{
public static Expression<Func<T, bool>> Owns<T>(this User user, Expression<Func<T, Item>> item)
=> user.OwnsItem().ApplyTo(item);
public static Expression<Func<T, bool>> CanAccess<T>(this User user, Expression<Func<T, Item>> item)
=> user.CanAccessItem().ApplyTo(item);
}
and add the following to the entity repository (in this case, Record repository):
static Expression<Func<Record, Item>> RecordItem => entity => entity.Item;
which would allow you to use there
var records = this.GetAll().Where(this.User.Owns(RecordItem));
or
var records = this.GetAll().Where(this.User.CanAccess(RecordItem));
This should be enough to satisfy your requirements.
You can go further and define an interface like this
public interface IHasItem
{
Item Item { get; set; }
}
and let the entities implement it
public class Record : IHasItem // <--
{
// Same as in the example - IHasItem.Item is auto implemented
// ...
}
then add additional helpers like this
public static partial class UserFilters
{
public static Expression<Func<T, Item>> GetItem<T>() where T : class, IHasItem
=> entity => entity.Item;
public static Expression<Func<T, bool>> OwnsItem<T>(this User user) where T : class, IHasItem
=> user.Owns(GetItem<T>());
public static Expression<Func<T, bool>> CanAccessItem<T>(this User user) where T : class, IHasItem
=> user.CanAccess(GetItem<T>());
}
which would allow you omit the RecordItem expression in the repository and use this instead
var records = this.GetAll().Where(this.User.OwnsItem<Record>());
or
var records = this.GetAll().Where(this.User.CanAccessItem<Record>());
Not sure if it gives you a better readability, but is an option, and syntactically is closer to Item methods.
For Thing etc. just add similar UserFilters methods.
As a bonus, you can go even further and add the usual PredicateBuilder methods And and Or
public static partial class ExpressionUtils
{
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
=> Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left.Body,
right.Body.ReplaceParameter(right.Parameters[0], left.Parameters[0])), left.Parameters);
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
=> Expression.Lambda<Func<T, bool>>(Expression.OrElse(left.Body,
right.Body.ReplaceParameter(right.Parameters[0], left.Parameters[0])), left.Parameters);
}
so you could use something like this if needed
var items = this.GetAll().Where(this.User.OwnsItem().Or(this.User.CanAccessItem()));
in the Item repository, or
var records = this.GetAll().Where(this.User.OwnsItem<Record>().Or(this.User.CanAccessItem<Record>()));
in the Record repository.
I can't really tell if this could work in your case, depends on how your entities might be setup, but one thing you can try is to have an interface like IHasItemProperty with a GetItem() method and have the entities where you want to use this implement that interface. Something like this :
public interface IHasItemProperty {
Item GetItem();
}
public class Item: IHasItemProperty {
public Item GetItem() {
return this;
}
public int UserId {get; set;}
}
public class Record: IHasItemProperty {
public Item item{get;set;}
public Item GetItem() {
return this.item;
}
}
public class Repo
{
protected Expression<Func<T, bool>> ItemIsOnAccount<T>() where T: IHasItemProperty
{
return entity => entity.GetItem().UserId == 5;
}
}
I have used an int just to make things simpler.
You should be able to do this with .AsQueryable().
class Account
{
public IEnumerable<User> Users { get; set; }
public User SingleUser { get; set; }
static void Query()
{
IQueryable<Account> accounts = new Account[0].AsQueryable();
Expression<Func<User, bool>> userExpression = x => x.Selected;
Expression<Func<Account, bool>> accountAndUsersExpression =
x => x.Users.AsQueryable().Where(userExpression).Any();
var resultWithUsers = accounts.Where(accountAndUsersExpression);
Expression<Func<Account, bool>> accountAndSingleUserExpression =
x => new[] { x.SingleUser }.AsQueryable().Where(userExpression).Any();
var resultWithSingleUser = accounts.Where(accountAndSingleUserExpression);
}
}
class User
{
public bool Selected { get; set; }
}
You should only use sql (or your database like) items for the predicate. If you put this.User.AccountId into your lambda, that does not exists at database and can't be parsed by it, that's the source of your error message.
My question is very similar to the following two questions but I have an added requirement that these do not satisfy.
How to set property value using Expressions?
How set value a property selector Expression<Func<T,TResult>>
Just like those questions, I have an Expression<Func<TEntity, TProperty>> where I want to set a value to the specified property. And those solutions work great if the body of the expression is only one level deep, such as x => x.FirstName but they don't work at all if that body is deeper, like x => x.Parent.FirstName.
Is there some way to take this deeper expression and set the value to it? I don't need a terribly robust execution-deferred solution but I do need something that I can execute on an object and it would work whether 1-level or multiple levels deep. I also need to support most typical types you'd expect in a database (long, int?, string, Decimal, DateTime?, etc. although I don't care about more complex things like geo types).
For conversation's sake, let's say we're working with these objects although assume we need to handle N levels deep, not just 1 or 2:
public class Parent
{
public string FirstName { get; set; }
}
public class Child
{
public Child()
{
Mom = new Parent(); // so we don't have to worry about nulls
}
public string FavoriteToy { get; set; }
public Parent Mom { get; set; }
}
and let's say this is our unit test:
[TestFixture]
public class Tests
{
[Test]
public void MyTest()
{
var kid = new Child();
Expression<Func<Child, string>> momNameSelector = (ch => ch.Mom.FirstName);
Expression<Func<Child, string>> toyNameSelector = (ch => ch.FavoriteToy);
kid.ExecuteMagicSetter(momNameSelector, "Jane");
kid.ExecuteMagicSetter(toyNameSelector, "Bopp-It!");
Assert.That(kid.Mom.FirstName, Is.EqualTo("Jane"));
Assert.That(kid.FavoriteToy, Is.EqualTo("Bopp-It!"));
}
}
and our extension method we're looking at (I'm not set on it needing to be an extension method but it seems simple enough) would look like this:
public static TEntity ExecuteMagicSetter<TEntity, TProperty>(this TEntity obj, Expression<Func<TEntity, TProperty>> selector, TProperty value)
where TEntity : class, new() // I don't require this but I can allow this restriction if it helps
{
// magic
}
P.S. This version of code was written in the SO editor - my apologies for dumb syntax issues but this should be darn close! #LockedDownWorkstationsSuck
As I stated in the comments, it shouldn't be all that complicated. With the selector, just add an assignment to the expression. You'll just need to compile and run the expression.
public static TEntity ExecuteMagicSetter<TEntity, TProperty>(
this TEntity obj,
Expression<Func<TEntity, TProperty>> selector,
TProperty value)
{
var setterExpr = CreateSetter(selector);
setterExpr.Compile()(obj, value);
return obj;
}
private static Expression<Action<TEntity, TProperty>> CreateSetter<TEntity, TProperty>
(Expression<Func<TEntity, TProperty>> selector)
{
var valueParam = Expression.Parameter(typeof(TProperty));
var body = Expression.Assign(selector.Body, valueParam);
return Expression.Lambda<Action<TEntity, TProperty>>(body,
selector.Parameters.Single(),
valueParam);
}
I'm working with the Entity Framework Code First and trying to map from my entity classes to my DTO classes. But I'm having difficult figuring out how to write the Selector.
In this small example, I have created a Person class and a Address class.
In the DTO classes I have created a Selector, which maps from my Entity to my DTO, but is it not possible to use AddressDto.Selector inside the PersonDto.Selector?
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Street { get; set; }
}
Now I'm trying to map this to a DTO classes.
public class PersonDto
{
public static Expression<Func<Person, PersonDto>> Selector =
entity => new PersonDto
{
Id = entity.Id,
Name = entity.Name,
Address = ??? AddressDTO.Selector
};
public int Id { get; set; }
public string Name { get; set; }
public AddressDto Address { get; set; }
}
public class AddressDto
{
public static Expression<Func<Address, AddressDto>> Selector =
entity => new AddressDto
{
Id = entity.Id,
Street = entity.Street
};
public int Id { get; set; }
public string Street { get; set; }
}
I know that I could just write this inside PersonDto.Selector
Address = new AddressDto
{
Id = entity.Address.Id,
Street = entity.Address.Street
};
But I'm looking for a way to reuse the Selector from the AddressDto class. To keep the code clean and separate responsibility between classes.
So, we're going to need several helper methods here, but once we have them, things should be fairly simple.
We'll start out with this class that can replace all instance of one expression with another:
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Then an extension method to make calling it easier:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
Next we'll write a compose extension method. This will take a lambda that computes an intermediate result, and then another lambda that computes a final result based on the intermediate result and returns a new lambda that takes what the initial lambda returns and returns the output of the final lambda. In effect it calls one function, and then calls another on the result of the first, but with expressions, not methods.
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
Now we'll create a Combine method. This will be similar, but subtly different. It will take a lambda that computes an intermediate result, and a function that uses both the initial input, and the intermediate input, to compute a final result. It's basically the same as the Compose method, but the second function also gets to know about the first parameter:
public static Expression<Func<TFirstParam, TResult>>
Combine<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TFirstParam, TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], param)
.Replace(second.Parameters[1], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
Okay, now that we have all of this, we get to use it. First thing we'll do is create a static constructor; we won't be able to inline all we have to do into the field initializer. (The other option is to make a static method that computes this, and have the initializer call it.)
After that we'll create an expression that take a person and returns it's address. It's one of the missing puzzle pieces in the expressions that you have. Using that, we'll compose that address selector with the AddressDto selector, and then use Combine on that. Using that we have a lambda that takes a Person and an AddressDTO and returns a PersonDTO. So in there we have basically what you wanted to have, but with an address parameter given to us to assign to the address:
static PersonDto()
{
Expression<Func<Person, Address>> addressSelector =
person => person.Address;
Selector = addressSelector.Compose(AddressDto.Selector)
.Combine((entity, address) => new PersonDto
{
Id = entity.Id,
Name = entity.Name,
Address = address,
});
}
First off, I would use only a single class to do this. No need for DTO class that does the same thing.
If you insist on having secondary DTO classes, then I would simply create extension methods for them. Ex: PersonDTO personDto = myPerson.ToDTO()
Here is code I just wrote in the context of Linq2Sql:
public static CampaignEntity GetOrCreate(int pid, SynchDBDataContext db)
{
CampaignEntity ce = (from c in db.CampaignEntities
where c.pid == pid
select c).FirstOrDefault();
if (ce == null)
{
ce = new CampaignEntity();
ce.pid = pid;
db.CampaignEntities.InsertOnSubmit(ce);
}
return ce;
}
The things to make a generic routine would be:
- the entity type
- the type of the primary key column
- the value of the primary key
What I've seen done before is something like this:
public static TEntity GetOrCreate<TEntity, TPKey>(TPKey pid, SyncDBCataContext db, Func<int, TEntity> create){...}
The Func would be the method that creates the entity, so you could inline it when you call this function or you could take this out and hard-code how an entity is created like you have done in your question.
Here is a method that will get by id:
public T GetById<TEntity, TPKey>(TPKey id, DataContext context) where TEntity : class
{
MetaTable metaTable = context.Mapping.GetTable(typeof(TEntity));
MetaDataMember primaryKeyMetaDataMember = metaTable.RowType.DataMembers.SingleOrDefault(d => d.IsPrimaryKey);
return context.GetTable<TEntity>().SingleOrDefault(GetEqualityLambdaExpression<TEntity>(primaryKeyMetaDataMember.Name, id));
}
Here is a method that will create the expression for the filter needed by the get by id method:
public Expression<Func<T, bool>> GetEqualityLambdaExpression<T>(string fieldName, object constantValue)
{
ParameterExpression param = Expression.Parameter(typeof(T), "e");
Expression<Func<T, bool>> expression = Expression.Lambda<Func<T, bool>>(
Expression.Equal(Expression.Property(param, fieldName),
Expression.Constant(constantValue)),
new ParameterExpression[] { param });
return expression;
}
As you can see there is some use of reflection. If performance is a big concern, you can implement some sort of caching to reduce the overhead in future calls.