我最近换了一个ContextFactory,因为second operation was started on this context ...
所以我注册了DbContextFactory:
builder.Services.AddDbContextFactory<CharacterSheetDbContext>(
options => {
options.UseMySql(
builder.Configuration.GetConnectionString("DefaultConnection"),
new MySqlServerVersion(new Version(8, 0, 27))
);
options.EnableSensitiveDataLogging();
}
);然后我有了我的服务层:
当工厂被注入发动机时:
public ARepository(IDbContextFactory<CharacterSheetDbContext> contextFactory) {
_contextFactory = contextFactory;
}和更新方法(在这里我创建上下文):
public async Task UpdateAsync(TEntity entity) {
await using var context = await _contextFactory.CreateDbContextAsync();
var set = context.Set<TEntity>();
//_context.ChangeTracker.Clear(); <-- (tried with and without this one)
set.Update(entity);
await context.SaveChangesAsync();
}但是,当我对一个实体调用Update时,它会抛出以下错误:
An exception occurred in the database while saving changes for context type 'Model.Configurations.CharacterSheetDbContext'.
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> MySqlConnector.MySqlException (0x80004005): Duplicate entry '3-9' for key 'characters_has_personalities.PRIMARY'因此,它试图再次插入这个东西,但我只是希望它不被更新,如果不改变。
我该知道些什么?
发布于 2022-01-16 10:45:10
自从切换到DbContext工厂之后,您得到这个异常的原因是,您正在获取一个实体,并告诉工厂创建的一个全新的DbContext实例保存它,以及与它相关的任何实体。
您最肯定看到的问题不是您要更新的实体,而是子实体或该实体所引用的实体。在您的情况下,如果您试图持久化某个字符,并且该字符具有一组个性,那么由于该DBContext没有跟踪这些个性实例,它将把它们视为新的实体,并尝试并插入它们。
坦率地说,与独立实体一起工作是一件痛苦的事情。传递一个实体到一个新的DbContext实例并调用Update看起来很简单,但是除了最简单的场景之外,它很少那么简单。试图将数据访问层构建为一般实现只会增加复杂性。
在最基本的级别上,在处理具有关系的实体时,需要在持久化时将这些相关实体与DbContext相关联。但是,首先您需要检查所讨论的DbContext实例是否还没有跟踪匹配的实体,如果是的话就替换引用。
public async Task UpdateCharacterAsync(Character character)
{
await using var context = await _contextFactory.CreateDbContextAsync();
var set = context.Set<Character>();
foreach(var personality in character.Personalities)
{
context.Attach(personality);
}
set.Update(entity);
await context.SaveChangesAsync();
}最低限度可能会让你朝着正确的方向前进。因为我们需要了解个性--任何其他字符可能引用的东西--并确保DbContext正在跟踪这些特性,因此,如果不使用大量的复杂性和反射,这种方法就不可能是真正的通用方法。即使如此,这种方法也不能防止愚昧,也没有效率。如果同一个相关实体可能在一组关系中多次出现,且传递到该实体的引用不是相同的引用,则在尝试第二次附加该实体时可能会遇到错误。这在处理反序列化实体图时尤为重要,例如使用MVC或Ajax调用,其中实体和相关数据是从JSON反序列化的。例如,如果字符和个性具有对CreatedBy用户实体的引用,则需要附加这些用户实体。假设您有两个对用户ID #1的引用,其中它们是两个不同的对象,而不是对同一个对象的两个引用。当您附加第一个实例时,一切都很好,但是尝试附加第二个实例会导致一个错误,即上下文已经在跟踪具有相同ID的实例。
使用独立的实体可能会很痛苦。处理独立的实体图(关系)很容易成为一场噩梦,因为像这样的错误是情景性的。
使用Update也是没有效率的,因为这将导致UPDATE语句更新表中的所有列,不管是否发生了更改。这还会产生一些情况,如果您不使用快照和行版本控制之类的东西,则需要考虑陈旧的数据覆盖。虽然传递实体并调用Update以避免重新加载实体似乎更好,但这有几个缺点,只有在调用SaveChanges()时才会隐约暴露出来。在执行更新时,我的建议是使用一种跨行的读取和复制方法,因为这样可以在执行过程中验证数据状态,并确保DB UPDATE语句仅在值更改时运行,以及哪些列实际更改。
理想情况下,这与向Update调用发送DTO(只包含可能被更改的字段)结合在一起,在引用的情况下,只需要传递这些引用的ID或ID集合。这使有效负载大小从客户端尽可能紧凑地传递到服务器,而不是传递整个实体结构。
Automapper的Map(source, destination)方法可以帮助将更新后的值从DTO复制到加载的实体,EF的更改跟踪从那里接管,以确定UPDATE语句是否实际需要执行。
该方法最后看起来更像:
public async Task UpdateAsync(UpdateCharacterDTO characterDto)
{
if (characterDto == null) throw new ArgumentNullException("characterDto");
await using var context = await _contextFactory.CreateDbContextAsync();
var character = context.Characters
.Include(x => x.Personalities)
.Single(x => x.CharacterId == characterDTO.CharacterId);
// Here you could check a row version on the DB vs. DTO to see if the DB had changed since the data used to build the DTO was read.
_mapper.Map(characterDto, character);
//If personalities can be added or removed...
var existingPersonalityIds = character.Personalities.Select(x => x.PersonalityId);
var personalityIdsToAdd = characterDto.PersonalityIds.Except(existingPersonalityIds).ToList();
var personalityIdsToRemove = existingPersonalityIds.Except(characterDto.PersonalityIds).ToList();
var personalitiesToAdd = await context.Personalities.Where(x => personalityIdsToAdd.Contains(x.PersonalityId).ToListAsync();
var personalitiesToRemove = character.Personalities.Where(x => personalityIdsToRemove.Contains(x.PersonalityId)).ToList();
foreach(var personality in personalitiesToRemove)
character.Personalities.Remove(personality);
foreach(var personality in personalitiesToAdd)
character.Personalities.Add(personality);
await context.SaveChangesAsync();
}其中,CharacterDTO是一个数据容器,包含可以更新的ID和字段,以及个性ID的集合。(用户可能已经添加或删除项)
它会产生更多的代码,但是它应该很容易跟踪它正在做的事情,并且对于预期的EF跟踪/引用没有任何混淆。理想情况下,您可以通过使操作更具有原子性来避免这种复杂性,例如会有更简单的调用来添加和删除个性和其他相关元素,而不是试图在一个顶级update方法中更新整个对象图。像这样的Update方法工作得很好,但前提是同一个DbContext实例从开始到结束都要跟踪所涉及的实体。
https://stackoverflow.com/questions/70723347
复制相似问题