EF6 - Update()失败,多个SaveChangeAsync,但没有多个SaveChanges()

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EF6 - Update()失败,多个SaveChangeAsync,但没有多个SaveChanges()相关的知识,希望对你有一定的参考价值。

我目前面临的情况我不太明白。对于每个存储库,我有一个基础,这是它的一部分:

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(),它抛出以下异常,但仅适用于异步逻辑:

附加“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();
    });
}

我服务的两种方法

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()可以单独调用,我不想保持2次相同的代码。

如果您尝试这段代码(通过将其包含在项目中)以重现该情况,您将看到第二个Update()是抛出上述异常的那个。


因为我无法提供TryExecuteTransactionalFuncAsynclogic的完整代码,所以有一个简单的版本

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;
    }
}
答案

好吧,我确实找到了解决方法!感谢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,这取决于您的实现

我希望它有帮助:)

以上是关于EF6 - Update()失败,多个SaveChangeAsync,但没有多个SaveChanges()的主要内容,如果未能解决你的问题,请参考以下文章

EF6 中具有不同执行策略的多个 DbConfiguration

EF6(代码优先)单个外键属性上的多个导航属性

在 Azure Devops 中编写 EF6 迁移脚本

首先使用EF6代码在多个表中添加记录

EF6 - BaseClass数据复制

使用 linq 在一个 SQL 查询 (EF6) 中为多个数据库列选择可能的值