Entity Framework 6 更改跟踪器以错误的顺序发出语句

问题描述 投票:0回答:1

问题描述

我有一种方法,通过将接收到的实体与相应的数据库行进行比较,将实体图的更改同步到数据库,然后用每个对象正确的

ChangeTracker
更新
State

这通常工作得很好,但是当存在唯一索引时,由于语句的顺序变得很重要,所以一切都会崩溃。

在这个例子中,我首先要删除唯一:“A”的行

DeleteUpdateOrders
,然后修改具有唯一:“A”的另一行。如果
DELETE
UPDATE
之前执行,这将起作用,但是语句将以相反的方式执行。

有没有办法控制 Entity Framework 6 发出语句的顺序?

数据库脚本(SQL Server):

CREATE DATABASE ChangeTracker
GO

USE ChangeTracker

CREATE TABLE DeleteUpdateOrders
(
    DeleteUpdateOrderId INT NOT NULL PRIMARY KEY,
    [Unique] CHAR NOT NULL
)

CREATE UNIQUE INDEX IX_DeleteUpdateOrders_Unique 
ON DeleteUpdateOrders ([Unique])

INSERT INTO DeleteUpdateOrders VALUES (1, 'A')
INSERT INTO DeleteUpdateOrders VALUES (2, 'B')

最小重现:

public void Main()
{
    var context = new TestContext();

    context.Database.Log = Console.Write;

    var databaseRows = context.DeleteUpdateOrders.AsNoTracking().ToArray();

    // Delete the row with Unqiue = "A"
    var rowToDelete = databaseRows.Single(r => r.DeleteUpdateOrderId == 1); 
    context.Entry(rowToDelete).State = EntityState.Deleted;

    // Get the existing row with Unique = "B" and set Unique = "A"
    var rowToModify = databaseRows.Single(r => r.DeleteUpdateOrderId == 2); 
    rowToModify.Unique = "A";
    context.Entry(rowToModify).State = EntityState.Modified;

    // context.ChangeTracker at this stage contains:
    // Modified { DeleteUpdateOrderId: 2, Unique: "A" }
    // Deleted  { DeleteUpdateOrderId: 1, Unique: "A" }

    // Console output indicates that the UPDATE was executed before
    // the DELETE statement, and the innermost SqlException thrown on 
    // context.SaveChanges is:
    //
    // Cannot insert duplicate key row in object 'dbo.DeleteUpdateOrders' with 
    // unique index 'IX_DeleteUpdateOrders_Unique'.The duplicate key value is (A).
    context.SaveChanges();
}

public class TestContext : DbContext
{
    public IDbSet<DeleteUpdateOrder> DeleteUpdateOrders { get; set; }

    public TestContext() : 
        base("Server=.; Database=ChangeTracker; Integrated Security=SSPI")
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        Database.SetInitializer<TestContext>(null);

        // Informing EF6 about the index does not seem to make a difference ...
        modelBuilder
            .Entity<DeleteUpdateOrder>()
            .HasIndex(entity => entity.Unique)
            .IsUnique();
    }
}

public class DeleteUpdateOrder
{
    public int DeleteUpdateOrderId { get; set; }
    
    public string Unique { get; set; }
}
c# entity-framework entity-framework-6 change-tracking
1个回答
0
投票

据我所知,没有办法告诉 EF 明确的操作顺序。强制执行的明显方法是使用 2x

SaveChanges
调用,但这需要显式事务来确保两者一起提交或回滚。

每当涉及导航属性时,EF 将强制执行操作顺序。根据这个唯一值的性质,如果该值本身有意义,您可以引入表格和阴影导航属性。

例如,如果我们有一个 UniqueValues 表,将此 UniqueValue 作为 PK,那么我们可以将关系配置为:

modelBuilder
    .Entity<DeleteUpdateOrder>()
    .HasOne<UniqueValue>("UniqueValue")
    .WithOne()
    .HasForeignKey(entity => entity.Unique)
    .IsRequired();

现在,当您运行该场景时,您可以:

var rowToDelete = context.DeleteUpdateOrders.Single(r => r.DeleteUpdateOrderId == 1); 
context.DeleteUpdateOrders.Remove(rowToDelete);

// Get the existing row with Unique = "B" and set Unique = "A"
var rowToModify = context.DeleteUpdateOrders.Single(r => r.DeleteUpdateOrderId == 2); 
rowToModify.Unique = "A";
context.SaveChanges();

我使用删除/插入、插入/更新等进行了几次不同的运行测试。操作顺序并不重要,EF 在知道存在依赖关系时会正确解析顺序。

此方法的警告是,它确实需要唯一值的 FK 和关联表,因此如果您只想要一个可以检查和插入(如果不存在)的唯一值,则此方法将不起作用。在插入新订单之前,需要将新值插入到 UniqueValues 表中。

© www.soinside.com 2019 - 2024. All rights reserved.