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
例如,生产者A保存一些
CommonEntityMeeting
,而生产者B保存CommonEntitySet
。他们中的任何一个都必须持久化与其特定项目相关的CommonEntity
。基本上,关键点是:
- 有独立的生产者。 - 他们并行操作。 - 理论上(虽然这可能会改变,目前还不完全正确),它们将通过相同的Web服务(ASP.Net Web API)进行操作,只是具有各自的端点/“资源”。因此,理想的解决方案不应该依赖于此。 - 他们努力保留包含可能尚不存在的CommonEntity/CommonEntityGroup对象的不同对象图。 - CommonEntity/CommonEntityGroup在创建后就不可变且永远不会被修改或删除。 - 根据它们的某些属性(
Name
和相关的通用实体,如果有的话(例如,CommonEntity
按CommonEntity.Name
+CommonEntityGroup.Name
唯一)),CommonEntity/CommonEntityGroup是唯一的。
- 生产者不知道/不关心那些CommonEntities
的ID - 他们通常只传递带有那些CommonEntities
的Names
(唯一)和相关信息的DTO。因此,任何Common(Group)Entity
都必须通过Name
找到/创建。
- 生产者有一定的可能性同时尝试创建相同的CommonEntity/CommonEntityGroup。
- 尽管这样的CommonEntity/CommonEntityGroup对象已经存在于数据库中,但更有可能发生。因此,在将Entity Framework(先数据库,虽然这可能并不重要)作为DAL和SQL Server作为存储时,如何有效可靠地确保所有这些生产者会同时成功持久化它们交叉的对象图?
考虑到
UNIQUE INDEX
已经确保不会有重复的CommonEntities
(Name,GroupName对是唯一的),我可以看到以下解决方案:- 确保在构建其他对象的图形之前找到/创建每个CommonEntity/CommonGroupEntity + SaveChanged()。 在这种情况下,当为相关实体调用
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.
}
}
}
采用此方法将对SaveChanges进行多次调用,并且每个CommonEntity都将拥有自己的一种Repository,尽管这似乎是最可靠的解决方案。 2.只需创建整个图并在索引违规时从头开始重建 有点丑陋和低效(使用10个CommonEntities可能需要重试10次),但简单而相对可靠。 3.只需创建整个图并在索引违规时替换重复项 不确定是否有一种简单可靠的方法来替换更为复杂的对象图中的重复项,虽然可以实现特定于情况和更通用的基于反射的解决方案。
仍然像前一个解决方案一样可能需要多次重试。 4.尝试将此逻辑移到数据库中(SP) 怀疑在存储过程内部处理更容易。它将是在数据库端实现的乐观或悲观方法。
虽然它可能提供更好的性能(在这种情况下不是问题),并将插入逻辑放入一个共同的位置。 5.在存储过程中使用SERIALIZABLE隔离级别 / TABLOCKX + SERIALIZABLE表提示 - 这应该绝对有效,但我更喜欢不要将表完全锁定超过实际必要的时间,因为实际竞争很少。正如标题中已经提到的那样,我想找到一些乐观并发方法。
我可能会尝试第一种解决方案,但也许有更好的选择或潜在的风险。
CommonEntity.id
或CommonEntity.Name
?当您尝试保存对象图时,您是否知道CommonEntity
和CommonEntityGroup
的Name
或ID
?换句话说,当您保存对象图时,您是否需要先通过其Name
查找实体的ID
? - Vladimir Baranov