修复达梦EFCore驱动布尔类型兼容问题
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),
};
}
完
作者:Rick Carter
出处:http://pains.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
浙公网安备 33010602011771号