我有一个服务对象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
}
这是我从以下地方调用此方法的地方:
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);
}
以下是GetAsNotTracking
方法:
public Client GetAsNoTracking(long id)
{
return GetClientQueryableObject(id).AsNoTracking().FirstOrDefault();
}
Fetch
方法:
public object Fetch(string id)
{
long fetchId;
long.TryParse(id, out fetchId);
return GetClientQueryableObject(fetchId).FirstOrDefault();
}
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);
}
有什么想法吗?
我看过以下文章/讨论。无济于事:ASP.NET GitHub Issue 3839
更新
以下是GetAsNoTracking
的更改:
public Client GetAsNoTracking(long id)
{
return GetClientQueryableObjectAsNoTracking(id).FirstOrDefault();
}
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();
}
答案 0 :(得分:18)
如果不覆盖EF跟踪系统,您也可以分离本地&#39;输入并在保存之前附上您更新的条目:
//
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();
<强>更新强> 为避免代码冗余,您可以执行扩展方法:
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;
}
我的IIdentifier
界面只有一个Id
字符串属性。
无论您的实体是什么,您都可以在上下文中使用此方法:
_context.DetachLocal(tmodel, id);
_context.SaveChanges();
答案 1 :(得分:5)
听起来你真的只是想跟踪对模型所做的更改,而不是在内存中实际保留未跟踪的模型。我是否可以建议一种替代方法,将彻底解决问题?
EF会自动跟踪您的变化。如何利用内置的逻辑?
SaveChanges()
中的Ovverride 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();
}
可以在这里找到很好的例子:
Entity Framework 6: audit/track changes
Implementing Audit Log / Change History with MVC & Entity Framework
修改强>
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();
}
另外,请查看eranga订阅的好建议,而不是实际覆盖SaveChanges()。
答案 2 :(得分:5)
设置xUnit测试时,我遇到了同样的问题(EF内核)。 在测试中,对我来说“修复”的是设置种子数据后遍历变更跟踪器实体。
我设置了一个测试模拟环境:
/// <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;
}
扩展方法以添加一些种子数据 :
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;
}
}
控制器放置方法
.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();
}
和测试:
[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();
}
答案 3 :(得分:4)
public async Task<Product> GetValue(int id)
{
Product Products = await _context.Products.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
return Products;
}
AsNoTracking()
答案 4 :(得分:1)
在 EF 核心中 - 还要确保不要同时设置外键和外键导航属性。我在设置密钥和属性时遇到此错误。
例如
new VerificationAccount()
{
Account = konto_1630,
VerificationRowType = VerificationRowType.Template,
// REMOVED THE LINE BELOW AND THE ERROR WENT AWAY
//VerificationAccount = verificationAccounts.First(x => x.Account == konto_1630),
VerificationId = verificationId
}
答案 5 :(得分:0)
如果您的数据每隔更改一次,您会发现您没有跟踪表。例如,使用tigger的某些表更新ID([key])。如果进行跟踪,您将获得相同的ID并得到问题。
答案 6 :(得分:0)
就我而言,表的id列未设置为Identity列。
答案 7 :(得分:0)
嗯,这吸引了我,我花了很多时间对其进行故障排除。问题是我的测试是在Parellel(XUnit的默认设置)中执行的。
为使测试依次进行,我用以下属性装饰了每个类:
[Collection("Sequential")]
这就是我的解决方法: Execute unit tests serially (rather than in parallel)
我用GenFu模拟了我的EF In Memory上下文:
private void CreateTestData(TheContext dbContext)
{
GenFu.GenFu.Configure<Employee>()
.Fill(q => q.EmployeeId, 3);
var employee = GenFu.GenFu.ListOf<Employee>(1);
var id = 1;
GenFu.GenFu.Configure<Team>()
.Fill(p => p.TeamId, () => id++).Fill(q => q.CreatedById, 3).Fill(q => q.ModifiedById, 3);
var Teams = GenFu.GenFu.ListOf<Team>(20);
dbContext.Team.AddRange(Teams);
dbContext.SaveChanges();
}
在创建测试数据时,据我所知,它在两个范围内仍然存在(在团队测试运行时一次在员工的测试中):
public void Team_Index_should_return_valid_model()
{
using (var context = new TheContext(CreateNewContextOptions()))
{
//Arrange
CreateTestData(context);
var controller = new TeamController(context);
//Act
var actionResult = controller.Index();
//Assert
Assert.NotNull(actionResult);
Assert.True(actionResult.Result is ViewResult);
var model = ModelFromActionResult<List<Team>>((ActionResult)actionResult.Result);
Assert.Equal(20, model.Count);
}
}
使用此顺序收集属性包装这两个测试类已经清除了明显的冲突。
[Collection("Sequential")]
其他参考:
https://github.com/aspnet/EntityFrameworkCore/issues/7340
EF Core 2.1 In memory DB not updating records
http://www.jerriepelser.com/blog/unit-testing-aspnet5-entityframework7-inmemory-database/
http://gunnarpeipman.com/2017/04/aspnet-core-ef-inmemory/
https://github.com/aspnet/EntityFrameworkCore/issues/12459
Preventing tracking issues when using EF Core SqlLite in Unit Tests
答案 8 :(得分:0)
我遇到了同样的问题,但问题非常愚蠢,我错误地给了我错误的关系,给了我两个Id之间的关系。
答案 9 :(得分:0)
public static void DetachEntity<T>(this DbContext dbContext, T entity, string propertyName) where T: class, new()
{
try
{
var dbEntity = dbContext.Find<T>(entity.GetProperty(propertyName));
if (dbEntity != null)
dbContext.Entry(dbEntity).State = EntityState.Detached;
dbContext.Entry(entity).State = EntityState.Modified;
}
catch (Exception)
{
throw;
}
}
public static object GetProperty<T>(this T entity, string propertyName) where T : class, new()
{
try
{
Type type = entity.GetType();
PropertyInfo propertyInfo = type.GetProperty(propertyName);
object value = propertyInfo.GetValue(entity);
return value;
}
catch (Exception)
{
throw;
}
}
我做了这2种扩展方法,效果很好。
答案 10 :(得分:0)
我从后台服务中收到此错误。我解决了创建新范围的问题。
using (var scope = serviceProvider.CreateScope())
{
// Process
}
答案 11 :(得分:0)
无法更新数据库行。我面临同样的错误。现在使用以下代码:
_context.Entry(_SendGridSetting).CurrentValues.SetValues(vm);
await _context.SaveChangesAsync();
答案 12 :(得分:0)
如果您有重复的条目/实体并运行 SaveChanges(),则可能会出现此错误消息。
答案 13 :(得分:0)
我自己也遇到过这个问题。实体框架会跟踪您插入到数据库中的每个对象。因此,当您插入同一对象的重复记录并更改了几个字段时,EF 将通过此错误。我通过深度克隆我试图重新插入的对象来绕过它,它通过了。
Argument of type 'unknown' is not assignable to parameter of type 'SetStateAction<number | undefined>'.
Type 'unknown' is not assignable to type '(prevState: number | undefined) => number | undefined'.
然后:
public static T DeepClone<T>(this T a)
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, a);
stream.Position = 0;
return (T)formatter.Deserialize(stream);
}
}
答案 14 :(得分:0)
如果您设置了两个或多个带有 'Id' 的表或具有相同列名的列名,最简单的方法是更改上下文类中的 OnModelCreating 方法。
在这种情况下,我必须将“Id”更改为“AbandonedCartId”并告诉实体该对象具有列名称“Id”
entity.Property(e => e.AbandonedCartId).HasColumnName("Id");
示例
public partial class AbandonedCart
{
public int AbandonedCartId { get; set; }
public double? CheckoutId { get; set; }
public int? AppId { get; set; }
public double? CustomerId { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AbandonedCart>(entity =>
{
entity.Property(e => e.AbandonedCartId).HasColumnName("Id");
entity.Property(e => e.CreatedAt).HasColumnType("datetime");
entity.HasOne(d => d.App)
.WithMany(p => p.AbandonedCart)
.HasForeignKey(d => d.AppId)
.HasConstraintName("FK_AbandonedCart_Apps");
});
}