我在实体框架6(代码优先模型)中使用数据库拦截以实现软删除功能,但是间歇性地我从SQL Server抛出异常,说明“SQL语句的某些部分是嵌套的太深了。重写查询或将其分解为较小的查询。“
这是生成sql的一个例子,你可以看到生成的SQL非常离谱:
https://gist.github.com/junderhill/87caceac728809a8ca837b9d8b5189f3
我的EF拦截代码如下:
public class SoftDeleteInterceptor : IDbCommandTreeInterceptor
{
public const string IsDeletedColumnName = "IsDeleted";
public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
{
if (interceptionContext.OriginalResult.DataSpace != DataSpace.SSpace)
{
return;
}
var queryCommand = interceptionContext.Result as DbQueryCommandTree;
if (queryCommand != null)
{
interceptionContext.Result = HandleQueryCommand(queryCommand);
}
}
private static DbCommandTree HandleQueryCommand(DbQueryCommandTree queryCommand)
{
var newQuery = queryCommand.Query.Accept(new SoftDeleteQueryVisitor());
return new DbQueryCommandTree(
queryCommand.MetadataWorkspace,
queryCommand.DataSpace,
newQuery);
}
public class SoftDeleteQueryVisitor : DefaultExpressionVisitor
{
public override DbExpression Visit(DbScanExpression expression)
{
var table = (EntityType)expression.Target.ElementType;
if (table.Properties.All(p => p.Name != IsDeletedColumnName))
{
return base.Visit(expression);
}
var binding = expression.Bind();
return binding.Filter(
binding.VariableType
.Variable(binding.VariableName)
.Property(IsDeletedColumnName)
.NotEqual(DbExpression.FromBoolean(true)));
}
}
答案 0 :(得分:4)
<强>更新强>
这确实有效。然而,随着时间的推移,我们的服务器出现了严重的减速。每次我们重置IIS应用程序池时,它都会很快,但看起来EF会以某种方式缓存或泄漏内存并减慢它的速度。我们暂时把拦截器拉了出来。
我们在工作中遇到了这个问题,经过一段时间后,这里有一个解决方案(尚未经过广泛测试;如果需要,我会更新此更新):
要从根{1}开始,如果您需要添加过滤器,则会返回SoftDeleteQueryVisitor
,从而修改查询。这又会触发return binding.Filter(...)
。这会遍历树,再次找到TreeCreated
,然后重新添加过滤器。显而易见的解决方案是,如果已经有过滤器,则不添加过滤器。
查看树的结构,我们发现过滤器是扫描的父节点:
DbScanExpression
然而,一旦我们意识到我们拥有访客和访客的一生,我们意识到我们可以存储状态:
+ queryCommand {DbQueryCommandTree |_Parameters | |_p__linq__0 : Edm.Guid |_Query : Collection{Record['IDGUID'=Edm.Guid, 'Name'=Edm.String, 'FooField'=Edm.String, 'BarField'=Edm.Boolean, 'Group'=Edm.String, 'IsDeleted'=Edm.Boolean]} |_Project |_Input : 'Limit1' | |_Limit | |_Filter | | |_Input : 'Extent1' | | | |_Filter | | | |_Input : 'Var_8' | | | | |_Scan : CodeFirstDatabase.LeaderboardSuite | | | |_Predicate | | | |_ | | | |_Var(Var_8).IsDeleted | | | |_<> | | | |_True | | |_Predicate | | |_ | | |_Var(Extent1).IDGUID | | |_= | | |_@p__linq__0 | |_2 |_Projection |_NewInstance : Record['IDGUID'=Edm.Guid, 'Name'=Edm.String, 'FooField'=Edm.String, 'BarField'=Edm.Boolean, 'Group'=Edm.String, 'IsDeleted'=Edm.Boolean] |_Column : 'IDGUID' | |_Var(Limit1).IDGUID |_Column : 'Name' | |_Var(Limit1).Name |_Column : 'FooField' | |_Var(Limit1).FooField |_Column : 'BarField' | |_Var(Limit1).BarField |_Column : 'Group' | |_Var(Limit1).Group |_Column : 'IsDeleted' |_Var(Limit1).IsDeleted} System.Data.Entity.Core.Common.CommandTrees.DbQueryCommandTree
我们也知道var newQuery = queryCommand.Query.Accept(new SoftDeleteQueryVisitor());
节点将在Filter
节点之前被命中,因为它是Scan
节点的父节点。此时,我们必须考虑两种可能性:查询中没有Scan
子句,查询中有where
子句。
此时,我刚走过树,确定查询谓词是否在where
或IsDeleted
子句中使用了Equals
字段,并且没有添加节点如果是这样的话。我没有费心检查NotEquals
或Or
条款,因为这些情况极不可能。但是,如果您发现需要它们,它们很容易添加。
Xor