Entity Framework Core: метод Update () вставляет вместо ОБНОВЛЕНИЙ зависимый объект - PullRequest
0 голосов
/ 14 февраля 2020

Похоже, что EF Core делает INSERT вместо UPDATE, и поэтому MySQL жалуется на дубликат ключа. Тем не менее, я использую метод Update на DbSet, и у сущностей установлены первичные ключи. Это приводит к ошибке DUPLICATE ENTRY в MySql.

Запуск VS 2019, EF Core 3.1.1 и ASP. NET Core 3.1

* Модель 1007 * (я не использую конфигурацию 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; }
}

База данных:

Таблица «Тезисы», где ПЕРВИЧНЫЙ KEY является DeviceUuid и имеет ключ 'device2':

enter image description here

Контроллер:

Запрос поступает от где-то. 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 и выполняет .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. 'for key' 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.

1 Ответ

0 голосов
/ 15 февраля 2020

Итак, я думаю, что мог бы найти проблему.

Как отмечалось выше, я сначала выполнил поиск, чтобы получить Автомобиль, как это:

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

В этом случае сессия Vehicle.Session, если она существовала, была заполнена , а не . Позже я сделал обновление, как показано в приведенном выше коде, и оно не удалось, как указано выше.

Но если я изменю код выборки на следующий:

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

, тогда оно будет работать.

Неважно, назначу ли я .Session new VehicleSession(...) или я изменю свойства существующего объекта. Также не имеет значения, если я использую dbContext.Vehicles.Update(vehicle); до await dbContext.SaveChangesAsync();, вызов .Update(...) не нужен.

Единственное, что имело значение, было использование .Include(...).


Моя собственная теория:

При извлечении объекта из базы данных, трекер EF Core начинает отслеживание. Свойство Session имеет значение NULL. Когда Session позже заполняется, трекер обнаруживает, что он был NULL, но теперь не NULL, таким образом, он показывает, что требуется INSERT.

И затем, если вы заполните Session при получении, трекер увидит, что он есть, и должен быть обновлен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...