注意:这目前没有实现 SQL SERVER 所做的批量插入,如果要让多个使用相同的表名,您需要实现 AppendBulkInsertOperation
您可以创建一个覆盖Microsoft.EntityFrameworkCore.Update.IUpdateSqlGenerator 的实现,并在注册时对其执行ReplaceService。我已经成功覆盖了 Sql Server 中的插入操作。
在我的情况下,直接调用存储过程不是一种选择,因为我正在尝试将项目从 EF 6 升级到 EF Core,并进行更小的更改。
注册 DbContext:
services.AddDbContext<ExampleDbContext>((sp, options) =>
{
options
.UseSqlServer(connectionString)
.ReplaceService<IUpdateSqlGenerator, MapToProcedureUpdateSqlGenerator>();
});
MapToProcedureUpdateSqlGenerator
using Microsoft.EntityFrameworkCore.SqlServer.Update.Internal;
using Microsoft.EntityFrameworkCore.Update;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace EFCoreMapToStoredProcedures
{
// careful with this implementation as it gives warning
// This is an internal API that supports the Entity Framework Core infrastructure
// and not subject to the same compatibility standards as public APIs.
public class MapToProcedureUpdateSqlGenerator : SqlServerUpdateSqlGenerator
{
public MapToProcedureUpdateSqlGenerator(UpdateSqlGeneratorDependencies dependencies) : base(dependencies)
{
}
public override ResultSetMapping AppendInsertOperation(StringBuilder commandStringBuilder, ModificationCommand command, int commandPosition)
{
if (command == null) throw new ArgumentNullException(nameof(command));
if (commandStringBuilder == null) throw new ArgumentNullException(nameof(commandStringBuilder));
if (_tableInsertProcs.TryGetValue(command.TableName, out string procName))
{
var name = command.TableName;
var schema = command.Schema;
var operations = command.ColumnModifications;
var writeOperations = operations.Where(o => o.IsWrite).ToList();
AppendExecCommandHeader(commandStringBuilder, procName, schema, writeOperations);
if (operations.Any(_ => _.IsRead))
{
return ResultSetMapping.LastInResultSet;
}
return ResultSetMapping.NoResultSet;
}
else
{
return base.AppendInsertOperation(commandStringBuilder, command, commandPosition);
}
}
/// <summary>
/// Appends a SQL fragment for excuting a stored procedure
/// </summary>
/// <param name="commandStringBuilder"> The builder to which the SQL should be appended. </param>
/// <param name="name"> The name of the procedure. </param>
/// <param name="schema"> The table schema, or <see langword="null" /> to use the default schema. </param>
/// <param name="operations"> The operations representing the data to be inserted. </param>
protected virtual void AppendExecCommandHeader(
StringBuilder commandStringBuilder,
string name,
string schema,
IReadOnlyList<ColumnModification> operations)
{
if (commandStringBuilder == null) throw new ArgumentNullException(nameof(commandStringBuilder));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("required", nameof(name));
if (operations == null) throw new ArgumentNullException(nameof(operations));
commandStringBuilder.Append("EXEC ");
SqlGenerationHelper.DelimitIdentifier(commandStringBuilder, name, schema);
if (operations.Count > 0)
{
commandStringBuilder
.AppendJoin(
operations,
(this, name, schema),
(sb, o, p) =>
{
if (o.IsWrite)
{
var (g, n, s) = p;
if (!o.UseCurrentValueParameter)
{
throw new NotSupportedException("literals not supported");
}
else
{
g.SqlGenerationHelper.GenerateParameterNamePlaceholder(sb, o.ColumnName);
commandStringBuilder.Append(" = ");
g.SqlGenerationHelper.GenerateParameterNamePlaceholder(sb, o.ParameterName);
}
}
else
{
sb.Append("DEFAULT");
}
});
commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator);
}
}
// todo make configurable with dependencies
private readonly Dictionary<string, string> _tableInsertProcs = new Dictionary<string, string>()
{
["OrderItems"] = "InsertOrderItem"
};
}
}