dotnetcore / FreeSql

🦄 .NET aot orm, C# orm, VB.NET orm, Mysql orm, Postgresql orm, SqlServer orm, Oracle orm, Sqlite orm, Firebird orm, 达梦 orm, 人大金仓 orm, 神通 orm, 翰高 orm, 南大通用 orm, 虚谷 orm, 国产 orm, Clickhouse orm, QuestDB orm, MsAccess orm.
http://freesql.net
MIT License
4.09k stars 853 forks source link

MsAccess驱动或Odbc驱动处理DBNull时转为‘’而非null #1336

Closed RicardoHWu closed 1 year ago

RicardoHWu commented 1 year ago

使用freesql以字典格式查询数据时,库中空值将以DBNull而非null的格式存储到字典中,再将数据以InsertDict方式插入以MsAccess驱动或Odbc驱动连接的mdb数据库时,数据将转换为''而非null的格式拼接成sql,若相应的字段不为文本类型将导致报错 查询结果空值返回DBNull而不是null image MsAccess驱动或Odbc驱动使用字典插入mdb文件时DBNull将转为'' 而非null image

初步判断是OdbcAdo/MsAccessAdo的AddslashesProcessParam方法未对DBNull做判断处理导致,但不确定时候还有其他地方需要修改。

使用的FreeSql、DbContext、Respository、Provider.Odbc版本均为3.2.683-preview20221115,.net framework版本4.5

2881099 commented 1 year ago

字典设置 null 是什么结果

2881099 commented 1 year ago

刚才检查过。

所有驱动处理 AddslashesProcessParam、GetNoneParamaterSqlValue 都没有处理 DBNull.Value 的情况

建议用 c# null 表示该值。

RicardoHWu commented 1 year ago

刚才检查过。

所有驱动处理 AddslashesProcessParam、GetNoneParamaterSqlValue 都没有处理 DBNull.Value 的情况

建议用 c# null 表示该值。

请问有自定义这种转换处理的方法,或者设置以字典方式进行select时,结果的空值以null而不是DBNull存储么?就像我文中所说,目前的情况是使用FreeSql从postgresql中以字典类获取数据再直接写入mdb文件,若pg库中为null,则返回的字典中的值为DBNull,使得FreeSql拼接sql时会以‘’而不是null进行处理

2881099 commented 1 year ago

可以考虑写入前物理一遍判断 DBNull.Value,这样解决问题会比较简单一些。

2881099 commented 1 year ago

AOP 也可以统一处理 AuditValue:

fsql.Aop.AuditValue += (_, e) =>
{
    if (e.Object is Dictionary<string, object> dict)
    {
        foreach(var key in dict.Keys)
        {
            var val = dict[key];
            if (val == DBNull.Value) dict[key] = null;
        }
        e.ObjectAuditBreak = true;
    }
};
RicardoHWu commented 1 year ago

aop方法可行,dict的遍历代码需要修改,不然会报集合已被修改,可能无法操作枚举类型的错误

client.Aop.AuditValue += (s, e) => {
        if (e.Object is Dictionary<string, object> dict) {
                string[] keyArray = dict.Keys.ToArray();
                foreach (string key in keyArray) {
                         if (DBNull.Value.Equals(dict[key])) {
                                 dict[key] = null;
                         }
                }
                e.ObjectAuditBreak = true;
         }
};

但个人还是建议在框架代码中加入DBNull的处理,毕竟DBNull代表的是数据库中是空值,如果通过拼接sql而非sql预编译执行sql时,将DBNull转为''的处理明显是不合理的。同时框架本身也有对值做遍历处理,若再添加一层遍历也对效率有影响。

2881099 commented 1 year ago

3.2.683 处理该问题

RicardoHWu commented 1 year ago

更新v3.2.683-preview20221118版本后,如果字典中存在多个DBNull.Value,将会报如下错误

System.InvalidOperationException: 集合已修改;可能无法执行枚举操作。
   在 System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   在 System.Collections.Generic.Dictionary`2.KeyCollection.Enumerator.MoveNext()
   在 FreeSqlGlobalExtensions.LocalReplaceDictDBNullValue(Dictionary`2 dict) 位置 C:\Users\28810\Desktop\github\FreeSql\FreeSql\Extensions\FreeSqlGlobalExtensions.cs:行号 899
   在 FreeSqlGlobalExtensions.InsertDict(IFreeSql freesql, Dictionary`2 source) 位置 C:\Users\28810\Desktop\github\FreeSql\FreeSql\Extensions\FreeSqlGlobalExtensions.cs:行号 879

是否应该放在驱动的 AddslashesProcessParam、GetNoneParamaterSqlValue处理,而非处理前遍历字典进行处理更合理?

2881099 commented 1 year ago
    static void LocalReplaceDictDBNullValue(Dictionary<string, object> dict)
    {
        if (dict == null) return;
        var keys = dict.Keys;
        foreach (var key in keys)
        {
            var val = dict[key];
            if (val == DBNull.Value) dict[key] = null;
        }
    }

修改成这样了,今天会发布 3.2.683。

AddslashesProcessParam、GetNoneParamaterSqlValue 因为大多数是基于强实体类型,没有 DBNull.Value 的需求,目前针对 InsertDict/UpdateDict/DeleteDict 作处理成本比较低,也算合理。

目前这块是扩展方法的形式存在,不属于 interface,能在边缘上实现最好。

RicardoHWu commented 1 year ago

更新v3.2.683版本后,多个DBNull.Value时,依旧报改错误

System.InvalidOperationException: 集合已修改;可能无法执行枚举操作。
   在 System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   在 System.Collections.Generic.Dictionary`2.KeyCollection.Enumerator.MoveNext()
   在 FreeSqlGlobalExtensions.LocalReplaceDictDBNullValue(Dictionary`2 dict) 位置 C:\Users\28810\Desktop\github\FreeSql\FreeSql\Extensions\FreeSqlGlobalExtensions.cs:行号 900
   在 FreeSqlGlobalExtensions.InsertDict(IFreeSql freesql, IEnumerable`1 source) 位置 C:\Users\28810\Desktop\github\FreeSql\FreeSql\Extensions\FreeSqlGlobalExtensions.cs:行号 891

应该不能使用键集合的引用传递进行字典值的遍历修改吧?

RicardoHWu commented 1 year ago

并且由于LocalReplaceDictDBNullValue方法先于AuditValue方法调用,v3.2.683版本无法通过AOP手动处理DBNull的情况

2881099 commented 1 year ago

AuditValue 是683版本发布前的解决方法,不冲突

RicardoHWu commented 1 year ago

683版本的LocalReplaceDictDBNullValue方法依旧存在多个DBNull.Value时报集合已修改;可能无法执行枚举操作的问题,加上需要解决https://github.com/dotnetcore/FreeSql/issues/1332 的Odbc问题,由于683版本的odbc驱动依赖683版本的Freesql,目前解决方法是将整体版本回退到3.2.683-preview20221115,使用AuditValue处理

.net framework版本是4.7.2,不确定是否是因为.net框架不一致,所以作者的开发环境并没有报错

2881099 commented 1 year ago

请提供一个测试 demo,.net 4.7.2 的

RicardoHWu commented 1 year ago

这是测试demo,基于683版本的Freesql和32位access的odbc驱动,使用nunit作为单元测试框架 FreeSqlTest.zip

2881099 commented 1 year ago

谢谢反馈,已修复发布 3.2.684-preview20221130