在 Entity Framework 拦截器中将内部连接添加到 DbScanExpression
Adding Inner Join to DbScanExpression in Entity Framework Interceptor
我正在尝试使用 Entity Framework CommandTree 拦截器通过 DbContext 向每个查询添加过滤器。
为了简单起见,我有两个 table,一个叫 "User",有两列("UserId" 和 "EmailAddress"),另一个叫 "TenantUser" 有两列("UserId" 和 "TenantId")。
每次有用户 table 的 DbScan 时,我想对 TenantUser table 进行内部连接并根据 TenantId 列进行过滤。
有一个名为 EntityFramework.Filters 的项目按照这些思路做了一些事情,但不支持 "complex joins",这似乎是我正在尝试做的。
在 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();
但是,这会导致以下错误:
No property with the name 'EmailAddress' is declared by the type 'Transient.rowtype[(l,CodeFirstDatabaseSchema.User(Nullable=True,DefaultValue=)),(r,CodeFirstDatabaseSchema.User(Nullable=True,DefaultValue=))]'.
我是否错误地构建了 DbJoinExpression?还是我漏掉了什么?
您获得该异常的原因是因为 InnerJoin 生成了来自 table 的列的组合结果,另一方面,查询应该 return [=15] 的那些匹配属性=] 用户,因此您还需要在查询结束时使用投影。这是对我有用的代码:
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 表达式别名来引用特定的连接 table。因此,在我的例子中,您将用户 table 称为 l,将 TennantUser 称为 r。将使用字母 l 和 r 以及别名将结果 SQL 查询发送到数据库。在 InnerJoin 和 Select 操作之间,您可以放置您需要的其他逻辑,例如 Filter 等。
我正在尝试使用 Entity Framework CommandTree 拦截器通过 DbContext 向每个查询添加过滤器。
为了简单起见,我有两个 table,一个叫 "User",有两列("UserId" 和 "EmailAddress"),另一个叫 "TenantUser" 有两列("UserId" 和 "TenantId")。
每次有用户 table 的 DbScan 时,我想对 TenantUser table 进行内部连接并根据 TenantId 列进行过滤。
有一个名为 EntityFramework.Filters 的项目按照这些思路做了一些事情,但不支持 "complex joins",这似乎是我正在尝试做的。
在 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();
但是,这会导致以下错误:
No property with the name 'EmailAddress' is declared by the type 'Transient.rowtype[(l,CodeFirstDatabaseSchema.User(Nullable=True,DefaultValue=)),(r,CodeFirstDatabaseSchema.User(Nullable=True,DefaultValue=))]'.
我是否错误地构建了 DbJoinExpression?还是我漏掉了什么?
您获得该异常的原因是因为 InnerJoin 生成了来自 table 的列的组合结果,另一方面,查询应该 return [=15] 的那些匹配属性=] 用户,因此您还需要在查询结束时使用投影。这是对我有用的代码:
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 表达式别名来引用特定的连接 table。因此,在我的例子中,您将用户 table 称为 l,将 TennantUser 称为 r。将使用字母 l 和 r 以及别名将结果 SQL 查询发送到数据库。在 InnerJoin 和 Select 操作之间,您可以放置您需要的其他逻辑,例如 Filter 等。