gpt4 book ai didi

sql - 使用 Entity Framework 进行原子读写

转载 作者:行者123 更新时间:2023-12-03 20:31:34 25 4
gpt4 key购买 nike

我有两个不同的进程(在不同的机器上),它们正在读取和更新数据库记录。

我需要确保的规则是,仅当记录的值(假设是“Initial”)时才必须更新记录。另外,在提交之后,我想知道它是否实际上是从当前进程中更新的(如果值不是初始值)

现在,下面的代码执行如下操作:

var record = context.Records
.Where(r => (r.id == id && r.State == "Initial"))
.FirstOrDefault();

if(record != null) {
record.State = "Second";
context.SaveChanges();
}

现在有几个问题

1)从查看代码来看,似乎在状态为“Initial”的记录被提取后,其他一些进程可能已将其更新为“Second”状态,然后该进程才执行SaveChanges。
在这种情况下,我们不必要地将状态覆盖为相同的值。这种情况在这里发生吗?

2)如果情况1并非如此,则EntityFramework可能会将上述内容翻译成类似
update Record set State = "Second" where Id = someid and State = "Initial"

并将其作为交易执行。这样,只有一个进程写入该值。 EF默认TransactionScope是这种情况吗?

在这两种情况下,我又如何确定是从我的流程而不是其他流程进行了更新?

如果这是内存中对象,那么在代码中它将转换为类似假设多个线程访问相同数据结构的内容
Record rec = FindRecordById(id);
lock (someobject)
{
if(rec.State == "Initial")
{
rec.State = "Second";
//Now, that I know I updated it I can do some processing
}
}

谢谢

最佳答案

通常,可以使用2种主要的并发模式:

  • 悲观并发:锁定一行以防止其他人意外更改当前尝试更新的数据。 EF不为此类并发模式提供任何 native 支持。
  • 乐观并发:从EF's documentation引用:“乐观并发涉及乐观地尝试将您的实体保存到数据库,以希望自从实体加载以来那里的数据没有改变。如果事实证明数据已更改,则会引发异常,您必须先解决冲突,然后再尝试保存。” EF支持此模式,并且可以很简单地使用它。

  • 以EF支持的开放式并发选项为重点,让我们比较一下在有和没有EF进行开放式并发控制处理的情况下示例的行为。我假设您正在使用SQL Server。

    没有并发控制

    让我们从数据库中的以下脚本开始:

    create table Record (
    Id int identity not null primary key,
    State varchar(50) not null
    )

    insert into Record (State) values ('Initial')

    这是带有 DbContextRecord实体的代码:

    public class MyDbContext : DbContext
    {
    static MyDbContext()
    {
    Database.SetInitializer<MyDbContext>(null);
    }

    public MyDbContext() : base(@"Server=localhost;Database=eftest;Trusted_Connection=True;") { }

    public DbSet<Record> Records { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    base.OnModelCreating(modelBuilder);
    modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    modelBuilder.Configurations.Add(new Record.Configuration());
    }
    }

    public class Record
    {
    public int Id { get; set; }

    public string State { get; set; }

    public class Configuration : EntityTypeConfiguration<Record>
    {
    public Configuration()
    {
    this.HasKey(t => t.Id);

    this.Property(t => t.State)
    .HasMaxLength(50)
    .IsRequired();
    }
    }
    }

    现在,让我们使用以下代码测试并发更新方案:

    static void Main(string[] args)
    {
    using (var context = new MyDbContext())
    {
    var record = context.Records
    .Where(r => r.Id == 1 && r.State == "Initial")
    .Single();

    // Insert sneaky update from a different context.
    using (var sneakyContext = new MyDbContext())
    {
    var sneakyRecord = sneakyContext.Records
    .Where(r => r.Id == 1 && r.State == "Initial")
    .Single();

    sneakyRecord.State = "Sneaky Update";
    sneakyContext.SaveChanges();
    }

    // attempt to update row that has just been updated and committed by the sneaky context.
    record.State = "Second";
    context.SaveChanges();
    }
    }

    如果跟踪SQL,您将看到 update语句如下所示:

    UPDATE [dbo].[Record]
    SET [State] = 'Second'
    WHERE ([Id] = 1)

    因此,实际上,它不关心其他事务是否潜入了更新。它只是盲目地写了其他更新所做的任何事情。因此,该行在数据库中的 State的最终值为 'Second'

    乐观并发控制

    让我们调整初始SQL脚本,以在表中包括一个并发控制列:

    create table Record (
    Id int identity not null primary key,
    State varchar(50) not null,
    Concurrency timestamp not null -- add this row versioning column
    )

    insert into Record (State) values ('Initial')

    让我们还调整我们的 Record实体类( DbContext类保持不变):

    public class Record
    {
    public int Id { get; set; }

    public string State { get; set; }

    // Add this property.
    public byte[] Concurrency { get; set; }

    public class Configuration : EntityTypeConfiguration<Record>
    {
    public Configuration()
    {
    this.HasKey(t => t.Id);

    this.Property(t => t.State)
    .HasMaxLength(50)
    .IsRequired();

    // Add this config to tell EF that this
    // property/column should be used for
    // concurrency checking.
    this.Property(t => t.Concurrency)
    .IsRowVersion();
    }
    }
    }

    现在,如果我们尝试重新运行与上一场景相同的 Main()方法,您将注意到 update语句的生成和执行方式发生了变化:

    UPDATE [dbo].[Record]
    SET [State] = 'Second'
    WHERE (([Id] = 1) AND ([Concurrency] = <byte[]>))
    SELECT [Concurrency]
    FROM [dbo].[Record]
    WHERE @@ROWCOUNT > 0 AND [Id] = 1

    特别要注意的是,EF如何自动在 where语句的 update子句中包括为并发控制定义的列。

    在这种情况下,因为实际上存在并发更新,EF会检测到它,并在此行上抛出 DbUpdateConcurrencyException异常:

    context.SaveChanges();

    因此,在这种情况下,如果您检查数据库,您将看到有问题的行的 State值将是 'Sneaky Update',因为我们的第二次更新未能通过并发检查。

    最后的想法

    如您所见,在EF中激活自动乐观并发控制不需要做太多的事情。

    但是,棘手的地方是,如何在抛出 DbUpdateConcurrencyException异常时处理该异常?在这种情况下,完全由您决定要做什么。但是,有关该主题的进一步指导,您可以在这里找到更多信息: EF - Optimistic Concurrency Patterns

    关于sql - 使用 Entity Framework 进行原子读写,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32259306/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com