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

时间:2018-10-25 18:20:06

标签: c# entity-framework sql-update

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

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个不同的值,并且每次{{1 }},我Update(),它引发以下异常,但仅适用于异步逻辑:

  

附加类型为“ Project.Model.CarEntity”的实体失败,因为   相同类型的另一个实体已经具有相同的主键   值。使用“附加”方法或设置   如果实体中的任何实体处于“未更改”或“已修改”状态,   图的键值有冲突。这可能是因为某些实体   是新的,尚未收到数据库生成的键值。在   在这种情况下,请使用“添加”方法或“添加”实体状态进行跟踪   图形,然后将非新实体的状态设置为“不变”或   视情况进行“修改”。

有我的控制器:

SaveChanges()

我的2种服务方法

[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();
    });
}

当然,我只能用一种方法来做到这一点,但是SetColor()和SetLicencePlateColor()可以分别调用,而我不想保持两次相同的代码。

如果您尝试这段代码(通过将其包含到项目中)来重现这种情况,则会看到第二个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(); } 是引发上述异常的代码。


因为我无法提供Update()逻辑的完整代码,所以它有一个简单的版本

TryExecuteTransactionalFuncAsync

1 个答案:

答案 0 :(得分:0)

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

我希望这会有所帮助:)