We had a similar case that we solved using a DbCommandTreeInterceptor. We are using soft deletes and there is no global filter on the value. Yes, that could be implemented but it is not, and going through the millions of lines of code where we do access deleted data would be finding a needle in a haystack.
So what we had was that navigation properties had to be set up with a foreign key to the soft-del column. Meaning if the row in the table you join is deleted it would not be a match. For this, we wanted our foreign keys and primary keys to consist of id + del where del was a constant of 0. Joining del from one table to another in some cases degraded performance.
So by implementing a Visitor for DbJoinExpression we could modify the expression to use a ConstantExpression instead of a DbPropertyExpression to use in the Left/Right of the DbComparisonExpression.
private class JoinSetDelEqualsZeroExpressionVisitor : DefaultExpressionVisitor
{
private static readonly List<DbExpressionKind> SupportedJoins = new() { DbExpressionKind.FullOuterJoin, DbExpressionKind.InnerJoin, DbExpressionKind.LeftOuterJoin };
public override DbExpression Visit(DbJoinExpression expression)
{
var joinType = expression.ExpressionKind;
if (!SupportedJoins.Contains(joinType))
return base.Visit(expression);
if (expression.JoinCondition is not DbAndExpression oldJoinCondition)
return base.Visit(expression);
var newCompareLeft = CreateNewCompare(oldJoinCondition.Left);
var newCompareRight = CreateNewCompare(oldJoinCondition.Right);
var newJoinCondition = DbExpressionBuilder.And(newCompareLeft, newCompareRight);
DbJoinExpression newExp;
if (joinType == DbExpressionKind.LeftOuterJoin)
newExp = DbExpressionBuilder.LeftOuterJoin(expression.Left, expression.Right, newJoinCondition);
else if (joinType == DbExpressionKind.InnerJoin)
newExp = DbExpressionBuilder.InnerJoin(expression.Left, expression.Right, newJoinCondition);
else if (joinType == DbExpressionKind.FullOuterJoin)
newExp = DbExpressionBuilder.FullOuterJoin(expression.Left, expression.Right, newJoinCondition);
else
return base.Visit(expression);
return base.Visit(newExp);
}
private DbExpression CreateNewCompare(DbExpression exp)
{
if (exp is not DbComparisonExpression oldComp ||
oldComp.Left is not DbPropertyExpression oldLeftPropExp ||
oldComp.Right is not DbPropertyExpression oldRightPropExp)
return exp;
if (oldLeftPropExp.Property.Name == "del" || oldRightPropExp.Property.Name == "del")
{
DbExpression newLeft;
if (oldLeftPropExp.Property.Name == "del")
newLeft = DbExpressionBuilder.Equal(oldLeftPropExp, DbExpressionBuilder.Constant(0));
else
newLeft = oldLeftPropExp;
DbExpression newRight;
if (oldRightPropExp.Property.Name == "del")
newRight = DbExpressionBuilder.Equal(oldRightPropExp, DbExpressionBuilder.Constant(0));
else
newRight = oldRightPropExp;
return DbExpressionBuilder.And(newLeft, newRight);
}
return exp;
}
}
The result will be instead of joining table1.del with table2.del the join condition will be table1.del = 0 AND table2.del = 0.
It is not a beautiful solution but works for us.