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

标签 c# linq entity-framework lambda

我已经阅读了大约 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; }
}

目前编写查询以通过抽象层查找容器,该抽象层需要接受容器并返回 bool(谓词)的 Lambda 表达式。它不会接受 IQueryable,即使 Entity Framework 5 是首选的 ORM。

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

Expression<Func<Data , bool>>
to:
Expression<Func<Container , bool>>

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

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

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

    return result.FirstOrDefault();

}

这是对现有 Find 方法的补充:

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

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

    return result.FirstOrDefault();

}

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

LINQ to Entities does not recognize the method 'Boolean Invoke(Container.Data)' method, and this method cannot be translated into a store expression.

我已经用 Expression 类尝试了各种方法,但我无法找到映射方法:

Expression<Func<Data , bool>>
to:
Expression<Func<Container , bool>>

不使用 Entity Framework 不支持的 Invoke(但可以很好地处理内存中的可枚举数据)。

谁能帮助我使用表达式实现上述场景?

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

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

此问题现已解决

最佳答案

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

    }

}

它可以这样使用:

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

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

我现在可以看到,为每种风格的查询生成的 SQL Entity Framework 是相同的:

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

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]

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

关于c# - 与 Entity Framework 一起使用的表达式<Func<TEntity, bool>> 嵌套,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13734811/

相关文章:

linq - LLBLGen TypedConstantExpression 无法转换为 SetExpression

c# - 如何计算 ObjectQuery<T> 将返回多少个对象?

c# - 从没有代理类的数据库加载?

c# - 我怎样才能将我的数组属性包含到类构造函数中并能够通过对象初始化访问它?

c# - 链接到多个目标的转换 block 的谓词未按预期工作

c# - C# 中使用动态 SQL 的变量表名

c# - EF 模型查询 SELECT WHERE NOT IN

c# - IEqualityComparer 未按预期工作

c# - MVC 3,防止人们通过 URL 在服务器上浏览

c# - 如何使用 Entity Framework 仅保存特定项目?