【问题标题】:Entity Framework Core: Update() method INSERTs instead of UPDATEs on dependent entity实体框架核心:Update() 方法插入而不是依赖实体上的更新
【发布时间】:2020-05-30 11:04:28
【问题描述】:

似乎 EF Core 正在执行 INSERT 而不是 UPDATE,因此 MySQL 抱怨重复键。但是,我在 DbSet 上使用 Update 方法,并且实体确实设置了主键。这会导致 MySql 中出现 DUPLICATE ENTRY 错误。

运行 VS 2019、EF Core 3.1.1 和 ASP.NET Core 3.1

模型(我不使用 Fluent 配置来建立关系,只是约定):

public class Vehicle 
{
    public long Id {get; set; } // PRIMARY KEY, AUTO INCREMENT
    public string RegistrationNumber { get; set; }
    public VehicleSession Session { get; set; }
}


public class VehicleSession
{
    public long VehicleId { get; set; }
    public Vehicle Vehicle { get; set; }
    public string DeviceUuid { get; set; } // PRIMARY KEY
    public string AuthenticationToken { get; set; }
    public string OSName { get; set; }
    public string OSVersion { get; set; }
    public string DeviceModel { get; set; }
}

数据库:

主键为 DeviceUuid 且具有键 'device2' 的表 'vehiclesessions':

控制器:

一个请求来自某个地方。 DeviceUuid 是从 HTTP 标头中获取的。

    public async Task<ActionResult<VehicleLoginResponse>> Login(VehicleLogin login)
    {
        Request.Headers.TryGetValue(BaseConstants.DEVICE_UUID, out StringValues deviceUuid);
        Vehicle vehicle = await vehicleService.Authenticate(login.Username, login.Password); // returns a Vehicle by asking dbContet: dbContext.Vehicles.FirstOrDefault(v => v.Username.Equals(username));
        VehicleSession vs = await vehicleService.CreateSession(vehicle, login, deviceUuid);
        // ...
    }

服务:

此服务创建一个新的 VehicleSession 对象,将其分配给 Vehicle 属性,并对 Vehicle 执行 .Update,以便与它一起保存新会话。

    public async Task<VehicleSession> CreateSession(Vehicle vehicle, VehicleLogin vehicleLogin, string deviceUuid)
    {
        VehicleSession vs = new VehicleSession()
        {
            Vehicle = vehicle,
            AuthenticationToken = someTokenFetchedFromSomewhere,
            DeviceModel = vehicleLogin.DeviceModel,
            DeviceUuid = deviceUuid, // is 'device2'
            OSName = vehicleLogin.OSName,
            OSVersion = vehicleLogin.OSVersion
        };

        vehicle.Session = vs;
        dbContext.Vehicles.Update(vehicle);
        await dbContext.SaveChangesAsync();
        return vs;
    }

我是否替换 new VehicleSession 和分配都没关系,只需编辑已经存在的 Vehicle.Session,同样的错误:

    public async Task<VehicleSession> CreateSession(Vehicle vehicle, VehicleLogin vehicleLogin, string deviceUuid)
    {
        if (vehicle.Session == null)
            vehicle.Session = new VehicleSession(); // never executes this line
        vehicle.Session.AuthenticationToken = someTokenFetchedFromSomewhere;

        vehicle.Session.DeviceModel = vehicleLogin.DeviceModel;
        vehicle.Session.DeviceUuid = deviceUuid;
        vehicle.Session.OSName = vehicleLogin.OSName;
        vehicle.Session.OSVersion = vehicleLogin.OSVersion;

        await dbContext.SaveChangesAsync();
        return vehicle.Session;
    }

这样做时,我收到一条错误消息:

Microsoft.EntityFrameworkCore.DbUpdateException:发生错误 在更新条目时。有关详细信息,请参阅内部异常。 ---> MySql.Data.MySqlClient.MySqlException (0x80004005):重复条目 'device2' 用于键 'vehiclesessions.PRIMARY' ---> MySql.Data.MySqlClient.MySqlException (0x80004005):重复条目 'device2' 用于键 'vehiclesessions.PRIMARY'

