Dapper 使用存储过程将多行插入到两个表中
Dapper to insert multiple rows into two tables using stored procedure
我在 asp.net 核心 myweb api 项目中使用 Dapper (https://github.com/StackExchange/Dapper)。我需要创建一个主记录并将一组行插入子项 table。
Ex. OrdersMaster table
create an order id in this table
OrderDetails table
ordermasterid
order items
如何使用 Dapper 执行此操作?如果可能,请分享一些代码片段。
我会将插入操作实现为事务性存储过程,然后从您的 .NET 应用程序中调用它。
您可能需要 table-valued 类型来传递数据列表,如下所示:
CREATE TYPE List_Of_Items AS TABLE (
ItemID INT NOT NULL,
Quantity INT NOT NULL
)
程序可能如下所示
CREATE PROC Insert_Order_With_Details (
@CustomerId INT,
@Items List_Of_Items
) AS
BEGIN
BEGIN TRANSACTION
INSERT INTO OrdersMaster (CustomerId) VALUES @CustomerId
DECLARE @OrderID INT
SET @OrderID = SCOPE_IDENTITY() --last assigned id
INSERT INTO OrderDetails (OrderId, CustomerId, ItemId, Quantity)
SELECT @OrderID, @CustomerID, ItemID, Quantity
FROM @Items
COMMIT
END
然后在 C# 中,我建议创建用于创建 TVP 的方法。它并不像您想要的那么简单。这需要 using Microsoft.SqlServer.Server
和 using Dapper.Tvp
.
//This is a shell to create any kind of TVP
private static void AddTableCore<T>(
this DynamicParametersTvp dp,
string tvpTypeName,
Func<T, SqlDataRecord> valueProjection,
IEnumerable<T> values,
string parameterTableName)
{
var tvp = values
.Select(valueProjection)
.ToList();
//If you pass a TVP with 0 rows to SQL server it will error, you must pass null instead.
if (!tvp.Any()) tvp = null;
dp.Add(new TableValueParameter(parameterTableName, tvpTypeName, tvp));
}
//This will create your specific Items TVP
public static void AddItemsTable(this DynamicParametersTvp dp, IEnumerable<Item> items, string parameterTableName = "Items")
{
var columns = new[]
{
new SqlMetaData("ItemID", SqlDbType.Int)
new SqlMetaData("Quantity", SqlDbType.Int)
};
var projection = new Func<Item, SqlDataRecord>(item =>
{
var record = new SqlDataRecord(columns);
record.SetInt32(0, item.Id);
record.SetInt32(1, item.Quantity);
return record;
});
AddTableCore(dp, "Items", projection, items, parameterTableName);
}
然后你需要查询的地方你可以这样做:
using (var cn = new SqlConnection(myConnectionString))
{
var p = new DynampicParametersTvp(new {
CustomerId = myCustomerId
});
p.AddItemsTable(items);
cn.Execute("Insert_Order_With_Details", p, commandType: CommandType.StoredProcedure);
}
commandType
论点非常重要。它默认为纯 SQL 文本,如果您发送过程名称将会出错。
如果您想一次下多个订单,您需要使用 table-valued 参数和 Dapper.Tvp 包。
看到这个 SO 问题 Using Dapper.TVP TableValueParameter with other parameters as well as this documentation on TVP's from Microsoft https://docs.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine。我认为并非所有 SQL 供应商都支持 TVP。
使用其他答案中提到的存储过程是很好的解决方案。我的答案在没有存储过程的情况下实现了相同。
你必须使用交易。这样,将提交或回滚所有更改。下面的代码假设您使用 Identity
作为主键。请参考 问题以讨论我在下面的代码中使用的 @@IDENTITY
。
虽然代码不完整,但我已经放了详细的注释来解释步骤。
using (var connection = new SqlCeConnection("connection_string"))
{
connection.Open();
//Begin the transaction
using (var transaction = connection.BeginTransaction())
{
//Create and fill-up master table data
var paramMaster = new DynamicParameters();
paramMaster.Add("@XXX", ...);
....
//Insert record in master table. Pass transaction parameter to Dapper.
var affectedRows = connection.Execute("insert into OrdersMaster....", paramMaster, transaction: transaction);
//Get the Id newly created for master table record.
//If this is not an Identity, use different method here
newId = Convert.ToInt64(connection.ExecuteScalar<object>("SELECT @@IDENTITY", null, transaction: transaction));
//Create and fill-up detail table data
//Use suitable loop as you want to insert multiple records.
//for(......)
foreach(OrderItem item in orderItems)
{
var paramDetails = new DynamicParameters();
paramDetails.Add("@OrderMasterId", newId);
paramDetails.Add("@YYY", ...);
....
//Insert record in detail table. Pass transaction parameter to Dapper.
var affectedRows = connection.Execute("insert into OrderDetails....", paramDetails, transaction: transaction);
}
//Commit transaction
transaction.Commit();
}
}
我在 asp.net 核心 myweb api 项目中使用 Dapper (https://github.com/StackExchange/Dapper)。我需要创建一个主记录并将一组行插入子项 table。
Ex. OrdersMaster table
create an order id in this table
OrderDetails table
ordermasterid
order items
如何使用 Dapper 执行此操作?如果可能,请分享一些代码片段。
我会将插入操作实现为事务性存储过程,然后从您的 .NET 应用程序中调用它。
您可能需要 table-valued 类型来传递数据列表,如下所示:
CREATE TYPE List_Of_Items AS TABLE (
ItemID INT NOT NULL,
Quantity INT NOT NULL
)
程序可能如下所示
CREATE PROC Insert_Order_With_Details (
@CustomerId INT,
@Items List_Of_Items
) AS
BEGIN
BEGIN TRANSACTION
INSERT INTO OrdersMaster (CustomerId) VALUES @CustomerId
DECLARE @OrderID INT
SET @OrderID = SCOPE_IDENTITY() --last assigned id
INSERT INTO OrderDetails (OrderId, CustomerId, ItemId, Quantity)
SELECT @OrderID, @CustomerID, ItemID, Quantity
FROM @Items
COMMIT
END
然后在 C# 中,我建议创建用于创建 TVP 的方法。它并不像您想要的那么简单。这需要 using Microsoft.SqlServer.Server
和 using Dapper.Tvp
.
//This is a shell to create any kind of TVP
private static void AddTableCore<T>(
this DynamicParametersTvp dp,
string tvpTypeName,
Func<T, SqlDataRecord> valueProjection,
IEnumerable<T> values,
string parameterTableName)
{
var tvp = values
.Select(valueProjection)
.ToList();
//If you pass a TVP with 0 rows to SQL server it will error, you must pass null instead.
if (!tvp.Any()) tvp = null;
dp.Add(new TableValueParameter(parameterTableName, tvpTypeName, tvp));
}
//This will create your specific Items TVP
public static void AddItemsTable(this DynamicParametersTvp dp, IEnumerable<Item> items, string parameterTableName = "Items")
{
var columns = new[]
{
new SqlMetaData("ItemID", SqlDbType.Int)
new SqlMetaData("Quantity", SqlDbType.Int)
};
var projection = new Func<Item, SqlDataRecord>(item =>
{
var record = new SqlDataRecord(columns);
record.SetInt32(0, item.Id);
record.SetInt32(1, item.Quantity);
return record;
});
AddTableCore(dp, "Items", projection, items, parameterTableName);
}
然后你需要查询的地方你可以这样做:
using (var cn = new SqlConnection(myConnectionString))
{
var p = new DynampicParametersTvp(new {
CustomerId = myCustomerId
});
p.AddItemsTable(items);
cn.Execute("Insert_Order_With_Details", p, commandType: CommandType.StoredProcedure);
}
commandType
论点非常重要。它默认为纯 SQL 文本,如果您发送过程名称将会出错。
如果您想一次下多个订单,您需要使用 table-valued 参数和 Dapper.Tvp 包。
看到这个 SO 问题 Using Dapper.TVP TableValueParameter with other parameters as well as this documentation on TVP's from Microsoft https://docs.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine。我认为并非所有 SQL 供应商都支持 TVP。
使用其他答案中提到的存储过程是很好的解决方案。我的答案在没有存储过程的情况下实现了相同。
你必须使用交易。这样,将提交或回滚所有更改。下面的代码假设您使用 Identity
作为主键。请参考 @@IDENTITY
。
虽然代码不完整,但我已经放了详细的注释来解释步骤。
using (var connection = new SqlCeConnection("connection_string"))
{
connection.Open();
//Begin the transaction
using (var transaction = connection.BeginTransaction())
{
//Create and fill-up master table data
var paramMaster = new DynamicParameters();
paramMaster.Add("@XXX", ...);
....
//Insert record in master table. Pass transaction parameter to Dapper.
var affectedRows = connection.Execute("insert into OrdersMaster....", paramMaster, transaction: transaction);
//Get the Id newly created for master table record.
//If this is not an Identity, use different method here
newId = Convert.ToInt64(connection.ExecuteScalar<object>("SELECT @@IDENTITY", null, transaction: transaction));
//Create and fill-up detail table data
//Use suitable loop as you want to insert multiple records.
//for(......)
foreach(OrderItem item in orderItems)
{
var paramDetails = new DynamicParameters();
paramDetails.Add("@OrderMasterId", newId);
paramDetails.Add("@YYY", ...);
....
//Insert record in detail table. Pass transaction parameter to Dapper.
var affectedRows = connection.Execute("insert into OrderDetails....", paramDetails, transaction: transaction);
}
//Commit transaction
transaction.Commit();
}
}