【问题标题】:Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 'Database operation expected to affect 1 row(s) but actually affected 2 row(s)Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException:'数据库操作预计会影响 1 行,但实际上会影响 2 行
【发布时间】:2021-11-23 20:34:34
【问题描述】:

当我尝试使用相同的值 (carNumber) 更新表时收到此错误,我的条件是更新实际返回日期字段为空的位置。

由于某种原因,查询看起来返回 2 行,但实际上只有 1 行。我正在使用EF。这是函数:

the error - print screen

代码:

public void updateStatus(int carNumber1, string actualDate1)
{
        DateTime accReturn = DateTime.Parse(actualDate1);

        var orderCar1 =  db.CarRentalFields.FirstOrDefault(carNum =>
        (carNum.CarNumber == carNumber1 && carNum.ActualReturnDate == null));

    orderCar1.ActualReturnDate = accReturn;
             
    db.SaveChanges();
}

尝试调用db.saveChanges()时发生错误。

the table from the db, the car number is 1000 - print screen

modelBuilder.Entity pic

请告诉我如何解决这个问题。

【问题讨论】:

  • 你有这张表的主键吗?
  • 您只发布了几条记录。恐怕还有一条记录 carNum.CarNumber == carNumber1 && carNum.ActualReturnDate
  • 你有没有告诉 EF 车号是主键,而事实并非如此?
  • 该表的pk为:start_day, return_date,user_id, car_number。所有这 4 个一起。
  • @CaiusJard 我不确定 car_number 作为主键,它只能在主键组合时发生

标签: c# sql-server entity-framework linq


【解决方案1】:

通过向 car_rental_fields 表添加一个新列解决了问题,id 列包括身份。 正如我从这里和网络上了解到的那样,复杂的 pk 存在问题。 在我的解决方案中, id 不是主键,但它使 linq 更新正确列的逻辑。 感谢所有参与此问题的人。

