Expression <Func <TEntity, bool >> nesting that works with Entity Framework
I read about 30 questions and about 20 blogs and cannot find the answer. However, I am sure that I need an answer, so if you know about this, please indicate it (pay attention to the last paragraph / sentence about the answers that do not meet my requirement). Thanks!
I save instances of classes that have the same form:
public class Data { public int Id { get; set; } } public class Container { public int Id { get; set; } public Data Data { get; set; } } Queries are currently being written to find Containers through an abstraction layer that requires a Lambda Expression expression that takes a container and returns a bool (predicate). It will not accept IQueryable, although Entity Framework 5 is the ORM of choice.
My task is to present an API surface based on lambda expressions that accept a data type. I cannot change the abstraction layer (I have to pass the predicate that takes the container), so I am trying to transform the expression that I get as:
Expression<Func<Data , bool>> to: Expression<Func<Container , bool>> In my repository class, I added an extra method:
public Container Find( Expression<Func<Data , bool>> predicate ) { IEnumerable<Container> result = QueryStrategy.Fetch( c => predicate.Compile().Invoke( c.Data ) ); return result.FirstOrDefault(); } This complements the existing Find method:
public Container Find( Expression<Func<Container , bool>> predicate ) { IEnumerable<Container> result = QueryStrategy.Fetch( predicate ); return result.FirstOrDefault(); } When using the previous method, the following exception is thrown:
LINQ to Entities does not recognize the 'Boolean Invoke (Container.Data) "method, and this method cannot be translated into a storage expression.
I tried all kinds of things with Expression classes. I just don't see the display method:
Expression<Func<Data , bool>> to: Expression<Func<Container , bool>> Without using Invoke, which is not supported by the Entity Framework (but works great with enumerated data in memory).
Can someone help me get the above scenario using expressions?
I understand that I can use the LinqKit library to solve this problem, but I really want to solve this problem without involving a third-party library.
UPDATE: I am trying to introduce a simplified problem, I meant (initially using DbContext in the sample code) that the code will have access to IQueryable and / or is suitable for using LinqKit AsExpandable (). This is actually not the case - the repository class is not allowed to use IQueryable or any extensions for a particular provider. I have modified the examples above and hopefully this makes things clearer.
This issue is now resolved.
I managed to solve the problem after 4 hours of reading / messing with expressions (amazing technology).
I combined this class (it is not polished or final, but shows how it was achieved):
class ParameterRewriter<TTarget , TSource> : ExpressionVisitor { private ParameterExpression Source; private MemberExpression Target; public Expression<Func<TTarget , bool>> Rewrite( Expression<Func<TSource , bool>> predicate , Expression<Func<TTarget , TSource>> propertyNameExpression ) { var parameter = Expression.Parameter( typeof( TTarget ) ); var propertyName = ( propertyNameExpression.Body as MemberExpression ).Member.Name; Source = predicate.Parameters.Single(); Target = Expression.PropertyOrField( parameter , propertyName ); var body = Visit( predicate.Body ); return Expression.Lambda<Func<TTarget , bool>>( body , parameter ); } protected override Expression VisitParameter( ParameterExpression node ) { if ( node == Source ) { return Target; } return base.VisitParameter( node ); } } And it can be used as follows:
var parameterRewriter = new ParameterRewriter<Container , Data>(); Expression<Func<Data , bool>> dataPredicate = d => ( d.Id == 1 ); var containerPredicate = parameterRewriter.Rewrite( dataPredicate , c => c.Data ); In theory, you need to be able to overcome deeper relationships by exploring the NameExpression property, but I had enough for today.
Now I can see that the SQL Entity Framework generated for each taste of the query is identical:
==========================
Expression<Func<Container , bool>> p = c => c.Data.Id == 1 SELECT [Extent1].[Id] AS [Id], [Extent1].[Data_Id] AS [Data_Id] FROM [dbo].[Container] AS [Extent1] WHERE 1 = [Extent1].[Data_Id] ==========================
Expression<Func<Data , bool>> p = d => d.Id == 1 SELECT [Extent1].[Id] AS [Id], [Extent1].[Data_Id] AS [Data_Id] FROM [dbo].[Container] AS [Extent1] WHERE 1 = [Extent1].[Data_Id] ==========================
Join clients with filtering by given data, then select a client from the result
public Container Find(Expression<Func<Data, bool>> predicate ) { return MyDbContext.Containers .Join(MyDbContext.Containers.Select(c => c.Data) .Where(predicate), c => c.Data.Id, d => d.Id, (c, d) => c) .FirstOrDefault(); }