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.Serverusing 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();
    }
}