2016-03-01 22 views
4

我正在手动更改实体,之后我试图验证在我的DbContext中是否有与我的更改匹配的实体。我预期的“答案”是“真实的”,但它是“错误的”。 由于我的代码是非常复杂的,有许多规则,我创建了一个简单的例子来试图解释这个问题:手动进行的实体更改未被EntityFramework识别

var propertyValues = new Dictionary<string, object>() 
{ 
    {"MyProperty1", "My value"}, 
    {"MyProperty2", 10} 
}; 

var entityId = 13; 
var entityType = typeof(MyEntity); 

// Applies the changes 
this.ApplyChanges(db, entityType, entityId, propertyValues); 


// This is "false" 
var hasEntityWithValue = db.MyEntity.Any(p => p.Id == entityId && p.MyProperty1 != null); 

// The "myEntity" is null 
var myEntity = db.MyEntity.FirstOrDefault(p => p.Id == entityId && p.MyProperty1 != null); 

// Gets the entity only by Id 
myEntity = db.MyEntity.FirstOrDefault(p => p.Id == entityId); 

// And when I compare the "MyProperty1" it's "true". Why????? 
hasEntityWithValue = myEntity.MyProperty1 != null; 

的“ApplyChanges”的方法:

private void ApplyChanges(DbContext db, Type entityType, int entityId, 
Dictionary<string, object> propertyValues) 
{ 
    var entity = db.Set(entityType).Find(entityId); 
    foreach (var propertyValue in propertyValues) 
    { 
     var propertyInfo = entityType.GetProperty(propertyValue.Key); 

     // Sets the value 
     propertyInfo.SetValue(entity, propertyValue.Value); 
    } 

    db.ChangeTracker.DetectChanges(); 
} 

我相信这正在发生,因为当我查询实体时,我正在数据库中查询它们而不是EntityFramework“缓存”。

但是,有没有办法强制的EntityFramework使用IQueryable的扩展方法(如“任何”和“FirstOrDefault”方法)找出发生的变化,当我查询中的DbContext的实体?

+1

更仔细地阅读后,情况很简单,你有一个实体实例不是由实体框架跟踪。或STE,它不再支持EF6 – mijail

回答

1

我们先来看:

要搜索的物化对象

var entityId = 13; 
... 

// This is "false" 
var hasEntityWithValue = db.MyEntity.Any(p => p.Id == entityId && p.MyProperty1 != null); 

// The "myEntity" is null 
var myEntity = db.MyEntity.FirstOrDefault(p => p.Id == entityId && p.MyProperty1 != null); 

这两个发送相同的数据库查询:

SELECT * FROM MyEntities WHERE ID = 13 AND MyProperty1 IS NOT NULL 

由于数据库还没有新数据,因此不会返回数据库中的任何记录 - 数据库中没有保存记录的ID为13的记录,其中MyProperty1 IS NOT NULL。这是因为您尚未拨打db.SaveChanges()。第一条语句将该SQL语句的结果转换为值false,而第二条语句将其转换为值null

移动到下一个声明:

// Gets the entity only by Id 
myEntity = db.MyEntity.FirstOrDefault(p => p.Id == entityId); 

这将查询发送到数据库这样的:

SELECT * FROM MyEntities WHERE ID = 13 

数据库有13个ID的MyEntitiy,和它将MyEntity返回给EF。但是,在EF向您返回MyEntity之前,EF会检查其缓存中是否具有ID为13的MyEntity。它有一个ID为13的缓存的MyEntity,因此它发送缓存的MyEntity。而缓存的MyEntity恰好是您在致电自定义ApplyChanges方法时更新的那个。

// And when I compare the "MyProperty1" it's "true". Why????? 
hasEntityWithValue = myEntity.MyProperty1 != null; 

这是真的原因是因为返回给您的实体是在EF缓存中的实体。

当您使用EF进行查询时,它会将查询发送到数据库,并且如果从数据库返回记录,EF将检查它的缓存以查看具有相同密钥的记录是否在缓存中。如果它们已存在于缓存中,则即使缓存的记录与数据库记录不同,缓存的记录也会被返回以代替在数据库中找到的记录。 (有关如何解决此缓存的更多信息,请参阅http://codethug.com/2016/02/19/Entity-Framework-Cache-Busting/

缓存检查在查询运行时完成。所以,你可以拨打这个电话,你会看到更新后的数据:

var data = db.MyEntity 
    .Where(p => p.Id == entityId) 
    .ToList() 
    .Where(p => p.MyProperty1 != null); 

第一Where功能是由数据库处理。第二个是在你的C#代码运行的内存中处理的。 ToList调用强制将迄今为止构建的查询发送到数据库,并在执行更多筛选或排序之前运行。


您也可以为此使用一个事务,但正如您所提到的那样,这将锁定事务期间的资源。假设你有EF6工作,你可以这样做:

using (var transaction = db.Database.BeginTransaction()) 
{ 
    // Applies the changes 
    this.ApplyChanges(db, entityType, entityId, propertyValues); 

    db.SaveChanges();  

    // Should be true 
    var hasEntityWithValue = db.MyEntity.Any(p => p.Id == entityId && p.MyProperty1!=null); 

    // At this point, queries to the database will see the updates you made 
    // in the ApplyChanges method 
    var isValid = ValidateSave(); 

    if (isValid) 
    { 
     // Assuming no more changes were made since you called db.SaveChanges() 
     transaction .Commit(); 
    } 
    else 
    { 
     transaction .Rollback(); 
    } 
} 
+0

非常感谢您的解释。我已经和同事谈过了,我们决定做一些类似于你的第一个建议,那就是通过它的Id获取实体,然后检查它的“MyProperty1”。 –

4

你是对的。当您使用'Any','FirstOrDefault'或任何其他使用SQL查询查找数据的Linq扩展方法时。因此,除非调用“SaveChanges”,否则不会看到对象的任何更改(用于过滤目的)。

有一种方法可以查看物化对象,但您必须手动执行。你只能对物化对象进行Linq-to-Objects查询,看看你想要的是否在那里。然后,如果不是,则在数据库中定期执行Linq-to-Entities查询。不要混合这些查询,否则你可能会释​​放地狱。在前两个语句

context.ChangeTracker.Entries<MY_ENTITY>(); // strongly-typed, just an objects set 

context.ChangeTracker.Entries(); // everything 
+1

谢谢@Doug。我可以尝试使用这种解决方法,但唯一的问题是在很多查询中这样做是必要的,因为我的流程是: - 首先我手动更改主实体及其关联实体。 - 然后我调用一个“** ValidateSave **”方法,它有很多规则,许多查询直接在数据库中进行。 - 最后,如果没有验证错误,我将调用“** db.SaveChanges **”。 更糟糕的是,我需要为多种类型的实体执行此操作,并且每种实体都有一个“ValidateSave”方法。 –

+1

有谁知道是否有办法做到这一点**自动**,通过使用交易或类似的东西?虽然我认为交易不是一个好主意,因为操作可能需要一段时间,并因此锁定其他数据库操作。 –

0

之后,我跟我的同事,我们决定做与@CodeThug的第一个建议类似的东西。所以我要改变我的代码,查询“myEntity所”使用“LINQ到实体”兑现实体几点:

myEntity = db.MyEntity.First(p => p.Id == entityId); 
var hasEntityWithValue = myEntity.MyProperty1 != null;