【问题标题】:Entity changes made manually are not being identified by EntityFrameworkEntityFramework 无法识别手动进行的实体更改
【发布时间】:2016-03-03 01:12:40
【问题描述】:

我正在手动更改一个实体,然后我试图验证我的 DbContext 中是否有任何实体与我的更改相匹配。我期望的“答案”是“真”,但它是“假”。 由于我的代码非常复杂并且有很多规则,我创建了一个简单的示例来尝试解释问题:

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“缓存”中查询。

但是当我使用 IQueryable 扩展方法(例如“Any”和“FirstOrDefault”查询 DbContext 中的实体时,有没有办法强制 EntityFramework 识别更改>”方法)?

【问题讨论】:

  • 仔细阅读后,情况很简单,您有一个实体实例没有被实体框架跟踪。或 STE,在 EF6 上不再支持它

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


【解决方案1】:

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

有一种方法可以查看物化对象,但您必须手动完成。您必须仅对物化对象进行 Linq-to-Objects 查询,以查看您想要的是否存在。然后,如果不是,请进行常规 Linq-to-Entities 查询,在数据库中搜索它。请勿混合使用这些查询,否则您可能会陷入困境。

搜索物化对象:

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

context.ChangeTracker.Entries(); // everything

【讨论】:

  • 谢谢@Doug。我可以尝试使用此解决方法,但唯一的问题是在很多查询中都必须这样做,因为我的流程是: - 首先我手动更改主实体及其关联实体。 - 然后我调用一个“ValidateSave”方法,该方法有很多规则和许多直接在数据库中进行的查询。 - 最后,如果没有验证错误,我调用“db.SaveChanges”。更糟糕的是,我需要为不止一种实体这样做,并且每种实体都有一个“ValidateSave”方法。
  • 有谁知道是否有办法自动,通过使用事务或类似的东西?虽然我认为事务不是一个好主意,因为该操作可能需要一段时间,并因此锁定其他数据库操作。
【解决方案2】:

让我们看看前两条语句:

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

数据库确实有一个 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(); 
    }
}

【讨论】:

  • 非常感谢您的解释。我已经和我的同事谈过了,我们决定对您的第一个建议做类似的事情,即通过其 ID 获取实体,然后检查其“MyProperty1”。
【解决方案3】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-28
    • 1970-01-01
    • 1970-01-01
    • 2012-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多