EntityFramework不会识别手动进行的实体更改

时间:2016-03-01 22:40:31

标签: c# entity-framework entity-framework-6

我手动更改实体,之后我尝试验证DbContext中是否有与我的更改匹配的实体。 "答案"我期待的是" true"但是它" false"。 由于我的代码非常复杂并且有很多规则,我创建了一个简单的例子来尝试解释这个问题:

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;

&#34; ApplyChanges &#34;方法:

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();
}

我相信这种情况正在发生,因为当我查询实体时,我在数据库中查询它们而不是实体框架&#34;缓存&#34;。

但是当我通过使用IQueryable扩展方法(例如&#34; 任何&#34;和&#)查询DbContext中的实体时,有没有办法强制EntityFramework识别更改? 34; FirstOrDefault &#34;方法)?

3 个答案:

答案 0 :(得分:4)

你是对的。当您使用&#39; Any&#39;,&#39; FirstOrDefault&#39;或者使用查找SQL查询数据的任何其他Linq扩展方法。因此,除非您调用&#39; SaveChanges&#39;

,否则不会看到对象的任何更改(用于过滤目的)。

有一种方法可以查看物化对象,但您必须手动执行此操作。您必须仅对物化对象进行Linq-to-Objects查询,以查看是否存在所需内容。然后,如果不是,则进行常规的Linq-to-Entities查询在数据库中搜索它。不要混淆这些查询,否则你可能会释​​放地狱。

搜索物化对象:

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

context.ChangeTracker.Entries(); // everything

答案 1 :(得分: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

这不会返回数据库中的记录,因为数据库还没有新数据 - 在MyProperty1 IS NOT NULL中,ID为13的数据库中没有保存记录。这是因为您尚未调用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

数据库 具有ID为13的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(); 
    }
}

答案 2 :(得分:0)

在与同事交谈之后,我们决定采用@CodeThug的第一个建议做类似的事情。因此,我将使用“Linq to entities”更改查询“MyEntity”的代码点,以实现实体:

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