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.

+6
source share
2 answers

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] 

==========================

+2
source

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(); } 
+1
source

All Articles