当我不知道记录是不是存在时,如何使用实体框架进行合并?
Posted
技术标签:
【中文标题】当我不知道记录是不是存在时,如何使用实体框架进行合并?【英文标题】:How can I use use Entity Framework to do a MERGE when I don't know if the record exists?当我不知道记录是否存在时,如何使用实体框架进行合并? 【发布时间】:2014-07-17 23:05:18 【问题描述】:在this SO answer关于Entity Framework和MERGE的代码中,示例如下:
public void SaveOrUpdate(MyEntity entity)
if (entity.Id == 0)
context.MyEntities.AddObject(entity);
else
context.MyEntities.Attach(entity);
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
这假设您已经知道要更新的实体是否存在;在这种情况下,您检查entity.Id
。但是,如果您不知道该项目是否存在怎么办?例如,在我的例子中,我将来自供应商的记录导入到我的数据库中,并且给定的记录可能已经导入,也可能尚未导入。如果记录存在,我想更新它,否则添加它。但是在这两种情况下都已经设置了供应商的 id。
除非我简单地询问数据库记录是否已经存在,否则我看不到任何方法,这违背了 MERGE 的全部目的。
【问题讨论】:
我认为简单的答案是 EF 无法执行 T-SQL MERGE 语句。添加和更新是唯一的选项,它总是需要数据库往返。您可以通过在开始导入之前获取所有 ID 来提高此往返效率。 【参考方案1】:我在这种情况下使用AddOrUpdate。但是,我相信它确实会首先查询数据库以决定发出插入或更新。
context.MyEntities.AddOrUpdate(e => e.Id, entity);
更新:
我浏览了我的调试日志文件。首先它运行:
SELECT TOP (2) ... WHERE 1 = [Extent1].[Id]
然后它运行:
INSERT [dbo].[TestTable](...) VALUES (...)
SELECT [Id]
FROM [dbo].[TestTable]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
或者:
UPDATE [dbo].[TestTable]
SET ...
WHERE ([Id] = @2)
更新 2: 这是一个使用 MERGE 的有趣扩展方法: https://gist.github.com/ondravondra/4001192
【讨论】:
你的意思是它发出两个查询,一个检查存在,一个做插入或更新? 我更新了我的回复以显示我看到 EF 生成的查询。是的,它在第一个查询中检查是否存在,然后决定插入或更新。 有趣。我认为如果它要发布两个声明,我宁愿自己处理这个问题。我将检查该扩展方法,因为这似乎可以解决问题。 (但为什么 EntityFramework 一开始就不能以这种方式工作,这似乎是一个明显的性能胜利?) 注意 AddOrUpdate 方法:thedatafarm.com/data-access/…【参考方案2】:如果您想要一个没有存储过程的原子数据库 UPSERT 命令,并且您不担心上下文被更新,那么值得一提的是,您还可以将嵌入式 MERGE
语句包装在 ExecuteSqlCommand
调用中:
public void SaveOrUpdate(MyEntity entity)
var sql = @"MERGE INTO MyEntity
USING
(
SELECT @id as Id
@myField AS MyField
) AS entity
ON MyEntity.Id = entity.Id
WHEN MATCHED THEN
UPDATE
SET Id = @id
MyField = @myField
WHEN NOT MATCHED THEN
INSERT (Id, MyField)
VALUES (@Id, @myField);"
object[] parameters =
new SqlParameter("@id", entity.Id),
new SqlParameter("@myField", entity.myField)
;
context.Database.ExecuteSqlCommand(sql, parameters);
这并不漂亮,因为它在 EF 对实体的抽象之外工作,但它允许您利用 MERGE
命令。
【讨论】:
这与我最终做的非常相似,所以我将其标记为答案。我还将它包装在一个通用的扩展方法中以便于重用。 有什么方法可以模拟这个 ExecuteSqlCommand,而不是模拟方法本身?【参考方案3】:AddOrUpdate 是一个很好的解决方案,但它不是可扩展。需要一次数据库往返来检查实体是否已经存在,并且需要一次往返来插入或更新实体。因此,如果您保存 1000 个实体,将执行 2000 次数据库往返。
免责声明:我是项目的所有者Entity Framework Extensions
此库允许您在 Entity Framework 中执行合并操作,同时显着提高性能。保存 1000 个实体只需要 1 次数据库往返。
// Easy to use
context.BulkMerge(customers)
// Easy to customize
context.BulkMerge(customers, operation =>
operation.ColumnPrimaryKeyExpression =
customer => customer.Code;
);
【讨论】:
这将是一个很好的解决方案。但它不是免费的。它不能用于生产环境,因为免费下载版本需要每个月更新.... @user007 - 如果您想要同样快速和免费,那么您将不得不编写类似存储过程的东西,它采用 TableValuedParameter 并包含正确处理和锁定的 MERGE 语句,然后有效地更改您的 DAL使用它而不是 EF 来保存更改。如果您不想为这项工作而烦恼并且需要/想要将 EF 保留为 DAL,那么这个相当便宜的库是一个很棒的解决方案,因为它可以节省您的时间。【参考方案4】:您可以更改 Entity Framework 生成的 INSERT 或 UPDATE sql 的唯一方法是 configure your model to use stored procedures。
然后您可以修改在 Up 迁移中生成的 sql 以创建插入和更新过程以使用您的 MERGE sql 而不是 INSERT 和 UPDATE
CREATE PROCEDURE [dbo].[Blog_Insert]
@Name nvarchar(max),
@Url nvarchar(max)
AS
BEGIN
-- Your Merge Sql goes here
--And you need to use MERGE OUTPUT to get the primary key
--instead of SCOPE_IDENTITY()
--SELECT SCOPE_IDENTITY() AS BlogId
END
【讨论】:
我认为插入存储过程不会起作用,因为它没有现有主键的参数。但是,是的,也许更新存储过程可以用于此。要尝试此操作,应将所有实体标记为Modified
,并在存储过程中做出插入或更新的决定。我希望通过更新存储过程插入记录不会导致任何意外的副作用。
@GertArnold 我怀疑插入不需要现有的主键,因为 MERGE 使用的是“供应商 ID”——这不是主键——否则你不需要合并你呢?
这个问题并不完全清楚。 Id 可能是主键。无论如何,我认为使用这些程序之一是一个可行的选择。以上是关于当我不知道记录是不是存在时,如何使用实体框架进行合并?的主要内容,如果未能解决你的问题,请参考以下文章