Ошибка при обновлении сущности в EF(используются репозитории и DI)
Пытаюсь реализовать работу с БД, используя репозитории и DI (Microsoft.Extensions). В репозитории прописан следующий код:
public void Update(T item)
{
if (item is null) throw new ArgumentNullException(nameof(item));
_db.Entry(item).State = EntityState.Modified;
if (AutoSaveChanges) _db.SaveChanges();
}
Сами репозитории зареганы в контейнере .AddTransient<>() БД регистрируется так:
public static IServiceCollection AddDatabase(this IServiceCollection services, IConfiguration Configuration) => services
.AddDbContext<InveTimeDB>(opt =>
{
var type = Configuration["Type"];
if (type == "MSSQL") opt.UseSqlServer(Configuration.GetConnectionString(type));
else throw new InvalidOperationException($"Тип подключения {type} не поддерживается");
})
.AddScoped<DbInitializer>()
.AddRepositoriesInDb()
;
class DbInitializer
{
private readonly InveTimeDB _db;
public DbInitializer(InveTimeDB db)
{
_db = db;
}
public async Task InitializeAsync()
{
await _db.Database.MigrateAsync();
}
}
При попытке обновления бросает ошибку, что сущность уже отслеживается
The instance of entity type 'Employee' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
Смена типа регистрации в контейнере сервисов результата не дала, изменения в методе _db.Entry(item).State = EntityState.Modified на _db.Entry(item).State = EntityState.Detached; так же результата не дала. Мне дали совет настроить сервис так, чтобы он после использования сразу удалялся, но как это сделать я не нашёл..... Help please!!!....
PS Класс реализации репозитория:
internal class DbRepository<T> : IRepository<T> where T : Entity, new()
{
private readonly InveTimeDB _db;
private readonly DbSet<T> _Set;
public bool AutoSaveChanges { get; set; } = true;
public DbRepository(InveTimeDB db)
{
_db = db;
_Set = db.Set<T>();
}
public virtual IQueryable<T> Items => _Set;
public T Add(T item)
{
if (item is null) throw new ArgumentNullException(nameof(item));
_db.Entry(item).State = EntityState.Added;
if (AutoSaveChanges) _db.SaveChanges();
return item;
}
public async Task<T> AddAsync(T item, CancellationToken Cancel = default)
{
if (item is null) throw new ArgumentNullException(nameof(item));
_db.Entry(item).State = EntityState.Added;
if (AutoSaveChanges) await _db.SaveChangesAsync(Cancel).ConfigureAwait(false);
return item;
}
public T Get(int id) => Items.SingleOrDefault(item => item.Id == id);
public async Task<T> GetAsync(int id, CancellationToken Cancel = default) => await Items
.SingleOrDefaultAsync(item => item.Id == id, Cancel)
.ConfigureAwait(false);
public void Remove(int id)
{
var item = _Set.Local.FirstOrDefault(i => i.Id == id) ?? new T { Id = id };
_db.Remove(item);
if (AutoSaveChanges) _db.SaveChanges();
}
public async Task RemoveAsync(int id, CancellationToken Cancel = default)
{
var item = _Set.Local.FirstOrDefault(i => i.Id == id) ?? new T { Id = id };
_db.Remove(item);
if (AutoSaveChanges) await _db.SaveChangesAsync(Cancel).ConfigureAwait(false);
}
public void Update(T item)
{
if (item is null) throw new ArgumentNullException(nameof(item));
//TODO: The instance of entity type 'Employee' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
_db.Entry(item).State = EntityState.Modified;
if (AutoSaveChanges) _db.SaveChanges();
}
public async Task UpdateAsync(T item, CancellationToken Cancel = default)
{
if (item is null) throw new ArgumentNullException(nameof(item));
_db.Entry(item).State = EntityState.Modified;
if (AutoSaveChanges) await _db.SaveChangesAsync(Cancel).ConfigureAwait(false);
}
}
Регистратор репозитория
public static class RepositoryRegistrator
{
public static IServiceCollection AddRepositoriesInDb(this IServiceCollection services) => services
.AddTransient<IRepository<Employee>, EmployeeRepository>()
;
}
Команда загрузки данных
public async Task OnLoadEmployeesCommandExequted(object p)
{
EmployeesCollection = new ObservableCollection<EmpBaseInfo>(await _EmployeeRepository.Items
.Select(e => new EmpBaseInfo
{
Id = e.Id,
Name = e.Name,
SecondName = e.SecondName,
Patronymic = e.Patronymic,
Phone = e.Phone,
Email = e.Email,
Position = e.Position
})
.OrderBy(p => p.SecondName)
.ThenBy(p => p.Name)
.ThenBy(p => p.Patronymic)
.ToArrayAsync());
}
Команда изменения сущности в БД
public void OnModifiEmployeeCommandExequted(object p)
{
var emp_to_modifi = p ?? SelectedEmployee;
if (emp_to_modifi is not EmpBaseInfo empBase) return;
var emp = empBase.ConvertToEmployee();
if (!_UserDialog.EditEpmloyee(emp)) return;
//TODO: Realise command
_EmployeeRepository.Update(emp); //Вот тут все и ломается
/* пока не рассматривается. даже не помню, что я тут хотел сделать
var item = _EmployeesCollection.FirstOrDefault(i => i.Id == emp.Id);
if (item != null)
{
item = new EmpBaseInfo(emp);
}
}
*/
Метод диалогового окна для изменения данных сотрудника
public bool EditEpmloyee(Employee employee)
{
EmpEditorWindowViewModel employee_editor_viewModel = new(employee, _PositionRepository, _ShowPasswordWindowService);
EmpEditorWindow employee_editor_window = new()
{
DataContext = employee_editor_viewModel
};
if (employee_editor_window.ShowDialog() != true)
{
return false;
}
employee.Id = employee_editor_viewModel.EmpId;
employee.SecondName = employee_editor_viewModel.EmpSecondName;
employee.Name = employee_editor_viewModel.EmpName;
employee.Patronymic = employee_editor_viewModel.EmpPatronymic;
employee.Phone = employee_editor_viewModel.EmpPhone;
employee.Email = employee_editor_viewModel.EmpEmail;
employee.Login = employee_editor_viewModel.EmpLogin;
employee.Position = employee_editor_viewModel.SelectedPosition;
return true;
}
забыл, вот как сервисы вызваны в App:
internal static void ConfigureServices(HostBuilderContext host, IServiceCollection services) => services
.AddDatabase(host.Configuration.GetSection("Database"))
.AddViewModels()
.AddServices()
;
Ответы (1 шт):
Предлагаю найти в контексте энтити по айди и после ее модифицировать. С точки зрения кода это будет сделать проще всего, и без рефакторинга вот этого всего...
// var emp = empBase.ConvertToEmployee(); заменить на ->
var emp = await _EmployeeRepository.GetAsync(empBase.Id);