表达式<Func <TEntity,bool >>嵌套与Entity Framework一起使用

Fan*_*rns 6 c# linq lambda entity-framework

我已经阅读了大约30个Stack Overflow问题,以及大约20个博客,但找不到我的答案.那就是说,我确信我要求的答案就在那里,所以如果你知道它,请指出(请注意关于不符合我要求的答案的最后一段/句子).谢谢!

我坚持类似于以下类的类的实例:

public class Data {
    public int Id { get; set; }
}

public class Container {
    public int Id { get; set; }
    public Data Data { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

目前编写查询是为了通过抽象层查找容器,该抽象层要求Lambda Expression接受Container并返回bool(谓词).即使Entity Framework 5是首选的ORM,它也不会接受IQueryable.

我的任务是提供一个基于Lambda表达式的API表面,它接受数据类型.我无法更改抽象层(我必须传递一个接受Container的谓词)所以我试图将我收到的表达式转换为:

Expression<Func<Data , bool>>
to:
Expression<Func<Container , bool>>
Run Code Online (Sandbox Code Playgroud)

我在我的存储库类中添加了一个额外的方法,如下所示:

public Container Find( Expression<Func<Data , bool>> predicate ) {

    IEnumerable<Container> result = QueryStrategy.Fetch( c => predicate.Compile().Invoke( c.Data ) );

    return result.FirstOrDefault();

}
Run Code Online (Sandbox Code Playgroud)

这补充了现有的Find方法:

public Container Find( Expression<Func<Container , bool>> predicate ) {

    IEnumerable<Container> result = QueryStrategy.Fetch( predicate );

    return result.FirstOrDefault();

}
Run Code Online (Sandbox Code Playgroud)

使用前一种方法时,会产生以下异常:

LINQ to Entities无法识别方法'Boolean Invoke(Container.Data)'方法,并且此方法无法转换为商店表达式.

我用Expression类尝试了各种各样的东西我只是看不到映射的方法:

Expression<Func<Data , bool>>
to:
Expression<Func<Container , bool>>
Run Code Online (Sandbox Code Playgroud)

不使用Entity Framework不支持的Invoke(但在内存可枚举数据中可以正常工作).

任何人都可以帮助我使用表达式使上述方案工作吗?

我知道我可以使用LinqKit库来解决这个问题,但我真的想在不引入第三方库的情况下解决这个问题.

更新:在我努力提出一个简化的问题时,我暗示(最初在代码示例中使用DbContext)代码可以访问IQueryable和/或适合使用LinqKit的AsExpandable().实际情况并非如此 - 不允许存储库类使用IQueryable或任何特定于供应商的扩展.我修改了上面的例子,希望这会让事情更加清晰.

这个问题现在解决了

Fan*_*rns 3

经过 4 个小时的阅读/修改表达式(令人惊叹的技术)后,我成功解决了这个问题。

我已经将这个类放在一起(它不是完善的或最终的,但显示了它是如何实现的):

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 );

    }

}
Run Code Online (Sandbox Code Playgroud)

它可以这样使用:

var parameterRewriter = new ParameterRewriter<Container , Data>();
Expression<Func<Data , bool>> dataPredicate = d => ( d.Id == 1 );
var containerPredicate = parameterRewriter.Rewrite( dataPredicate , c => c.Data );
Run Code Online (Sandbox Code Playgroud)

理论上,人们应该能够通过检查 propertyNameExpression 来遍历更深层次的关系,但今天我已经受够了。

我现在可以看到 SQL 实体框架为每种类型的查询生成的都是相同的:

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

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]
Run Code Online (Sandbox Code Playgroud)

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

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]
Run Code Online (Sandbox Code Playgroud)

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