我有一个名为People的表,具有以下架构:
Id INT NOT NULL IDENTITY(1, 1)
FirstName NVARCHAR(64) NOT NULL
LastName NVARCHAR964) NOT NULL
我正在使用像这样的查询在一个查询中执行插入和更新:
MERGE INTO People AS TARGET
USING ( VALUES
(@id0, @firstName0, @lastname0),
(@id1, @firstName1, @lastname1)
...
) AS SOURCE ([Id],[FirstName],[LastName])
ON TARGET.[Id] = SOURCE.[Id]
WHEN MATCHED BY TARGET THEN
UPDATE SET
[FirstName] = SOURCE.[FirstName],
[LastName] = SOURCE.[LastName]
WHEN NOT MATCHED BY TARGET THEN
INSERT ([FirstName],[LastName])
VALUES ([FirstName],[LastName])
WHEN NOT MATCHED BY SOURCE THEN
DELETE
OUTPUT $action, INSERTED.*;
我的应用程序的结构使得客户端可以回调服务器以加载应用程序的现有状态。然后,客户端在本地创建/修改/删除实体,并将这些更改一堆推送到服务器。
以下是我的“ SaveEntities”代码当前的示例:
public void SavePeople(IEnumerable<Person> people)
{
// Returns the query I mentioned above
var query = GetMergeStatement(people);
using(var command = new SqlCommand(query))
{
using(var reader = command.ExecuteReader())
{
while(reader.Read())
{
// how do I tie these records back to
// the objects in the people collection?
}
}
}
}
我可以使用$action
列中的值来过滤掉仅插入的记录。 INSERTED.*
返回TARGET中插入记录的所有列。问题是我无法将这些结果明确地链接回传递给此方法的对象的集合。
我唯一想到的解决方案是在表中添加一个可写的GUID列,并允许MERGE语句指定该值,这样我就可以使用该链接链接到代码中的这些对象并从那里分配ID值,但是看来这违背了创建自动标识列的目的,并且令人费解。
我真的很好奇这是如何工作的,因为我知道Entity Framework可以缓解此问题(要明确,我相信如果我使用的是纯INSERT语句而不是MERGE,那我将遇到同样的问题)。在EF中,我可以将对象添加到模型中并调用Entity.SaveChanges()
,并使用magic自动更新实体的ID属性。我想这就是我想要了解更多的魔术。
此外,我知道我可以构造存储以一次插入一个记录并适当地级联更改(通过对每个插入返回SCOPE_IDENTITY
),但这会非常低效。
答案 0 :(得分:1)
我喜欢MERGE
语句的一件事是源数据在OUTPUT
子句中。
OUTPUT $action, SOURCE.Id, INSERTED.Id;
在插入时,这将为您提供三列:第一列为“ INSERT”,第二列为@id0
和@id1
的值,以及匹配的新插入的Id
值在第三。
在C#代码中,像往常一样读取行。
while (reader.Read())
{
string action = reader.GetString(0);
if (action == "INSERT")
{
int oldId = reader.GetInt32(1);
int newId = reader.GetInt32(2);
// Now do what you want with them.
}
}
您也可以检查“ DELETE”和“ UPDATE”,但是请记住,序数2将在“ DELETE”上为NULL
,因此您需要确保在调用{{1}之前检查一下}。
我已将其与表变量(reader.GetInt32
)结合使用,复制了层次4和5的表(均带有标识列)。