Dapper使用存储过程将多行插入到两个表中

时间:2018-04-03 18:10:51

标签: c# .net dapper asp.net-core-webapi

我在asp.net core myweb api项目中使用Dapper(https://github.com/StackExchange/Dapper)。我需要创建一个主记录并将一组行插入子表。

Ex. OrdersMaster table
    create an order id in this table

    OrderDetails table
    ordermasterid
    order items

我怎么能用Dapper做到这一点?如果可能,请分享一些代码段。

2 个答案:

答案 0 :(得分:2)

我将插入操作实现为事务存储过程,然后从.NET应用程序中调用它。

您可能需要一个表值类型来传递数据列表,如下所示:

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文本,如果发送proc的名称,则会出错。

如果您想一次输入多个订单,则需要使用表值参数和Dapper.Tvp包。

请参阅此问题Using Dapper.TVP TableValueParameter with other parameters以及来自Microsoft https://docs.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine的TVP文档。我不认为所有SQL供应商都支持TVP。

答案 1 :(得分:1)

使用其他答案中提到的存储过程是很好的解决方案。我的回答是在没有存储过程的情况下实现相同的。

您必须使用交易。这样,将提交或回滚所有更改。以下代码假定您使用Identity作为主键。有关我在以下代码中使用的@@IDENTITY的讨论,请参阅this问题。

虽然代码不完整,但我已经详细说明了解释步骤。

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