.Net通过ORM处理Oracle中US7ASCII字符集乱码的问题

前言

  这只是一种折中的方法,如果有更好的解决方案希望大家踊跃分享。

思路

  .Net通过Oracle官方提供的数据库工具去连接数据库的时候就已经决定了你当前传输的字符集,于是导致各类常见的ORM工具都无法正常的解析US7ASCII的字符集,可是我们通过查询数据库发现US7ASCII可以返回所有数字、英文这就给我们提供了解决的可能性。我们通过RAWTOHEX、HEXTORAW、utl_raw.cast_to_varchar2三个数据库函数将中文字符进行转换,得到Hex值后在ORM中进行转码即可得到正常的中文。

  本文将使用FreeSql,SqlSugar两款国产ORM工具进行演示,其它的ORM只要思路一致应当都是可以实现的。

FreeSql

 使用FreeSql主要是依靠执行前的SQL编辑以及数据渲染时将查出来的Hex转回中文,因为是演示所以这里就直接用的默认字符集实际情况需要具体分析。

  示例代码:

演示实体
 [Table(Name = "ASCII_DEMO")]
public class AsciiDemo
{
    public string NAME { get; set; }
    public int AGE { get; set; }
    public DateTime BIRTHDAY { get; set; }
}
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
    .UseConnectionString(FreeSql.DataType.Oracle, @"user id=admin;password=admin123; data source=//127.0.0.1:1521/helowin;Pooling=true;Min Pool Size=1")
    .UseMonitorCommand(cmd => Console.WriteLine($"Sql:{cmd.CommandText}"))
    .UseAutoSyncStructure(false) 
    .Build();
//数据渲染时将HEX转换为正常的中文
fsql.Aop.AuditDataReader += (_, e) =>
{
    if (e.Value != DBNull.Value && e.DataReader.GetFieldType(e.Index).Name == typeof(string).Name)
    {
        e.Value = Encoding.Default.GetString(Convert.FromHexString(e.Value.ToString()));
    }
};
fsql.Aop.CommandBefore += (_, e) =>
{
    //判断是否为查询语句
    if (e.Command.CommandText.StartsWith("SELECT"))
    {
        //对查询语句进行修改,将数据库中的值转换成Hex
        //这里只是用于演示所以列名替换是写死的,实际情况需要进行反射然后进行替换
        e.Command.CommandText = e.Command.CommandText.Replace("a.\"NAME\"", $"RAWTOHEX(a.\"NAME\")");
    }
    //判断是否有参数传入
    if (e.Command.Parameters.Count > 0)
    {
        for (int i = 0; i < e.Command.Parameters.Count; i++)
        {
            if (e.Command.Parameters[i].DbType is not System.Data.DbType.String)
            {
                continue;
            }
            //将Hex转回Raw然后转Varchar2,这里是针对SQL进行的替换
            e.Command.CommandText = e.Command.CommandText.Replace(e.Command.Parameters[i].ParameterName, $"utl_raw.cast_to_varchar2(HEXTORAW({e.Command.Parameters[i].ParameterName}))");
            //把当前中文进行Hex转换,参数中的值替换
            e.Command.Parameters[i].Value = Convert.ToHexString(Encoding.Default.GetBytes($"{e.Command.Parameters[i].Value}"));
        }
    }
};
var result = fsql.Select<AsciiDemo>().ToList();
AsciiDemo asciiDemo = new AsciiDemo { NAME = "张三", AGE = 22, BIRTHDAY = DateTime.Now.AddYears(-22) };
int rows = fsql.Insert(asciiDemo).ExecuteAffrows();
var result1 = fsql.Select<AsciiDemo>().ToList();

SqlSugar

  使用SqlSugar进行则需要注意RAWTOHEX(NAME) AS NAME这里必须要明确列名,否则SqlParameterDbType这里则无法触发。

演示实体
 [SugarTable("ASCII_DEMO")]
public class AsciiDemo
{
    [SugarColumn( SqlParameterDbType = typeof(HexStringConvert))]
    public string NAME { get; set; }
    public int AGE { get; set; }
    public DateTime BIRTHDAY { get; set; }
}
public class HexStringConvert : ISugarDataConverter
{
    public SugarParameter ParameterConverter<T>(object columnValue, int columnIndex)
    {
        string name = "@HexStr" + columnIndex;
        Type underType = UtilMethods.GetUnderType(typeof(T));
        if (columnValue is not null)
        {
            columnValue = Convert.ToHexString(Encoding.UTF8.GetBytes($"{columnValue}"));
        }
        return new SugarParameter(name, columnValue, underType);
    }

    public T QueryConverter<T>(IDataRecord dataRecord, int dataRecordIndex)
    {
        object value = dataRecord.GetValue(dataRecordIndex);
        if (value == null)
        {
            return default;
        }
        value = Encoding.UTF8.GetString(Convert.FromHexString($"{value}"));
        return (T)value;
    }
}
SqlSugarScope Db = new SqlSugarScope(new ConnectionConfig()
{
    ConnectionString = "Data Source=localhost/helowin;User ID=admin;Password=admin123",
    DbType = SqlSugar.DbType.Oracle,
    IsAutoCloseConnection = true, 
}, db =>
{
    db.Aop.OnExecutingChangeSql = (sql, pars) =>
    {
        if (sql.StartsWith("SELECT"))
        {
            //这里同样需要根据实际情况进行反射,而非这种写死的模式
            sql = sql.Replace("\"NAME\"", "RAWTOHEX(\"NAME\") AS NAME");
        }
        if (pars.Length > 0)
        {
            //这里一个:,一个@分别是针对插入、删除所以二者有一个满足就行
            var parList = pars
                .Where(x => x.ParameterName.StartsWith(":HexStr") || x.ParameterName.StartsWith("@HexStr"))
                .Select(x => x.ParameterName)
                .ToList();
            //遍历替换语句中的变量,将其转换为US7ASCII
            foreach (var par in parList)
            {
                sql = sql.Replace(par, $"utl_raw.cast_to_varchar2(HEXTORAW({par}))");
            }
        }
        return new KeyValuePair<string, SugarParameter[]>(sql, pars);
    };
});

结语

  关于查询部分的反射大致思路就是获取所有实体类然后将其中的TableName,ColumnName,ColumnType获取出来,等使用的时候只用去对比是否存在反射对应信息即可。

posted @ 2023-07-09 20:03  秦时明  阅读(340)  评论(1)    收藏  举报