Rij*_*rdt 35 c# asp.net entity-framework
我有一个服务对象 Update
public bool Update(object original, object modified)
{
var originalClient = (Client)original;
var modifiedClient = (Client)modified;
_context.Clients.Update(originalClient); //<-- throws the error
_context.SaveChanges();
//Variance checking and logging of changes between the modified and original
}
Run Code Online (Sandbox Code Playgroud)
这是我从以下方法调用此方法的地方:
public IActionResult Update(DetailViewModel vm)
{
var originalClient = (Client)_service.GetAsNoTracking(vm.ClientId);
var modifiedClient = (Client)_service.Fetch(vm.ClientId.ToString());
// Changing the modifiedClient here
_service.Update(originalClient, modifiedClient);
}
Run Code Online (Sandbox Code Playgroud)
这是GetAsNotTracking方法:
public Client GetAsNoTracking(long id)
{
return GetClientQueryableObject(id).AsNoTracking().FirstOrDefault();
}
Run Code Online (Sandbox Code Playgroud)
Fetch 方法:
public object Fetch(string id)
{
long fetchId;
long.TryParse(id, out fetchId);
return GetClientQueryableObject(fetchId).FirstOrDefault();
}
Run Code Online (Sandbox Code Playgroud)
GetClientQueryableObject:
private Microsoft.Data.Entity.Query.IIncludableQueryable<Client, ActivityType> GetClientQueryableObject(long searchId)
{
return _context.Clients
.Where(x => x.Id == searchId)
.Include(x => x.Opportunities)
.ThenInclude(x => x.BusinessUnit)
.Include(x => x.Opportunities)
.ThenInclude(x => x.Probability)
.Include(x => x.Industry)
.Include(x => x.Activities)
.ThenInclude(x => x.User)
.Include(x => x.Activities)
.ThenInclude(x => x.ActivityType);
}
Run Code Online (Sandbox Code Playgroud)
有任何想法吗?
我看过以下文章/讨论.无济于事:ASP.NET GitHub问题3839
更新:
以下是对以下内容的更改GetAsNoTracking:
public Client GetAsNoTracking(long id)
{
return GetClientQueryableObjectAsNoTracking(id).FirstOrDefault();
}
Run Code Online (Sandbox Code Playgroud)
GetClientQueryableObjectAsNoTracking:
private IQueryable<Client> GetClientQueryableObjectAsNoTracking(long searchId)
{
return _context.Clients
.Where(x => x.Id == searchId)
.Include(x => x.Opportunities)
.ThenInclude(x => x.BusinessUnit)
.AsNoTracking()
.Include(x => x.Opportunities)
.ThenInclude(x => x.Probability)
.AsNoTracking()
.Include(x => x.Industry)
.AsNoTracking()
.Include(x => x.Activities)
.ThenInclude(x => x.User)
.AsNoTracking()
.Include(x => x.Activities)
.ThenInclude(x => x.ActivityType)
.AsNoTracking();
}
Run Code Online (Sandbox Code Playgroud)
sab*_*sab 51
对我来说这只是解决了问题。在任何更新之前添加此代码
_context.ChangeTracker.Clear()
Run Code Online (Sandbox Code Playgroud)
来自微软文档
停止跟踪所有当前跟踪的实体。
DbContext 被设计为具有较短的生命周期,其中为每个工作单元创建一个新实例。这种方式意味着当上下文在每个工作单元结束时被处理时,所有跟踪的实体都将被丢弃。但是,在创建新上下文实例不切实际的情况下,使用此方法清除所有跟踪的实体可能会很有用。
这种方法应该始终优先于分离每个被跟踪的实体。分离实体是一个缓慢的过程,可能会产生副作用。此方法在从上下文中清除所有跟踪的实体方面更加有效。
请注意,此方法不会生成 StateChanged 事件,因为实体不是单独分离的。
更新
更新
最好不要调用该Update()方法。只需在查询需要修改的对象时使用跟踪功能,然后调用SaveChanges()方法,该方法将以优化的方式仅更新已编辑的字段。这就是 EfCore 跟踪的工作原理。如果使用得当,跟踪是一个很好的功能。
如果您的项目中或由于任何其他原因禁用了跟踪,则使用我创建的方法。它是针对 EFCore 更新对象的优化解决方案。调用它而不是Update()方法DbContext,它将检查对象是否被跟踪。如果它被跟踪,那么它将什么也不做。如果对象未被跟踪,那么它将手动执行跟踪工作。只需调用SaveChanges()之后的方法,它将创建优化的 SQL 语句,该语句将仅更新已更改的字段。如果您想为应用程序创建审核功能以轻松跟踪所有数据每行的更改,这也将为您提供帮助。
public void UpdateIfNoTracking(TEntity entityToUpdate) where TEntity : class
{
var keys = GetPrimaryKeys(context, entityToUpdate);
bool tracked = context.Entry(entityToUpdate).State != EntityState.Detached;
if (tracked)
return;
if (keys != null)
{
var oldValues = context.Set<TEntity>().Find(keys);
context.Entry(oldValues).CurrentValues.SetValues(entityToUpdate);
}
else
{
context.Set<TEntity>().Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
Run Code Online (Sandbox Code Playgroud)
我在 Github 上发布了完整的解决方案库:Solid.DataAccess,它很好地应用了工作单元和存储库模式。我在我所有的项目中都使用它,它的作用就像魅力一样。请随意克隆并为使其变得更好做出贡献。
GetPrimaryKeys()方法用于动态获取对象的所有主键值,因为我使用的是通用对象。当对实体使用复合主键或多个主键时,这很有帮助。
private static object[] GetPrimaryKeys<T>(DbContext context, T value)
{
var keyNames = context.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties
.Select(x => x.Name).ToArray();
var result = new object[keyNames.Length];
for (int i = 0; i < keyNames.Length; i++)
{
result[i] = typeof(T).GetProperty(keyNames[i])?.GetValue(value);
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
And*_*era 34
如果不覆盖EF轨道系统,您还可以在保存之前分离"本地"条目并附加更新的条目:
//
var local = _context.Set<YourEntity>()
.Local
.FirstOrDefault(entry => entry.Id.Equals(entryId));
// check if local is not null
if (!local.IsNull()) // I'm using a extension method
{
// detach
_context.Entry(local).State = EntityState.Detached;
}
// set Modified flag in your entry
_context.Entry(entryToUpdate).State = EntityState.Modified;
// save
_context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)
更新: 为避免代码冗余,您可以执行扩展方法:
public static void DetachLocal<T>(this DbContext context, T t, string entryId)
where T : class, IIdentifier
{
var local = context.Set<T>()
.Local
.FirstOrDefault(entry => entry.Id.Equals(entryId));
if (!local.IsNull())
{
context.Entry(local).State = EntityState.Detached;
}
context.Entry(t).State = EntityState.Modified;
}
Run Code Online (Sandbox Code Playgroud)
我的IIdentifier界面只有一个Id字符串属性.
无论您的实体是什么,您都可以在上下文中使用此方法:
_context.DetachLocal(tmodel, id);
_context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)
SHU*_*ATA 24
public async Task<Product> GetValue(int id)
{
Product Products = await _context.Products.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
return Products;
}
Run Code Online (Sandbox Code Playgroud)
AsNoTracking()
我在设置 xUnit 测试时遇到了同样的问题(EF Core)。在测试中为我“修复”的是在设置种子数据后循环遍历更改跟踪器实体。
我设置了一个测试模拟上下文:
/// <summary>
/// Get an In memory version of the app db context with some seeded data
/// </summary>
public static AppDbContext GetAppDbContext(string dbName)
{
//set up the options to use for this dbcontext
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(databaseName: dbName)
//.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
.Options;
var dbContext = new AppDbContext(options);
dbContext.SeedAppDbContext();
return dbContext;
}
Run Code Online (Sandbox Code Playgroud)
添加一些种子数据的扩展方法:
foreach方法底部的循环中分离实体。 public static void SeedAppDbContext(this AppDbContext appDbContext)
{
// add companies
var c1 = new Company() { Id = 1, CompanyName = "Fake Company One", ContactPersonName = "Contact one", eMail = "one@caomp1.com", Phone = "0123456789", AdminUserId = "" };
c1.Address = new Address() { Id = 1, AddressL1 = "Field Farm", AddressL2 = "Some Lane", City = "some city", PostalCode = "AB12 3CD" };
appDbContext.CompanyRecords.Add(c1);
var nc1 = new Company() { Id = 2, CompanyName = "Test Company 2", ContactPersonName = "Contact two", eMail = "two@comp2.com", Phone = "0123456789", Address = new Address() { }, AdminUserId = "" };
nc1.Address = new Address() { Id = 2, AddressL1 = "The Barn", AddressL2 = "Some Lane", City = "some city", PostalCode = "AB12 3CD" };
appDbContext.CompanyRecords.Add(nc1);
//....and so on....
//last call to commit everything to the memory db
appDbContext.SaveChanges();
//and then to detach everything
foreach (var entity in appDbContext.ChangeTracker.Entries())
{
entity.State = EntityState.Detached;
}
}
Run Code Online (Sandbox Code Playgroud)
控制器 put 方法
该.ConvertTo<>()方法是从一个扩展方法ServiceStack
[HttpPut]
public async Task<IActionResult> PutUpdateCompany(CompanyFullDto company)
{
if (0 == company.Id)
return BadRequest();
try
{
Company editEntity = company.ConvertTo<Company>();
//Prior to detaching an error thrown on line below (another instance with id)
var trackedEntity = _appDbContext.CompanyRecords.Update(editEntity);
await _appDbContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException dbError)
{
if (!CompanyExists(company.Id))
return NotFound();
else
return BadRequest(dbError);
}
catch (Exception Error)
{
return BadRequest(Error);
}
return Ok();
}
Run Code Online (Sandbox Code Playgroud)
和测试:
[Fact]
public async Task PassWhenEditingCompany()
{
var _appDbContext = AppDbContextMocker.GetAppDbContext(nameof(CompaniesController));
var _controller = new CompaniesController(null, _appDbContext);
//Arrange
const string companyName = "Fake Company One";
const string contactPerson = "Contact one";
const string newCompanyName = "New Fake Company One";
const string newContactPersonName = "New Contact Person";
//Act
var getResult = _controller.GetCompanyById(1);
var getEntity = (getResult.Result.Result as OkObjectResult).Value;
var entityDto = getEntity as CompanyFullDto;
//Assert
Assert.Equal(companyName, entityDto.CompanyName);
Assert.Equal(contactPerson, entityDto.ContactPersonName);
Assert.Equal(1, entityDto.Id);
//Arrange
Company entity = entityDto.ConvertTo<Company>();
entity.CompanyName = newCompanyName;
entity.ContactPersonName = newContactPersonName;
CompanyFullDto entityDtoUpd = entity.ConvertTo<CompanyFullDto>();
//Act
var result = await _controller.PutUpdateCompany(entityDtoUpd) as StatusCodeResult;
//Assert
Assert.True(result.StatusCode == 200);
//Act
getResult = _controller.GetCompanyById(1);
getEntity = (getResult.Result.Result as OkObjectResult).Value;
entityDto = getEntity as CompanyFullDto;
//Assert
Assert.Equal(1, entityDto.Id); // didn't add a new record
Assert.Equal(newCompanyName, entityDto.CompanyName); //updated the name
Assert.Equal(newContactPersonName, entityDto.ContactPersonName); //updated the contact
//make sure to dispose of the _appDbContext otherwise running the full test will fail.
_appDbContext.Dispose();
}
Run Code Online (Sandbox Code Playgroud)
听起来您确实只是想跟踪对模型所做的更改,而不是真正将未跟踪的模型保留在内存中。我是否可以建议完全替代该问题的替代方法?
EF会自动为您跟踪更改。如何利用内置逻辑?
Ovverride SaveChanges()在你的DbContext。
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<Client>())
{
if (entry.State == EntityState.Modified)
{
// Get the changed values.
var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).GetModifiedProperties();
var currentValues = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).CurrentValues;
foreach (var propName in modifiedProps)
{
var newValue = currentValues[propName];
//log changes
}
}
}
return base.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)
很好的例子可以在这里找到:
编辑:
Client可以轻松更改为界面。比方说ITrackableEntity。这样,您可以集中逻辑并自动将所有更改记录到实现特定接口的所有实体。接口本身没有任何特定的属性。
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<ITrackableClient>())
{
if (entry.State == EntityState.Modified)
{
// Same code as example above.
}
}
return base.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)
另外,看看eranga的很棒的订阅建议,而不是实际覆盖SaveChanges()。
小智 5
对我来说,我在使用 AutoMapper 和 .NET 6 时遇到了这个问题。为了解决这个问题,我将代码更改为:
DbItem? result = await _dbContext.DbItems.FirstOrDefaultAsync(t => t.Id == id);
if (result == null)
{
return null;
}
DbItem mappedItem = _mapper.Map<DbItem>(dto); //problematic line
var updatedItem = _dbContext.DbItems.Update(mappedItem);
Run Code Online (Sandbox Code Playgroud)
到:
DbItem? result = await _dbContext.DbItems.FirstOrDefaultAsync(t => t.Id == id);
if (result == null)
{
return null;
}
_mapper.Map(dto, result); //the fix
var updatedItem = _dbContext.DbItems.Update(result);
Run Code Online (Sandbox Code Playgroud)
有问题的行创建了一个具有相同键值的新 DbItem,从而导致了问题。修复线将 DTO 中的字段映射到原始 DbItem。
小智 5
也有这个问题..通过在保存之前先取消跟踪旧实体来解决它。
public async Task<int> Update<T>(T entity) where T : BaseEntity
{
entity.UpdatedAt = DateTime.UtcNow;
// Untrack previous entity version
var trackedEntity = this.context.Set<T>()
.SingleOrDefaultAsync(e => e.Id == entity.Id);
this.context.Entry<T>(await trackedEntity).State = EntityState.Detached;
// Track new version
this.context.Set<T>().Attach(entity);
this.context.Entry<T>(entity).State = EntityState.Modified;
await this.context.SaveChangesAsync();
return entity.Id;
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
47356 次 |
| 最近记录: |