【问题标题】:Adding Inner Join to DbScanExpression in Entity Framework Interceptor在实体框架拦截器中向 DbScanExpression 添加内部联接
【发布时间】:2015-02-12 00:10:06
【问题描述】:

我正在尝试使用实体框架 CommandTree 拦截器通过 DbContext 为每个查询添加过滤器。

为简单起见,我有两个表,一个名为“User”,有两列(“UserId”和“EmailAddress”),另一个名为“TenantUser”,有两列(“UserId”和“TenantId”)。

每次对 User 表进行 DbScan 时,我都想对 TenantUser 表进行内部联接并根据 TenantId 列进行过滤。

有一个名为EntityFramework.Filters 的项目可以按照这些思路进行操作,但不支持“复杂连接”,这似乎是我正在尝试做的。

a demo from TechEd 2014 之后,我创建了一个拦截器,该拦截器使用以下方法的访问者将 DbScanExpressions 替换为 DbJoinExpression。一旦我开始工作,我计划将它包装在 DbFilterExpression 中,以将 TenantId 列与已知 ID 进行比较。

    public override DbExpression Visit(DbScanExpression expression)
    {
        var table = expression.Target.ElementType as EntityType;
        if (table != null && table.Name == "User")
        {
            return DbExpressionBuilder.InnerJoin(expression, DbExpressionBuilder.Scan(expression.Target), (l, r) =>
                DbExpressionBuilder.Equal(DbExpressionBuilder.Variable(tenantUserIdProperty.TypeUsage, "UserId"),
                    DbExpressionBuilder.Variable(userIdProperty.TypeUsage, "UserId")));
        }

        return base.Visit(expression);
    }

为了测试上面的代码,我将拦截器添加到 dbContext 并运行以下代码:

    dbContext.Users.Select(u => new { u.EmailAddress }).ToList();

但是,这会导致以下错误:

类型“Transient.rowtype[(l,CodeFirstDatabaseSchema.User(Nullable=True,DefaultValue=)),(r,CodeFirstDatabaseSchema.User(Nullable=True,DefaultValue=) 没有声明名为“EmailAddress”的属性))]'。

我是否错误地构建了 DbJoinExpression?还是我错过了什么?

【问题讨论】:

    标签: c# entity-framework entity-framework-6 entity-framework-6.1


    【解决方案1】:

    您获得该异常的原因是因为 InnerJoin 会生成两个表中的列组合的结果,另一方面,查询应该返回 User 类的匹配属性,因此您还需要在末尾使用投影询问。这是对我有用的代码:

    public override DbExpression Visit(DbScanExpression expression)
    {
        var table = expression.Target.ElementType as EntityType;
        if (table != null && table.Name == "User")
        {
            return expression.InnerJoin(
                DbExpressionBuilder.Scan(expression.Target.EntityContainer.BaseEntitySets.Single(s => s.Name == "TennantUser")),
                (l, r) =>
                    DbExpressionBuilder.Equal(
                        DbExpressionBuilder.Property(l, "UserId"),
                        DbExpressionBuilder.Property(r, "UserId")
                    )
            )
            .Select(exp => 
                new { 
                    UserId = exp.Property("l").Property("UserId"), 
                    Email = exp.Property("l").Property("Email") 
                });
        }
    
        return base.Visit(expression);
    }
    

    正如您在连接操作后看到的,您通过使用指定连接条件的表达式中的 lambda 表达式别名来引用特定的连接表。因此,在我的例子中,您将 User 表称为 l,将 TennantUser 称为 r。将使用字母 l 和 r 以及将生成的 SQL 查询发送到数据库的别名。在 InnerJoin 和 Select 操作之间,您可以放置​​您需要的其他逻辑,例如 Filter 等。

    【讨论】:

    • 这正是我所需要的。非常感谢!
    • 我的回答对你来说可能“有点”太晚了,抱歉:) 但也许将来有人会使用它。
    • 有没有办法将正则表达式传递给访问者,以便更轻松地编写这些扫描?
    • 你所说的“正则表达式”是什么意思?拉姆达?在上面的示例中,只有连接条件是使用 DbExpression 构建的。这是您想要更改的部分吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多