EFCore执行自定义SQL时格式化错误:Input string was not in a correct format.

  记录一下EFCore执行自定义SQL报System.FormatException异常的问题,这个异常可能是“Input string was not in a correct format.”,也可能是其它格式化异常,比如:System.ArgumentException:“Format of the initialization string does not conform to specification starting at index 0.”,总之就是格式化错误。

  首先,说明一下,我这边使用的是.net6。

  其次,我们如果要使用EFCore执行自定义的SQL,对于查询语句,不同版本的支持不同,比如我这里使用的.net6,它就没有提供使用EFCore执行自定义SQL的方法,但是.net6后续版本就有了,对于非查询语句,比如增删改,我们可以使用DbContext.Database.ExecuteSqlRaw 的方法和它的异步方法DbContext.Database.ExecuteSqlRawAsync 来来实现,但是需要注意字符串的格式化问题。

  比如我执行这个SQL语句:

    var sql = "INSERT INTO test(name,age,remark)VALUES('zhangsan',20,'remark:{ age is 20}')";
    var result = await dbContext.Database.ExecuteSqlRawAsync(sql);

  执行后报错:System.FormatException:“Input string was not in a correct format.”

  通过查看源码,发现报错是在Microsoft.EntityFrameworkCore.Storage.Internal.RawSqlCommandBuilderBuild 方法:

    public class RawSqlCommandBuilder : IRawSqlCommandBuilder
    {
        public virtual RawSqlCommand Build(string sql, IEnumerable<object> parameters)
        {
            //省略部分代码...

            foreach (var parameter in parameters)
            {
                if (parameter is DbParameter dbParameter)
                {
                    if (string.IsNullOrEmpty(dbParameter.ParameterName))
                    {
                        dbParameter.ParameterName = _sqlGenerationHelper.GenerateParameterName(parameterNameGenerator.GenerateNext());
                    }

                    substitutions.Add(_sqlGenerationHelper.GenerateParameterName(dbParameter.ParameterName));
                    relationalCommandBuilder.AddRawParameter(dbParameter.ParameterName, dbParameter);
                }
                else
                {
                    var parameterName = parameterNameGenerator.GenerateNext();
                    var substitutedName = _sqlGenerationHelper.GenerateParameterName(parameterName);

                    substitutions.Add(substitutedName);
                    relationalCommandBuilder.AddParameter(parameterName, substitutedName);
                    parameterValues.Add(parameterName, parameter);
                }
            }

            // ReSharper disable once CoVariantArrayConversion
            sql = string.Format(sql, substitutions.ToArray());

            return new RawSqlCommand(
                relationalCommandBuilder.Append(sql).Build(),
                parameterValues);
        }
    }

  从上面这段代码可以看到,它是在sql = string.Format(sql, substitutions.ToArray()); 部分报错,这里做格式化,而我们的SQL语句中有花括号,自然就会报错了!

  怎么解决,有三个方法。

  1、使用string.Replace 替换掉花括号,因为string.Format会将花括号当做替换的参数,比如:{0}  {1} ,所以,我们要保留原有的花括号,可以对整个SQL语句执行以下替换,将SQL语句中的单花括号替换成两个花括号,两个花括号会在string.Format的时候转义成一个,比如:

    var sql = "INSERT INTO test(name,age,remark)VALUES('zhangsan',20,'remark:{ age is 20}')";
    sql = sql.Replace("{", "{{").Replace("}", "}}");
    var result = await dbContext.Database.ExecuteSqlRawAsync(sql);

  2、使用参数化来实现,比如:

    var connection = dbContext.Database.GetDbConnection();
    var factory = DbProviderFactories.GetFactory(connection);

    var nameParameter = factory.CreateParameter();
    nameParameter.ParameterName = "name";
    nameParameter.Value = "zhangsan";

    var ageParameter = factory.CreateParameter();
    ageParameter.ParameterName = "age";
    ageParameter.Value = 20;

    var remarkParameter = factory.CreateParameter();
    remarkParameter.ParameterName = "remark";
    remarkParameter.Value = "remark:{ age is 20 }";

    var sql = "INSERT INTO test(name,age,remark)VALUES(@name,@age,@remark)";
    var result = await dbContext.Database.ExecuteSqlRawAsync(sql);

   3、直接使用格式化,其实从上面的代码上看,本质上也是参数化,我们想想Microsoft.EntityFrameworkCore.Storage.Internal.RawSqlCommandBuilderBuild 方法为什么要做一个格式化?不就是给我们自定义格式化SQL使用的么,我们可以这么做:  

    var sql = "INSERT INTO test(name,age,remark)VALUES('{0}',{1},'{2}')";
    var result = await dbContext.Database.ExecuteSqlRawAsync(sql, "zhangsan", 20, "remark:{age is 20}");

  

  总结

  其实,这个就是花括号导致的问题,但是我们不能说保存到数据库的字符串里面不能存在花括号,比如我们输入的文本、数学表达式、代码、JSON格式化的数据等都有可能出现花括号需要保存到数据库中。总之,知道为什么报错就好了,其实开发的时候尽量避免,无论哪种解决方法,都可以根据自己的情况定,是在不行,可以自行使用ADO.NET的方式来实现。

   

posted @ 2024-08-04 14:21  没有星星的夏季  阅读(155)  评论(0编辑  收藏  举报