EF6 - Update () завершается ошибкой с несколькими SaveChangeAsync, но не с несколькими SaveChanges () - PullRequest
0 голосов
/ 25 октября 2018

В настоящее время я сталкиваюсь с ситуацией, которую я не уверен, что понимаю.Для каждого репозитория у меня есть база, и это ее часть:

public abstract class BaseRepository<T> : IBaseRepository<T> where T : class
{
    /// <summary>
    /// Context for the database
    /// </summary>
    protected readonly DataBaseContext DbContext;

    protected readonly DbSet<T> DbSet;

    // ...

    public virtual T GetById(Guid id)
    {
        Requires.ArgumentNotNullAndNotDefault(id);
        return DbSet.Find(id);
    }

    public virtual Task<T> GetByIdAsync(Guid id)
    {
        Requires.ArgumentNotNullAndNotDefault(id);
        return DbSet.FindAsync(id);
    }

    // ...

    public virtual void Update(T entity)
    {
        DbSet.Attach(entity);
        DbContext.Entry(entity).State = EntityState.Modified;
    }

    // ...

    public void SaveChanges() => DbContext.SaveChanges();

    public Task<int> SaveChangesAsync() => DbContext.SaveChangesAsync();
}

Странно то, что если я получу 2 раза сущность (CarEntity), скажем, в одном и том же контексте, Я GetById() два раза и обновляю 2 разных значения, и при каждом Update(), я SaveChanges() выдает следующее исключение, но только для логики Async:

Присоединение объектатипа 'Project.Model.CarEntity' не удалось, поскольку другой объект того же типа уже имеет то же значение первичного ключа.Это может произойти при использовании метода «Присоединить» или установке состояния объекта на «Неизменено» или «Изменено», если какие-либо объекты в графе имеют конфликтующие значения ключей.Это может быть потому, что некоторые объекты являются новыми и еще не получили сгенерированные базой данных значения ключей.В этом случае используйте метод «Добавить» или «Состояние добавленной сущности» для отслеживания графика, а затем установите для состояния не новых сущностей значение «Неизмененный» или «Измененный», в зависимости от ситуации.

Вот мой контроллер:

[Route("set_car_color_and_licence_plate_color")]
[ResponseType(typeof(Car))]
[HttpPost]
public Task<IHttpActionResult> SetCarColorAndLicencePlate([FromBody] SetCarColorAndLicencePlateRequest setCarColorAndLicencePlateRequest)
{
    return TryExecuteTransactionalFuncAsync(async () =>
    {
        Guid authenticatedStaffMemberId = GetAuthenticatedStaffMemberId();
        await CarService.SetCarColorAsync(authenticatedStaffMemberId, setCarColorAndLicencePlateRequest.CarId, setCarColorAndLicencePlateRequest.CarColor);
        await CarService.SetLicencePlateColorAsync(authenticatedStaffMemberId, setCarColorAndLicencePlateRequest.CarId, setCarColorAndLicencePlateRequest.LicencePlateColor);

        return Ok();
    });
}

И 2 моих метода из моего сервиса

public async Task SetColorAsync(Guid authenticatedStaffMemberId, Guid carId, Color color)
{
    CarEntity carToUpdate = await CarRepository.GetByIdAsync(carId);
    if (carToUpdate == null) throw new BusinessException($"Unknown user for the id : {carId}");

    carToUpdate.UpdatedAt = DateTimeOffset.UtcNow;
    carToUpdate.UpdatedBy = authenticatedStaffMemberId;
    carToUpdate.Color = color;
    UserRepository.Update(carToUpdate);
    await CarRepository.SaveChangesAsync();
}

public async Task SetLicencePlateColorAsync(Guid authenticatedStaffMemberId, Guid carId, Color licencePlateColor)
{
    CarEntity carToUpdate = await CarRepository.GetByIdAsync(carId);
    if (carToUpdate == null) throw new BusinessException($"Unknown user for the id : {carId}");

    carToUpdate.UpdatedAt = DateTimeOffset.UtcNow;
    carToUpdate.UpdatedBy = authenticatedStaffMemberId;
    carToUpdate.LicencePlateColor = licencePlateColor;
    UserRepository.Update(carToUpdate);
    await CarRepository.SaveChangesAsync();
}

Конечно, я мог бы сделать это только одним методом, но SetColor () и SetLicencePlateColor ()можно вызывать отдельно, и я не хочу, чтобы дважды поддерживать один и тот же код.

Если вы попробуете этот фрагмент кода (включив его в проект), чтобы воспроизвести ситуацию, вы 'Вы увидите, что второе Update() - это то, что вызывает исключение выше.


Поскольку я не могу предоставить полный код логики TryExecuteTransactionalFuncAsync, существует облегченная версия этого

public async Task<IHttpActionResult> TryExecuteTransactionalFuncAsync(Func<Task<IHttpActionResult>> apiTask)
{
    using (var transaction = new DatabaseTransaction(DbContext.Database.BeginTransaction()))
    {
        var output = await apiTask.Invoke();
        transaction.Complete();
        return output;
    }
}

1 Ответ

0 голосов
/ 26 октября 2018

Хорошо, я нашел обходной путь!Благодаря https://www.itworld.com/article/2700950/development/a-generic-repository-for--net-entity-framework-6-with-async-operations.html

я только что изменил свой BaseRepository на:

public abstract class BaseRepository<T> : IBaseRepository<T> where T : class
{
    /// <summary>
    /// Context for the database
    /// </summary>
    protected readonly DataBaseContext DbContext;

    protected readonly DbSet<T> DbSet;

    // ...

    public async Task<(T, int)> AddAsync(T entity, bool autoSaveChangesAsync = false)
    {
        Requires.ArgumentNotNullAndNotDefault(entity);

        GetDbContext().Set<T>().Add(entity);
        return (entity, await saveChangesAsync(autoSaveChangesAsync));
    }

    public async Task<(IEnumerable<T>, int)> AddAsync(IEnumerable<T> entities, bool autoSaveChangesAsync = false)
    {
        Requires.ArgumentNotNullAndNotDefault(entities);

        var addedEntities = new List<T>();
        foreach (var entity in entities)
            addedEntities.Add((await AddAsync(entity, false)).Item1);

        return (addedEntities, await saveChangesAsync(autoSaveChangesAsync));
    }

    public Task<T> GetByIdAsync(Guid id)
    {
        Requires.ArgumentNotNullAndNotDefault(id);
        return DbSet.FindAsync(id);
    }

    // ...

    public async Task<(T, int)> UpdateAsync(T entity, int key, bool autoSaveChangesAsync = false)
    {
        if (entity == null)
            return (null, 0);

        T existingEntity = await DbContext.Set<T>().FindAsync(key);
        if (existingEntity != null)
        {
            DbContext.Entry(existingEntity).CurrentValues.SetValues(entity);
            return (existingEntity, await saveChangesAsync(autoSaveChangesAsync));
        }

        return (existingEntity, 0); // Because 0 entity have been written into the database
    }

    // ...

    private async Task<int> saveChangesAsync(bool autoSaveChangesAsync)
    {
        if (autoSaveChangesAsync)
            return await SaveChangesAsync();
        else
            return 0; // Because 0 entity have been written into the database
    }

    public Task<int> SaveChangesAsync() => GetDbContext().SaveChangesAsync();
}

Для этой базы мои ключи int, но вы можете изменить его на Guid, это зависит от вашей реализации

Надеюсь, это поможет:)

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