Eug*_*kal 8 c# sql-server entity-framework optimistic-concurrency sql-insert
假设一个具有多个并发生成器的系统,每个生成器都努力使用以下可通过其名称唯一标识的公共实体来持久保存一些对象图:
CREATE TABLE CommonEntityGroup(
Id INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
Name NVARCHAR(100) NOT NULL
);
GO
CREATE UNIQUE INDEX IX_CommonEntityGroup_Name
ON CommonEntityGroup(Name)
GO
CREATE TABLE CommonEntity(
Id INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
Name NVARCHAR(100) NOT NULL,
CommonEntityGroupId INT NOT NULL,
CONSTRAINT FK_CommonEntity_CommonEntityGroup FOREIGN KEY(CommonEntityGroupId)
REFERENCES CommonEntityGroup(Id)
);
GO
CREATE UNIQUE INDEX IX_CommonEntity_CommonEntityGroupId_Name
ON CommonEntity(CommonEntityGroupId, Name)
GO
Run Code Online (Sandbox Code Playgroud)
例如,生产者A保存了一些CommonEntityMeetings,而生产者B保存了CommonEntitySets.他们中的任何一个都必须坚持CommonEntity与他们的特定物品相关.
基本上,关键点是:
Name以及相关的公共实体,如果有的话)(例如CommonEntity,唯一的CommonEntity.Name+ CommonEntityGroup.Name)是唯一的.CommonEntities- 他们通常只是通过DTO与Names这些CommonEntities和相关信息(唯一).所以任何Common(Group)Entity必须找到/创建Name.因此,使用Entity Framework(数据库优先,尽管可能无关紧要)作为DAL和SQL Server作为存储,什么是一种有效且可靠的方法来确保所有这些生产者能够同时成功地保持其交叉对象图?
考虑到UNIQUE INDEX已经确保不会有重复CommonEntities(Name,GroupName对是唯一的)我可以看到以下解决方案:
在这种情况下,当SaveChanges被调用相关实体时,由于其他生产者在此刻之前创建了相同的实体,因此不存在任何索引违规.
要实现它,我会有一些
public class CommonEntityGroupRepository // sort of
{
public CommonEntityGroupRepository(EntitiesDbContext db) ...
// CommonEntityRepository will use this class/method internally to create parent CommonEntityGroup.
public CommonEntityGroup FindOrCreateAndSave(String groupName)
{
return
this.TryFind(groupName) ?? // db.FirstOrDefault(...)
this.CreateAndSave(groupName);
}
private CommonEntityGroup CreateAndSave(String groupName)
{
var group = this.Db.CommonEntityGroups.Create();
group.Name = groupName;
this.Db.CommonGroups.Add(group)
try
{
this.Db.SaveChanges();
return group;
}
catch (DbUpdateException dbExc)
{
// Check that it was Name Index violation (perhaps make indices IGNORE_DUP_KEY)
return this.Find(groupName); // TryFind that throws exception.
}
}
}
Run Code Online (Sandbox Code Playgroud)
使用这种方法会有多次调用SaveChanges,每个CommonEntity都有自己的Repository,尽管它似乎是最可靠的解决方案.
有点丑陋和低效(有10个CommonEntities我们可能要重试10次),但简单,或多或少可靠.
不确定是否有一种简单可靠的方法来替换或多或少复杂的对象图中的重复条目,尽管可以实现特定于案例和更通用的基于反射的解决方案.
仍然,像以前的解决方案一样,它可能需要多次重试.
怀疑在存储过程中处理它会更容易.这将是刚刚在数据库端实现的相同的乐观或悲观方法.
虽然它可以提供更好的性能(在这种情况下不是问题)并将插入逻辑放在一个共同的位置.
我可能会尝试第一种解决方案,但也许有更好的替代方案或一些潜在的陷阱.
表值参数
一种选择是使用table valued parameters数据库而不是单独调用。
使用表值参数的示例过程:
create type dbo.CommonEntity_udt as table (
CommonEntityGroupId int not null
, Name nvarchar(100) not null
, primary key (CommonEntityGroupId,Name)
);
go
create procedure dbo.CommonEntity_set (
@CommonEntity dbo.CommonEntity_udt readonly
) as
begin;
set nocount on;
set xact_abort on;
if exists (
select 1
from @CommonEntity as s
where not exists (
select 1
from dbo.CommonEntity as t
where s.Name = t.Name
and s.CommonEntityGroupId = t.CommonEntityGroupId
))
begin;
insert dbo.CommonEntity (Name)
select s.Name
from @CommonEntity as s
where not exists (
select 1
from dbo.CommonEntity as t with (updlock, holdlock)
where s.Name = t.Name
and s.CommonEntityGroupId = t.CommonEntityGroupId
);
end;
end;
go
Run Code Online (Sandbox Code Playgroud)
表值参数参考:
merge除非有令人信服的论据,否则我不推荐。这种情况只看插入,所以显得有点大材小用了。
merge具有表值参数的示例版本:
create procedure dbo.CommonEntity_merge (
@CommonEntity dbo.CommonEntity_udt readonly
) as
begin;
set nocount on;
set xact_abort on;
if exists (
select 1
from @CommonEntity as s
where not exists (
select 1
from dbo.CommonEntity as t
where s.Name = t.Name
and s.CommonEntityGroupId = t.CommonEntityGroupId
))
begin;
merge dbo.CommonEntity with (holdlock) as t
using (select CommonEntityGroupId, Name from @CommonEntity) as s
on (t.Name = s.Name
and s.CommonEntityGroupId = t.CommonEntityGroupId)
when not matched by target
then insert (CommonEntityGroupId, Name)
values (s.CommonEntityGroupId, s.Name);
end;
end;
go
Run Code Online (Sandbox Code Playgroud)
merge参考:
MERGE语句 - Aaron Bertrandignore_dup_key代码注释:
// 检查是否违反名称索引(也许使索引 IGNORE_DUP_KEY)
ignore_dup_key将serializable在幕后使用;非聚集索引可能会产生高昂的开销;即使索引是聚集的,根据重复项的数量,也可能会产生巨大的成本。
这可以在存储过程中使用Sam Saffron 的 upsert(更新/插入)模式或此处显示的模式之一来处理:不同错误处理技术的性能影响 - Aaron Bertrand。
| 归档时间: |
|
| 查看次数: |
656 次 |
| 最近记录: |