Dapper如何插入具有相同外键的多行

huangapple go评论64阅读模式
英文:

Dapper how to insert multiple rows with same foreign key

问题

我有一个子表,它引用了一个具有一对多关系的父表。
主键 -> 多个外键。原因是外键表是与父表上的ID相关的对象列表。

如何使用Dapper插入具有相同外键的多行?

assessmentModel.locationInformationModels = new List<LocationInformationModel>();

string sqlStatement = @"
    INSERT INTO LocationInformation
    ([LocationInformationId]                          
    ,[Address1]
    ,[Address2]
    ,[City]
    ,[State]
    ,[Zip])
    VALUES (
    @LocationInformationId, @Address1, @Address2, @City, @State, @Zip)";

using (var connection = new SqlConnection(_connectionString))
{
    await connection.OpenAsync();
    using (var ts = connection.BeginTransaction())
    {
        var retVal = await connection.ExecuteAsync(sqlStatement, assessmentModel.locationInformationModels, transaction: ts);
        ts.Commit();
        return retVal;
    }        
}

请注意,这段代码似乎尝试将 assessmentModel.locationInformationModels 中的多个对象插入到 "LocationInformation" 表中,每个对象都具有相同的外键值。

英文:

I have a child table that rerferences a parent table with a 1 to many relationship.
PK -> multiple foreign keys. The reason is the FK table are a list of objects related to an ID on the parent table.

How can I use dapper to insert multiple rows with the same FK?

assessmentModel.locationInformationModels = new List&lt;LocationInformationModel&gt;();

string sqlStatement = @&quot;INSERT INTO LocationInformation
                                ([LocationInformationId]                          
                                ,[Address1]
                                ,[Address2]
                                ,[City]
                                ,[State]
                                ,[Zip])
                                VALUES(
                                @LocationInformationId,@Address1,@Address2,@City,@State,@Zip)&quot;;            
         
using (var connection = new SqlConnection(_connectionString))
            {
                await connection.OpenAsync();
                using (var ts = connection.BeginTransaction())
                {
                    var retVal = await connection.ExecuteAsync(sqlStatement, assessmentModel.locationInformationModels, transaction:ts);
                    ts.Commit();
                    return retVal;
                }        
            }

答案1

得分: 3

问题有点不太清楚。代码显示了插入一系列项的操作,但没有看到任何类似外键的东西。无论如何,Dapper是一个微型ORM,它不处理实体之间的关系,也不自动更新键。它不会更改提供的SQL语句以处理多个对象。

我怀疑真正的问题是如何从数据库返回生成的值,例如IDENTITY键,尽管代码显示LocationInformationId由应用程序提供。为了做到这一点,查询将不得不修改以返回任何生成的值,例如使用OUTPUT子句。

返回生成的值

例如,这将返回自动生成的ID:

var sql = @"""
    INSERT INTO LocationInformation
        ([Address1]
        ,[Address2]
        ,[City]
        ,[State]
        ,[Zip])
    OUTPUT inserted.LocationInformationId
    VALUES(@Address1,@Address2,@City,@State,@Zip)
    """;

问题的代码已经使用ExecuteAsync(sql, IEnumerable<T>, ...)来插入多个项。这将为每个项执行一次查询,但不会返回任何结果。没有QueryAsync的等效方法,因此应用程序将不得不显式执行INSERT:

foreach (var item in items)
{
    var newId = await connection.QueryAsync(sql, item);
    item.Id = newId;
}

行构造器

可以使用行构造器一次性插入多行,但这需要动态生成SQL查询:

INSERT INTO LocationInformation
    ([Address1]
    ,[Address2]
    ,[City]
    ,[State]
    ,[Zip])
VALUES
OUTPUT inserted.LocationInformationId
(@Address1_01, @Address2_01, @City_01, @State_01, @Zip_01),
(@Address1_02, @Address2_02, @City_02, @State_02, @Zip_02),
(@Address1_03, @Address2_03, @City_03, @State_03, @Zip_03),
...

所有项目的值也必须由应用程序转换为参数列表。这并不是很方便。

表值参数

另一种选择是使用表值参数类型,一次性插入传递为DataTable的多个值。这需要在数据库中创建一个表值参数类型,并将数据转换为DataTable,例如:

