我手动更改实体,之后我尝试验证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;方法)?
答案 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;