我在創建專案時發現了一個問題。如果有人提到這個問題,我將不勝感激。在我的專案中,我使用分層模型。與資料庫 (DB) 通信的存盤庫層(資料訪問層)和實作服務和物件的服務層(業務邏輯層)(資料傳輸物件)。
因此,dbSet.Update 方法存在問題。當物件(obj)作為引數進入Update方法時,在_db.Entry(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj).State = EntityState.Modified的方法呼叫期間或 _db.Update(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj) 在第一個用戶更新的情況下(如來自“view.xaml”)obj 是更新和更改保存在資料庫中(因為 dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) 回傳 null 并且顯然我的 obj 進入了 _db.Update 方法)。如果用戶重復更新(在“view.xaml”視圖中)物件 obj - 當它進入 _db.Update(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) 時? obj) 方法,它沒有考慮,因為資料背景關系已經跟蹤它并且在_db中。Update 方法從 dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) 獲取物件。如果 dbSet.Local 中的這個物件根據來自用戶的型別進行更新,一切都會好起來的。但是,情況并非如此,當用戶編輯其屬性時,它會被跟蹤但不會更改。由于我使用服務以及相應的資料傳輸物件物體,因此無法正確跟蹤它。
鑒于上述,我有一個問題。如何更新被跟蹤的物體(通過新的修改物件),或者如何在 dbSet.Local 中手動分配必要的物件來替換存盤在那里的物件?或者如何使 Microsoft.EntityFrameworkCore.ChangeTracking 不以任何方式跟蹤我的物件的更改?
為了使更改不被跟蹤,我為 DbContextOptionsBuilder 物體使用了 QueryTrackingBehavior.NoTracking 引數,但這僅在第一次加載時有幫助,并且在資料進一步更新時仍使用跟蹤。我還使用了 dbSet.Local.Clear() 方法,但這是一個壞主意,因為從資料庫中洗掉以前的資料會導致資料更新(例如從表中洗掉 20 行并添加更新的行)。
public abstract class GenericRepository<T, TKey> : IGenericRepository<T, TKey> where T : class, IEntity, new()
{
protected DbContext context;
protected DbSet<T> dbSet;
private readonly object _lock = new object();
public GenericRepository(DbContext context)
{
this.context = context;
this.dbSet = context.Set<T>();
}
public virtual IQueryable<T> GetAll()
{
lock (_lock)
return dbSet;
}
public virtual async Task<T> GetAsync(TKey id)
{
try
{
return await dbSet.FindAsync(id);
}
catch (Exception exc)
{
throw exc;
}
}
public async Task AddAsync(T obj)
{
try
{
//dbSet.Local.Clear();
await dbSet.AddAsync(obj);
//context.Entry(obj).State = EntityState.Added;
}
catch (Exception exc)
{
throw exc;
}
}
public async void Delete(TKey id)
{
//context.Entry(obj).State = EntityState.Deleted;
//context.Remove(obj);
try
{
T obj = await GetAsync(id);
dbSet.Remove(obj);
//dbSet.Remove(dbSet.Find(1504));
}
catch (Exception exc)
{
throw exc;
}
}
public void Update(T obj)
{
try
{
context.Entry(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj).State = EntityState.Modified;
//dbSet.Update(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj);
//dbSet.Update(obj).State = EntityState.Modified;
//dbSet.Update(obj);
//dbSet.Local.FirstOrDefault(i => i.Id == obj.Id)
}
catch (Exception exc) { throw exc; }
}
public async Task SaveAsync()
{
try
{
await context.SaveChangesAsync();
}
catch (Exception exc)
{
throw exc;
}
}
public virtual IQueryable<T> Where(Expression<Func<T, bool>> predicate)
{
lock (_lock)
return dbSet.Where(predicate);
}
//public virtual IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
//{
// return dbSet.Where(predicate);
//}
}
}
Data saving method occurs at a higher level (service level). In the View user updates data (for example, adjusts the property one of the rows of the DataGrid), then in a context binding to this View, specifically in the ViewModel, calls the binding property (by INotifyPropertyChanged), which initiates call of the method that is responsible for the CRUD operation, then this method (On_bt_CategoryUpdate_Command) calling method from private field of genericService which is responsible for updating data in the database.
public class MainViewModel : ViewModel
{
#region Fields
private IGenericService<ContractDTO, int> _contractService;
#endregion
#region Commands
#endregion
#region Properties
private object GetList;
public object _GetList { get => GetList; private set => Set(ref GetList, value); } //Property to initialize DataGrid
private ContractDTO _contractDTO;
public ContractDTO SelectedItem { get => _contractDTO; set => Set(ref _contractDTO, value); } //Property with the data of the selected row in the DataGrid
#endregion
public SpesTechViewModel(IGenericService<ContractDTO, int> contractService)
{
_contractService = contractService;
}
#region Commands function
private bool Can_bt_CategoryUpdate_Command() => true;
private void On_bt_CategoryUpdate_Command()
{
try
{
_contractService.UpdateAsync(SelectedItem); //Method that updates and saves the data in the database
}
catch (Exception exc) { MessageBox.Show(exc.Message); }
}
#endregion
#region Function
void GetByFilter<T>(IFilterModel<T> filter, IGenericService<T, int> service) where T : class, new()
{
lock (_lock)
_GetList = service.Where(filter.Predicate()).Result.ToObservableCollection();
}
#endregion
}
_contractService.UpdateAsync(SelectedItem):
public async Task<DbObjectDTO> UpdateAsync(DbObjectDTO obj)
{
try
{
DbObject dbObject = mapper.Map<DbObject>(obj);
repository.Update(dbObject);
await repository.SaveAsync();
return mapper.Map<DbObjectDTO>(dbObject);
}
catch (Exception exc) { throw exc; }
}
the first thing in this method: using AutoMapper, the DTO object is converted into a DAL object; second: data is update using the repository layer (code above, public void Update(T obj) method); third: saving to the database; fourth: reverse mapping.
If I'm trying to do smth like this in the Update(T obj) method of GenericRepository<T, TKey>:
public void Update(T obj)
{
try
{
dbSet.Update(obj);
//dbSet.Local.FirstOrDefault(i => i.Id == obj.Id)
}
catch (Exception exc) { throw exc; }
}
On the second update of the same Entity in dbSet with a new changes of T obj I get an exception:
{"The instance of entity type 'Contract' 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."}
uj5u.com熱心網友回復:
與本地快取如此緊密地耦合是不尋常的。正常模式是呼叫DbSet.Find(id)并更新它回傳的物體的屬性。
var entity = await dbSet.FindAsync(new object[] { id }, cancellationToken);
var entry = context.Entry(entity);
entry.CurrentValues.SetValues(obj);
await context.SaveChangesAsync(cancellationToken);
與您正在執行的操作相比,此模式有一些好處:
- 如果不存在具有該 ID 的物體,您有機會通知呼叫者
- 物體框架處理快取
- Entity Framework 只更新實際改變的屬性
如果物體不在快取中,那么 Entity Framework 確實必須轉到資料庫。在某些情況下,例如,如果您的物體具有較大的二進制屬性并且您計劃用新值覆寫它,您可能希望避免從資料庫加載現有值而只是跳到更新部分。
var local = dbSet.Local.FirstOrDefault(i => i.Id == obj.Id);
if (local != null)
{
var entry = context.Entry(local);
entry.CurrentValues.SetValues(obj);
}
else
{
var entry = dbSet.Attach(obj);
entry.State = EntityState.Modified;
}
await context.SaveChangesAsync(cancellationToken);
這是一般用例的一個例外,我建議您在大多數情況下遵循正常模式,并且僅在性能要求時才使用它。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/426628.html