CREATE TYPE dbo.LocationTableType AS TABLE  
    ( Address1 nvarchar(50), ...  ) 

查询应更改为使用表参数的INSERT ... SELECT

var sql = @"""
    INSERT INTO LocationInformation
        ([Address1]
        ,[Address2]
        ,[City]
        ,[State]
        ,[Zip])
    OUTPUT inserted.LocationInformationId
    SELECT Address1, Address2, City, State, Zip 
    FROM @table
    """;

MoreLinq的ToDataTable可用于将输入项转换为DataTable。然后,QueryAsync调用将执行INSERT并返回输出列表:

var table = items.ToDataTable();
var newIds = await connection.QueryAsync(sql, table);
英文:

The question is a bit unclear. The code shows inserting a list of items without anything that looks like a foreign key. In any case, Dapper is a microORM, it doesn't handle relations between entities, or auto-update keys. It won't change the supplied SQL statement to handle multiple objects either.

I suspect the real question is how to return a generated value from the database, eg an IDENTITY key, although the code shows that LocationInformationId is supplied by the application. To do this, the query will have to be modified to return any generated value, eg with the OUTPUT clause.

Returning generated values

For example, this will return the auto-generated ID :

var sql = &quot;&quot;&quot;
    INSERT INTO LocationInformation
        ([Address1]
        ,[Address2]
        ,[City]
        ,[State]
        ,[Zip])
    OUTPUT inserted.LocationInformationId
    VALUES(@Address1,@Address2,@City,@State,@Zip)
    &quot;&quot;&quot;;

The question's code already uses ExecuteAsync(sql, IEnumerable&lt;T&gt;,...) to insert multiple items. That executes the query once for every item but doesn't return any results. There's no equivalent for QueryAsync, so the application will have to execute the INSERTs explicitly :

foreach(var item in items)
{
    var newId=await connection.QueryAsync(sql,item);
    item.Id=newId;
}

Row constructors

It's possible to insert multiple rows with a single INSERT using row constructors, but that requires generating the SQL query dynamically :

    INSERT INTO LocationInformation
        ([Address1]
        ,[Address2]
        ,[City]
        ,[State]
        ,[Zip])
    VALUES
    OUTPUT inserted.LocationInformationId
    (@Address1_01,@Address2_01,@City_01,@State_01,@Zip_01),
    (@Address1_02,@Address2_02,@City_02,@State_02,@Zip_02),
    (@Address1_03,@Address2_03,@City_03,@State_03,@Zip_03),
    ...

All item values will have to be flattened by the application to a list of parameters too. Not very helpful.

Table valued parameters

Another option is to use a table-valued parameter type parameter to insert multiple values passed as a DataTable at once. This requires creating a table valued parameter type in the database and converting the data into a DataTable, eg :

CREATE TYPE dbo.LocationTableType AS TABLE  
    ( Address1 nvarchar(50), ...  ) 

The query should change to an INSERT ... SELECT using the table parameter :

var sql = &quot;&quot;&quot;
    INSERT INTO LocationInformation
        ([Address1]
        ,[Address2]
        ,[City]
        ,[State]
        ,[Zip])
    OUTPUT inserted.LocationInformationId
    SELECT Address1,Address2,City,State,Zip 
    FROM @table
    &quot;&quot;&quot;;

MoreLinq's ToDataTable can be used to convert the input items to a DataTable. After that, a QueryAsync call will execute the INSERT and return the list of outputs :

var table=items.ToDataTable();
var newIds=await connection.QueryAsync(sql,table);

答案2

得分: 0

如果我理解正确,您想要批量在表中插入数据。纯粹的Dapper没有内置的批量插入功能,因此您的选择要么是使用Dapper-Plus,要么是使用SqlBulkCopy来执行所有批量操作。

使用SqlBulkCopy,您可以设置动态的列映射,通过反射来避免需要显式定义所有列。

英文:

If I understand you correctly you want to do inserts on the table in batches. Pure Dapper does not have any built in way to do bulk inserts, so your options would be either using Dapper-Plus or SqlBulkCopy for all bulk actions you want to perform.

With SqlBulkCopy you could set up a dynamic columnmapping using reflection to avoid having to define all columns explicitly.

huangapple
  • 本文由 发表于 2023年2月19日 10:23:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/75497622.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定