在Entity Framework Interceptor中添加内部联接到DbScanExpression

时间:2015-02-12 00:10:07

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

我正在尝试使用Entity Framework CommandTree拦截器通过DbContext为每个查询添加过滤器。

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

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

有一个名为EntityFramework.Filters的项目沿着这些方向做了一些事情,但不支持“复杂连接”,这似乎是我正在尝试做的事情。

a demo from TechEd 2014之后,我创建了一个拦截器,它使用带有下面方法的访问者用DbJoinExpression替换DbScanExpressions。一旦我开始工作,我计划将其包装在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?或者我错过了其他什么?

1 个答案:

答案 0 :(得分:3)

你获得该异常的原因是因为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操作之间,您可以添加所需的其他逻辑,如过滤器等。