问题描述
我有一个父实体,我需要做一个并发检查(如下注释) 我有一堆客户端进程,它们从这个父实体中访问只读值,主要是更新它的子实体. 约束 客户端不应干扰彼此的工作(例如,更新子记录不应在父实体上引发并发异常). 我有一个服务器进程,它会 更新这个父实体,在这种情况下如果 父实体 已更改,客户端进程需要抛出. 注意:客户端的并发检查是牺牲的,服务器的工作流程是关键任务[时间戳]公共字节 [] RowVersion { 获取;放;}
问题
我需要(从客户端进程)检查父实体是否已更改而不更新父实体的行版本.
在EF中对父实体进行并发检查很容易:
//更新行版本的原始值_db.Entry(dbManifest).Property(b => b.RowVersion).OriginalValue = dbManifest.RowVersion;//客户端最初读取的行版本//将行版本标记为已修改_db.Entry(dbManifest).Property(x => x.RowVersion).IsModified = true;
IsModified = true
是交易破坏者,因为它强制更改行版本.或者,在上下文中说,来自客户端进程的此检查将导致 父实体 中的行版本更改,这会不必要地干扰其他 客户端进程的 工作流.p>
解决方法:我可能会将客户端进程中的 SaveChanges
包装在事务中,然后读取父实体的行版本,反过来,如果行版本发生变化,则回滚.
总结
实体框架是否有一种开箱即用的方式,我可以在其中SaveChanges
(在客户端进程中 用于 子实体)还检查 父实体的行版本是否已更改(不更新 父实体的行版本em>).
有一个非常简单的解决方案,out-of-2-boxes",但它需要两个修改愿意,做出:
- 在包含
ParentRowVersion
列的 Child 表上创建一个 可更新 视图 - 将子实体映射到此视图
让我展示一下它是如何工作的.这一切都很简单.
数据库模型:
创建表 [dbo].[父]([ID] [int] NOT NULL IDENTITY(1, 1),[名称] [nvarchar] (50) 非空,[RowVersion] [时间戳] NOT NULL) 开 [主要]ALTER TABLE [dbo].[Parent] 添加约束 [PK_Parent] PRIMARY KEY CLUSTERED ([ID]) ON [PRIMARY]创建表 [dbo].[子项]([ID] [int] NOT NULL IDENTITY(1, 1),[名称] [nvarchar] (50) 非空,[RowVersion] [时间戳] 非空,[ParentID] [int] 非空) 开 [主要]ALTER TABLE [dbo].[Child] 添加约束 [PK_Child] PRIMARY KEY CLUSTERED ([ID]) ON [PRIMARY]去创建视图 [dbo].[ChildView]使用架构作为选择孩子.ID, 孩子.姓名, 孩子.父母ID, Child.RowVersion, p.RowVersion 作为 ParentRowVersionFROM dbo.ChildINNER JOIN dbo.Parent p ON p.ID = Child.ParentID
视图是可更新的,因为它符合 Sql Server 视图可更新的条件.
数据
SET IDENTITY_INSERT [dbo].[Parent] ON插入 [dbo].[Parent]([ID],[Name])值(1,N'Parent1')SET IDENTITY_INSERT [dbo].[Parent] OFFSET IDENTITY_INSERT [dbo].[Child] ON插入 [dbo].[Child] ([ID], [Name], [ParentID]) 值 (1, N'Child1.1', 1)插入 [dbo].[Child] ([ID], [Name], [ParentID]) 值 (2, N'Child1.2', 1)SET IDENTITY_INSERT [dbo].[Child] 关闭
类模型
公共类家长{公共父(){Children = new HashSet();}公共 int ID { 获取;放;}公共字符串名称 { 获取;放;}公共字节 [] RowVersion { 获取;放;}公共 ICollection<Child>孩子{得到;放;}}公共课儿童{公共 int ID { 获取;放;}公共字符串名称 { 获取;放;}公共字节 [] RowVersion { 获取;放;}公共 int ParentID { 获取;放;}公共父母父母{得到;放;}公共字节[] ParentRowVersion { 得到;放;}}
上下文
公共类 TestContext : DbContext{公共TestContext(字符串连接字符串):基础(连接字符串){}公共数据库集<父级>父母{得到;放;}公共 DbSet<Child>孩子{得到;放;}受保护的覆盖无效 OnModelCreating(DbModelBuilder modelBuilder){modelBuilder.Entity<Parent>().Property(e => e.RowVersion).IsRowVersion();modelBuilder.Entity().ToTable(ChildView");modelBuilder.Entity<Child>().Property(e => e.ParentRowVersion).IsRowVersion();}}
把它放在一起
这段代码更新了一个Child
,而一个假的并发用户更新了它的Parent
:
使用 (var db = new TestContext(connString)){var child = db.Children.Find(1);//父级的假并发更新.db.Database.ExecuteSqlCommand("UPDATE dbo.Parent SET Name = Name + 'x' WHERE ID = 1");child.Name = child.Name + "y";db.SaveChanges();}
现在 SaveChanges
抛出所需的 DbUpdateConcurrencyException
.当父更新被注释掉时,子更新成功.
我认为这种方法的优势在于它完全独立于数据访问库.你所需要的只是一个支持乐观并发的 ORM.未来转向 EF-core 不会有问题.
I have a parent entity that I need to do a concurrency check (as annotated as below)
[Timestamp]
public byte[] RowVersion { get; set; }
I have a bunch of client processes that access readonly values out of this parent entity and primarily update its child entities.
The constraint
Clients should not interfere with each other's work, (e.g. updating child records should not throw a concurrency exception on the parent entity).
I have a server process that does update this parent entity, and in this case the client process needs to throw if the parent entity has been changed.
Note : The client's concurrency check is sacrificial, the server's workflow is mission critical.
The problem
I need to check (from the client process) if the parent entity has changed without updating the parents entity's row version.
It's easy enough to do a concurrency check on the parent entity in EF:
// Update the row version's original value
_db.Entry(dbManifest)
.Property(b => b.RowVersion)
.OriginalValue = dbManifest.RowVersion; // the row version the client originally read
// Mark the row version as modified
_db.Entry(dbManifest)
.Property(x => x.RowVersion)
.IsModified = true;
The IsModified = true
is the deal breaker because it forces the row version to change. Or, said in context, this check from the client process will cause a row version change in the parent entity, which interferes needlessly with the other client processes' workflows.
A work around : I could potentially wrap the SaveChanges
from the client process in a Transaction and then a subsequent read of the parent entity's row version, in-turn, rolling back if the row version has changed.
Summary
Is there an out-of-the-box way with Entity Framework where I can SaveChanges
(in the client process for the child entities) yet also check if the parent entity's row version has changed (without updating the parent entities row version).
There is a surprisingly simple solution, "out-of-2-boxes", but it requires two modifications I'm not sure you can, or are willing to, make:
- Create an updatable view on the Child table containing a
ParentRowVersion
column - Map the Child entity to this view
Let me show how this works. It's all pretty straightforward.
Database model:
CREATE TABLE [dbo].[Parent]
(
[ID] [int] NOT NULL IDENTITY(1, 1),
[Name] [nvarchar] (50) NOT NULL,
[RowVersion] [timestamp] NOT NULL
) ON [PRIMARY]
ALTER TABLE [dbo].[Parent] ADD CONSTRAINT [PK_Parent] PRIMARY KEY CLUSTERED ([ID]) ON [PRIMARY]
CREATE TABLE [dbo].[Child]
(
[ID] [int] NOT NULL IDENTITY(1, 1),
[Name] [nvarchar] (50) NOT NULL,
[RowVersion] [timestamp] NOT NULL,
[ParentID] [int] NOT NULL
) ON [PRIMARY]
ALTER TABLE [dbo].[Child] ADD CONSTRAINT [PK_Child] PRIMARY KEY CLUSTERED ([ID]) ON [PRIMARY]
GO
CREATE VIEW [dbo].[ChildView]
WITH SCHEMABINDING
AS
SELECT Child.ID
, Child.Name
, Child.ParentID
, Child.RowVersion
, p.RowVersion AS ParentRowVersion
FROM dbo.Child
INNER JOIN dbo.Parent p ON p.ID = Child.ParentID
The view is updatable because it meets the conditions for Sql Server views to be updatable.
Data
SET IDENTITY_INSERT [dbo].[Parent] ON
INSERT INTO [dbo].[Parent] ([ID], [Name]) VALUES (1, N'Parent1')
SET IDENTITY_INSERT [dbo].[Parent] OFF
SET IDENTITY_INSERT [dbo].[Child] ON
INSERT INTO [dbo].[Child] ([ID], [Name], [ParentID]) VALUES (1, N'Child1.1', 1)
INSERT INTO [dbo].[Child] ([ID], [Name], [ParentID]) VALUES (2, N'Child1.2', 1)
SET IDENTITY_INSERT [dbo].[Child] OFF
Class model
public class Parent
{
public Parent()
{
Children = new HashSet<Child>();
}
public int ID { get; set; }
public string Name { get; set; }
public byte[] RowVersion { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public int ID { get; set; }
public string Name { get; set; }
public byte[] RowVersion { get; set; }
public int ParentID { get; set; }
public Parent Parent { get; set; }
public byte[] ParentRowVersion { get; set; }
}
Context
public class TestContext : DbContext
{
public TestContext(string connectionString) : base(connectionString){ }
public DbSet<Parent> Parents { get; set; }
public DbSet<Child> Children { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>().Property(e => e.RowVersion).IsRowVersion();
modelBuilder.Entity<Child>().ToTable("ChildView");
modelBuilder.Entity<Child>().Property(e => e.ParentRowVersion).IsRowVersion();
}
}
Bringing it together
This piece of code updates a Child
while a fake concurrent user updates its Parent
:
using (var db = new TestContext(connString))
{
var child = db.Children.Find(1);
// Fake concurrent update of parent.
db.Database.ExecuteSqlCommand("UPDATE dbo.Parent SET Name = Name + 'x' WHERE ID = 1");
child.Name = child.Name + "y";
db.SaveChanges();
}
Now SaveChanges
throws the required DbUpdateConcurrencyException
. When the update of the parent is commented out the child update succeeds.
I think the advantage of this method is that it's pretty independent of a data access library. All you need is an ORM that supports optimistic concurrency. A future move to EF-core won't be a problem.
这篇关于在不更新行版本的情况下检查实体的并发性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!