dm庫相比其他庫本身缺少一些語法差異,也可以説是缺陷。
比如:
0和1無法直接在sql中當作真假值用,where 0這種寫法不支持,報錯:查詢使用值表達式作為過濾條件;
t.field is null 也無法直接作為select項;
不支持OUTER APPLY等SQL語法;
以及數據庫函數中的又只能用0和1作為布爾參數值。
但是dm.efcore生成的語句就是這樣的,現在來給修復辦法。
1.查詢使用值表達式作為過濾條件
這個問題其實不好修,正常的修復辦法需要重寫很多代碼,主要是EFCore裏面的這個代碼SqlNullabilityProcessor,主要就是為了優化sql語句的
- a == true -> a
a == true -> a 變成where a這樣,就是這個代碼導致的,但是很遺憾達夢不支持。
public class SqlNullabilityProcessor
{
.....
private SqlExpression OptimizeComparison(
SqlBinaryExpression sqlBinaryExpression,
SqlExpression left,
SqlExpression right,
bool leftNullable,
bool rightNullable,
out bool nullable)
{
var leftNullValue = leftNullable && left is SqlConstantExpression or SqlParameterExpression;
var rightNullValue = rightNullable && right is SqlConstantExpression or SqlParameterExpression;
// a == null -> a IS NULL
// a != null -> a IS NOT NULL
if (rightNullValue)
{
var result = sqlBinaryExpression.OperatorType == ExpressionType.Equal
? ProcessNullNotNull(_sqlExpressionFactory.IsNull(left), leftNullable)
: ProcessNullNotNull(_sqlExpressionFactory.IsNotNull(left), leftNullable);
nullable = false;
return result;
}
// null == a -> a IS NULL
// null != a -> a IS NOT NULL
if (leftNullValue)
{
var result = sqlBinaryExpression.OperatorType == ExpressionType.Equal
? ProcessNullNotNull(_sqlExpressionFactory.IsNull(right), rightNullable)
: ProcessNullNotNull(_sqlExpressionFactory.IsNotNull(right), rightNullable);
nullable = false;
return result;
}
if (TryGetBool(right, out var rightBoolValue)
&& !leftNullable
&& left.TypeMapping!.Converter == null)
{
nullable = leftNullable;
// only correct in 2-value logic
// a == true -> a
// a == false -> !a
// a != true -> !a
// a != false -> a
return sqlBinaryExpression.OperatorType == ExpressionType.Equal ^ rightBoolValue
? OptimizeNonNullableNotExpression(_sqlExpressionFactory.Not(left))
: left;
}
if (TryGetBool(left, out var leftBoolValue)
&& !rightNullable
&& right.TypeMapping!.Converter == null)
{
nullable = rightNullable;
// only correct in 2-value logic
// true == a -> a
// false == a -> !a
// true != a -> !a
// false != a -> a
return sqlBinaryExpression.OperatorType == ExpressionType.Equal ^ leftBoolValue
? OptimizeNonNullableNotExpression(_sqlExpressionFactory.Not(right))
: right;
}
// only correct in 2-value logic
// a == a -> true
// a != a -> false
if (!leftNullable
&& left.Equals(right))
{
nullable = false;
return _sqlExpressionFactory.Constant(
sqlBinaryExpression.OperatorType == ExpressionType.Equal,
sqlBinaryExpression.TypeMapping);
}
if (!leftNullable
&& !rightNullable
&& sqlBinaryExpression.OperatorType is ExpressionType.Equal or ExpressionType.NotEqual)
{
var leftUnary = left as SqlUnaryExpression;
var rightUnary = right as SqlUnaryExpression;
var leftNegated = IsLogicalNot(leftUnary);
var rightNegated = IsLogicalNot(rightUnary);
if (leftNegated)
{
left = leftUnary!.Operand;
}
if (rightNegated)
{
right = rightUnary!.Operand;
}
// a == b <=> !a == !b -> a == b
// !a == b <=> a == !b -> a != b
// a != b <=> !a != !b -> a != b
// !a != b <=> a != !b -> a == b
nullable = false;
return sqlBinaryExpression.OperatorType == ExpressionType.Equal ^ leftNegated == rightNegated
? _sqlExpressionFactory.NotEqual(left, right)
: _sqlExpressionFactory.Equal(left, right);
}
nullable = false;
return sqlBinaryExpression.Update(left, right);
}
}
本來像的修復辦法是重寫這個類,但是這個方法不是虛方法還是私有的,無法重寫,就需要從調用這個方法的所有方法全部重寫,這就太麻煩了,這種重寫還會有其他問題,比如下次升級efcore時,新版本的這個方法有改動,但我重寫了就需要再重新重寫一次。
在想不到辦法的時候,無意間注意到這個方法轉換0 和1 的地方就時TryGetBool這個幾個if判斷中(本來就時想改這裏),都有下面這個判斷
XXX.TypeMapping!.Converter == null
那麼我就想如果boolTypeMapping.Converter不是null的不就可以繞過這個轉換了嗎。
所以就有了下面這個解決辦法,直接上代碼,不解釋了,都能看懂:
public class MyDmBoolTypeMapping : BoolTypeMapping
{
public MyDmBoolTypeMapping(string storeType, DbType? dbType)
: base(new RelationalTypeMappingParameters(
new(typeof(bool), new DmBooleanConverter(), null), storeType))
{
}
protected MyDmBoolTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters)
{
}
protected override string GenerateNonNullSqlLiteral(object value)
{
if (!(bool)value)
{
return "0";
}
return "1";
}
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
{
return (RelationalTypeMapping)(object)new MyDmBoolTypeMapping(parameters);
}
private sealed class DmBooleanConverter : ValueConverter<bool, bool>
{
public DmBooleanConverter()
: base(b => b, b => b)
{
}
}
}
public class MyDmTypeMappingSource:DmTypeMappingSource
{
public MyDmTypeMappingSource([NotNull] TypeMappingSourceDependencies dependencies, [NotNull] RelationalTypeMappingSourceDependencies relationalDependencies)
: base(dependencies, relationalDependencies)
{
var f = typeof(DmTypeMappingSource).GetField("_bool",BindingFlags.Instance| BindingFlags.NonPublic);
f.SetValue(this,new MyDmBoolTypeMapping("BIT", DbType.Boolean));
}
}
上面代碼就時給Bool類型的加上了Converter,它不再是null了,同樣像上一個dmefcore驅動文章説的一樣,用把這個MyDmTypeMappingSource替換服務類
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.ReplaceService<IRelationalTypeMappingSource, MyDmTypeMappingSource>();
}
到這裏就解決了a == true -> a這一種“查詢使用值表達式作為過濾條件”報錯問題,但是不徹底,還會有其他比如 a.Contains(t.Id) -> false這樣的情況,當然還有其他很多情況。
無法直接改代碼修復,我又繼續思考,然後我發現達夢不支持0和1作為條件,但是支持false和true這樣的作為條件,那麼我就把這個MyDmBoolTypeMapping中的GenerateNonNullSqlLiteral方法改成返回FALSE和TRUE不就可以了嗎,所以就有了下面的代碼,驗證發現完美解決問題
public class MyDmBoolTypeMapping : BoolTypeMapping
{
public MyDmBoolTypeMapping(string storeType, DbType? dbType)
: base(new RelationalTypeMappingParameters(
new(typeof(bool), new DmBooleanConverter(), null), storeType))
{
}
protected MyDmBoolTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters)
{
}
protected override string GenerateNonNullSqlLiteral(object value)
{
if (!(bool)value)
{
return "FALSE";
}
return "TRUE";
}
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
{
return (RelationalTypeMapping)(object)new MyDmBoolTypeMapping(parameters);
}
private sealed class DmBooleanConverter : ValueConverter<bool, bool>
{
public DmBooleanConverter()
: base(b => b, b => b)
{
}
}
}
2.數據庫函數中的又只能用0和1作為布爾參數值
先説函數這個問題,因為我們改成了FALSE和TRUE導致的,那麼我再繼續重寫:
public class MyDmSqlNullabilityProcessor : SqlNullabilityProcessor
{
protected override SqlExpression VisitSqlFunction(SqlFunctionExpression sqlFunctionExpression, bool allowOptimizedExpansion, out bool nullable)
{
var exp = base.VisitSqlFunction(sqlFunctionExpression, allowOptimizedExpansion, out nullable);
if (exp is SqlFunctionExpression functionExpression)
{
var arguments = functionExpression.Arguments.Select(e =>
{
if (e is SqlConstantExpression constantExpression && constantExpression.Value is bool value)
{
return _sqlExpressionFactory.Constant(value ? 1 : 0, new IntTypeMapping("INT", DbType.Int32));
}
return e;
}).ToArray();
return functionExpression.Update(functionExpression.Instance, arguments);
}
return exp;
}
}
3.其他問題
主要修復代碼就是下面這個,關鍵代碼是這行UpdateShaperExpression(Visit(shapedQueryExpression.ShaperExpression))
同樣需要其他類new它並且跟其他服務類一樣替換注入,就不多解釋了。
public class MySearchConditionConvertingExpressionVisitor : SearchConditionConvertingExpressionVisitor
{
protected override Expression VisitExtension(Expression extensionExpression)
=> extensionExpression switch
{
ShapedQueryExpression shapedQueryExpression
=> shapedQueryExpression
.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression))
.UpdateShaperExpression(Visit(shapedQueryExpression.ShaperExpression)),
_ => base.VisitExtension(extensionExpression),
};
}
完