【讨论】:

    【解决方案2】:

    当 EF 无法为您的实体解析 PK 时,会发生此错误。在大多数情况下,对于简单实体,EF 约定可以计算出 PK,但在您的情况下,您使用的是复合键,因此需要对其进行配置。根据您映射实体的方式,您可以通过以下方式执行此操作:

    • EDMX
    • 在 DbContext.OnModelCreating 中
    • 使用EntityTypeConfiguration 声明
    • 在实体本身中使用属性

    由于我们不知道您的实体是如何配置的,您可以通过在您的实体中使用属性方法作为测试来验证这一点。如果您使用的是 EDMX,则会生成实体类,因此您需要将其替换为 EDMX 中的配置。 (不能真正帮助你,因为我不使用该死的东西:D)

    你可能会有类似的东西:

    public class CarRentalFields
    {
        [Column("start_day")]
        public DateTime StartDay { get; set; }
        [Column("return_date")]
        public DateTime ReturnDate { get; set; }
        [Column("user_id")]
        public int UserId { get; set; }
        [Column("car_number")]
        public DateTime CarNumber { get; set; }
        
        // ... more columns...
    }
    

    您甚至可以在这些字段之一(例如 CarNumber)上具有 [Key] 属性。如果实体中映射了 PK,则问题是它不够具体,无法唯一标识该行。当 EF 去更新一个实体时,它正在检查并期望只更新表中的一行。它发现不止一行会受到影响,所以它会失败。

    [Key] 的属性附加到列顺序中,以便将其识别为复合键。

    public class CarRentalFields
    {
        [Key, Column(Name="start_day", Order=1)]
        public DateTime StartDay { get; set; }
        [Key, Column(Name="return_date", Order=2)]
        public DateTime ReturnDate { get; set; }
        [Key, Column(Name="user_id", Order=3)]
        public int UserId { get; set; }
        [Key, Column(Name="car_number", Order=4)]
        public DateTime CarNumber { get; set; }
        
        // ... more columns...
    }
    

    只要保证这 4 列是表的唯一约束,EF 在构建它的 UPDATE SQL 语句时只更新一行时将被满足。

    再次注意,如果这可行并且您使用的是 EDMX,则需要检查和修改您的 EDMX 映射以进行适当的更改,因为该实体类可能会重新生成,从而丢失您的额外属性。 (我相信从 EDMX 生成的实体类有一个注释标题警告你它是一个生成的类,所以这是一个需要注意的指标。)

    更新: 我的主要怀疑是该表实际上没有定义匹配的 PK,或者运行不同的 PK 组合,或者考虑到这些字段的性质,更可能没有 PK。 EF 可以对没有定义 PK 的表进行操作,但它确实需要一个确保可以唯一标识记录的 Key 定义。您看到的错误发生在该键定义不够唯一时。 (即,如果您正在更新汽车 1,并选择具有以下内容的行: car_number = 1, start_day = 2021-11-21, return_day = 2021-11-22, user_id = 0 问题是数据库中有不止一行具有该组合。如果您正在检查的数据库没有超过一个匹配行,那么您的应用程序几乎可以肯定指向与您正在检查的不同的数据库。

    您可以做的事情来验证这一点:

    1. 获取运行时连接字符串并查看它是否与您正在检查的数据库匹配:

    在运行查询之前,添加以下内容:

    // EF6
    var connectionString = db.Database.Connection.ConnectionString;
    // EF Core 5
    var connectionString = db.Database.GetConnectionString();
    
    1. 查看您实际查询的数据:

    .

    var cars =  db.CarRentalFields.Where(carNum =>
        (carNum.CarNumber == carNumber1 && carNum.ActualReturnDate == null)).ToList();
    

    虽然此查询可能仅返回 1 条记录,但这不是问题的原因。您需要的是该记录的 CarNumber、StartDate、ReturnDate 和 UserId:

    var car =  db.CarRentalFields
        .Where(carNum => carNum.CarNumber == carNumber1 
            && carNum.ActualReturnDate == null)
        .Select(x => new 
        {
            x.CarNumber,
            x.StartDay,
            x.ReturnDate,
            x.UserId
        }).Single(); // Expect our 1 record here...
    var cars = db.CarRentalFields
        .Where(x => x.CarNumber == car.CarNumber
            && x.StartDay == car.StartDay
            && x.ReturnDate == car.ReturnDate
            && x.UserId == car.UserId)
        .ToList(); // Get rows that match our returned Key fields.
    

    这些查询为您要更新的汽车记录选择假定的 PK 值,然后搜索汽车以查找具有预期键字段的匹配记录。我的钱是在顶部查询返回 1 条记录时,底部查询返回两行,这意味着虽然只有 1 条记录具有 #null ActualReturnDate 值,但您的 Key 对于该表的内容来说不够唯一。

    【讨论】:

    • 我在使用contex,你看到我的模型构建器打印屏幕了吗?
    • 是的,好的,这是使用 DbContext 的 OnModelCreating modelBuilder,所以正在定义 Key。接下来要检查的是这些列是否与相应数据库中的 PK /w 唯一约束匹配。如果不是,则可能需要扩展该键。应尽可能避免使用复合键,因为它们使建立关系变得更加困难。您还可以使用分析器捕获建议的更新语句,然后将其转换为简单的 SELECT 以查看返回的行。出于某种原因,不止一行返回。
    • 要检查的另一件事是,应用程序在运行时是否访问了与您正在检查的数据库相同的数据库。如果数据库没有对这些列强制执行唯一约束,您正在检查的数据可能看起来足够独特,但在运行时指向的数据库有重复的行。
    • 使用您的解决方案时出现同样的错误,我是否也需要更改我的功能? @史蒂夫·派
    • 捕获正在生成并在数据库上运行的 SQL。我通常为此使用 Profiler,因此对于 SQL Server 和 SSMS,在 Tools\SQL Profiler 下。对您的数据库运行它,然后执行您的查询。您可以在 SaveChanges 之前在应用程序中使用断点,然后在恢复清除之前的噪音并找到您的 UPDATE 语句之前清除分析器输出。
    猜你喜欢
    • 2018-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-04
    相关资源
    最近更新 更多