ExecuteSqlInterpolatedAsync EF Core 在数据库中插入失败

Failed insert in db by ExecuteSqlInterpolatedAsync EF Core

我在通过 ef 核心方法插入 psql 数据库时遇到问题 ExecuteSqlInterpolatedAsync/ExecuteSqlInterpolated。 表方案:

      Column  |  Type   | Collation | Nullable |                 Default                  
----------+---------+-----------+----------+------------------------------------------
 Id       | integer |           | not null | nextval('"LookupRows_Id_seq"'::regclass)
 Index    | integer |           | not null | 
 RowData  | text    |           | not null | 
 LookupId | integer |           | not null | 

插入方法:

FormattableString str = $"INSERT into \"LookupRows\" (\"Index\", \"RowData\", \"LookupId\") VALUES {q.First()};";
await _context.Database.ExecuteSqlRawAsync(str.ToString(),cancellationToken);
await _context.Database.ExecuteSqlInterpolatedAsync($"INSERT into \"LookupRows\" (\"Index\", \"RowData\", \"LookupId\") VALUES {q.First()};",cancellationToken);

q.First() = (0, '{{"0":["0","Bucharest","0"]}}', 115)

ExecuteSqlRawAsync 工作正常。条目成功插入。 但是 ExecuteSqlInterpolatedAsync 总是 return 一个错误: MessageText: syntax error at or near "" 我运行出主意了。我做错了什么?

两个选项都不行。

在第一种情况下,代码使用普通的旧字符串操作来构造一个 SQL 查询,允许 SQL 注入和转换错误。如果那个 q.First() 是一个值为 ('','',''); drop table Users;-- 的字符串,你最终会得到一个 dropped table.

在第二种情况下,语法完全无效。不是通过参数提供预期的 3 个值,而是使用单个值。

ExecuteSqlRawAsync 的正确 语法是:

var sql=@"INSERT into LookupRows (Index, RowData, LookupId) 
VALUES (@index,@row,@lookup);"

var paramObject=new {index=1,row="abc",lookup=100};
await _context.Database.ExecuteSqlRawAsync(sql, paramObject, cancellationToken);

参数对象的属性必须与参数名称匹配。

ExecuteSqlInterpolatedAsync 的正确语法是:

await _context.Database.ExecuteSqlInterpolatedAsync(
    @$"INSERT into LookupRows (Index, RowData, LookupId) 
VALUES ({paramObject.index},{paramObject.row},{paramObject.lookup});", 
cancellationToken);

FormattableString sql=@$"INSERT into LookupRows (Index, RowData, LookupId) 
VALUES ({paramObject.index},{paramObject.row},{paramOject.lookup});";

await _context.Database.ExecuteSqlInterpolatedAsync(sql, cancellationToken);

ExecuteSqlInterpolatedAsync 将检查 FormattableString 并使用字符串占位符作为位置参数并将它们的值作为参数值来生成新的参数化查询。相当于:

var sql=@"INSERT into LookupRows (Index, RowData, LookupId) 
VALUES (?,?,?);"

await _context.Database.ExecuteSqlRawAsync(sql, 1,"abc",100);

使用 ExecuteSqlInterpolatedAsync 相当冒险,因为 方式 太容易忘记明确指定 FormattableString 并以 :

结尾
var sql=@$"INSERT into LookupRows (Index, RowData, LookupId) 
VALUES ({paramObject.index},{paramObject.row},{paramObject.lookup});";

这只是一个由数据构造的字符串,再次容易受到 SQL 注入

正在插入 10 万个项目

看起来实际问题是插入 100K 行。 ORM 是这项工作的错误工具。在这种情况下,有 10 万行没有业务逻辑,而不是单个对象图。

执行 100K 条 INSERT 语句将花费很长时间,并且会淹没数据库的事务日志。解决方案是使用 SqlBulkCopy 通过批量操作和最少的日志记录来插入行。

SqlBulkCopy.WriteToServer expects a DataTable or DataReader. To use an IEnumerable<T> we can use FastMember's ObjectReader 包装它:


IEnumerable<SomeType> data = ... 


using(var bcp = new SqlBulkCopy(connection)) ;
using(var reader = ObjectReader.Create(data, "Id", "Name", "Description")) 
{ 
  bcp.DestinationTableName = "SomeTable"; 
  bcp.WriteToServer(reader); 
}

正在导入 CSV

要导入 CSV 文件,可以使用 CsvHelper CsvDataReader :

using (var reader = new StreamReader("path\to\file.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
    // Do any configuration to `CsvReader` before creating CsvDataReader.
    using (var dr = new CsvDataReader(csv))
    using(var bcp = new SqlBulkCopy(connection))
    {       
           
      bcp.DestinationTableName = "SomeTable"; 
      bcp.WriteToServer(dr); 
    }
}