生成的 SQL:

 Failed executing DbCommand (125ms) [Parameters=[@p0='?' (Size = 95), @p1='?' (Size = 95), @p2='?' (Size = 4000), @p3='?' (Size = 4000), @p4='?' (Size = 4000), @p5='?' (DbType = Int64)], CommandType='Text', CommandTimeout='30']
      INSERT INTO `VehicleSessions` (`DeviceUuid`, `AuthenticationToken`, `DeviceModel`, `OSName`, `OSVersion`, `VehicleId`)
      VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
fail: Microsoft.EntityFrameworkCore.Update[10000]
      An exception occurred in the database while saving changes for context type 'blabla'.
      Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
       ---> MySql.Data.MySqlClient.MySqlException (0x80004005): Duplicate entry 'device2' for key 'vehiclesessions.PRIMARY'
       ---> MySql.Data.MySqlClient.MySqlException (0x80004005): Duplicate entry 'device2' for key 'vehiclesessions.PRIMARY'

为什么会出现此错误?似乎完成了 INSERT 而不是 UPDATE,即使 Vehicle 对象是从 dbContext 获取、传递并设置了 PRIMARY id。

【问题讨论】:

  • 我缺少生成或 uuid 的部分,例如 Guid obj = Guid.NewGuid();因为你似乎没有生成,否则 mysql 不会介意
  • Guid 没关系,它只是一个字符串。这不是问题。
  • DeviceUUID 来自其他地方,不是在服务中生成的。但同样,这不是问题。问题是为什么似乎在车辆会话中进行了 INSERT,而不是我期望的 UPDATE。
  • 这是一个误解 Vehicles.Update(vehicle) 试图将所有更改发送到数据库,而编程的 vehicle.Session = vs 和 vs 是一个新元素,因此它尝试将其发送到数据库.新元素等于 INSERT
  • 谢谢,但我尝试不执行“新...”,而只是更新现有 VehicleSessions 对象中的一些值。同样的错误。上面的问题中提到了这一点。我刚刚又试了一次,“重复”错误,和以前一样。

标签: mysql asp.net-core entity-framework-core


【解决方案1】:

所以,我想我可能找到了问题所在。

如上所述,我首先进行了搜索以检索车辆,如下所示:

Vehicle vehicle = dbContext.Vehicles.FirstOrDefault(v =&gt; v.Username.Equals(username));

在这种情况下,Vehicle.Session(如果存在的话)是没有填充的。后来,我按照上面的代码进行了更新,但如上所述失败了。

但是,如果我将获取代码更改为:

Vehicle entityVehicle = dbContext.Vehicles
    .Include(x => x.Session)// <-- NEW!
    .FirstOrDefault(v => v.Username.Equals(username));

然后就可以了。

如果我为 .Session 分配一个new VehicleSession(...) 或者我是否更改现有对象中的属性都没有关系。如果我在await dbContext.SaveChangesAsync(); 之前使用dbContext.Vehicles.Update(vehicle); 也没关系,不需要调用.Update(...)

唯一不同的是使用.Include(...)


我自己的理论是:

从 db 获取对象时,EF Core“跟踪器”开始跟踪。属性 Session 为 NULL。当稍后填充Session 时,跟踪器检测到它 NULL,但现在不是 NULL,因此它认为需要INSERT。

然后,如果您在获取时填充Session,跟踪器会看到它在那里,并且应该更新。

【讨论】:

  • 刚刚在 v2.2 中偶然发现了这个问题。虽然您的解释是有道理的,但这似乎应该是一个错误。即使它为空,在我的情况下,关系修复也会自动设置该属性。当 EF Core 未经我要求就这样做时,为什么我必须处理这样的事情?
猜你喜欢
  • 2016-11-08
  • 1970-01-01
  • 2020-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-01
  • 2021-11-17
  • 2023-04-11
相关资源
最近更新 更多