如何在SaveChanges前搜索未持久化的Signature实体?
解决EF Core中SaveChanges前检索内存中未提交实体的问题
我完全理解你的困扰:在同一个Edit请求里多次调用TryUpdateSignature时,第一次创建的Signature实体还只存在于EF上下文的内存中,没通过SaveChanges持久化到数据库,第二次查询时因为默认的FirstOrDefaultAsync只会去数据库查找,找不到内存里的实体,就会重复创建。你不想每次添加后就调用SaveChanges,这确实很合理——频繁提交会破坏事务性,也影响性能。
下面给你几个可行的解决方案:
方案1:结合Local集合与数据库查询
EF Core的DbSet<T>.Local属性包含了所有被上下文跟踪但尚未提交到数据库的实体。我们可以先从本地集合查找,找不到再去数据库查询,这样就能覆盖内存中的未提交实体。
修改你的查询逻辑部分:
// 先从本地跟踪的实体中查找匹配项 var recSignature = db.Signature.Local.FirstOrDefault(s => s.UserID == userName && s.DateSign == newDate); // 本地找不到再查询数据库 if (recSignature == null) { recSignature = await db.Signature.FirstOrDefaultAsync(s => s.UserID == userName && s.DateSign == newDate); }
这样修改后,第二次调用方法时,就能找到第一次添加到上下文的Signature实体,不会重复创建了。
方案2:维护本地查找字典(高频调用场景更高效)
如果这个方法会被频繁调用,或者你需要更高效的查找,可以在上下文级别或者方法内部维护一个以(UserID, DateSign)为键的字典,用来缓存已创建但未提交的实体:
// 可以把这个字典放在MyDbContext的扩展属性里,或者作为方法的静态变量 private static readonly Dictionary<(string UserID, Date? DateSign), Signature> _pendingSignatures = new Dictionary<(string, Date?), Signature>(); public static async Task<int?> TryUpdateSignature(MyDbContext db, Signature oldSignUser, Date? newDate, string userName) { int? SignatureID = null; if ((oldSignUser == null && newDate != null) || (oldSignUser != null && oldSignUser.DateSign != newDate)) { var key = (userName, newDate); // 先查本地字典 if (_pendingSignatures.TryGetValue(key, out var recSignature)) { SignatureID = recSignature.SignatureID; } else { // 再查Local集合 recSignature = db.Signature.Local.FirstOrDefault(s => s.UserID == userName && s.DateSign == newDate); if (recSignature == null) { // 最后查数据库 recSignature = await db.Signature.FirstOrDefaultAsync(s => s.UserID == userName && s.DateSign == newDate); if (recSignature == null) { recSignature = new Signature(); recSignature.UserID = userName; recSignature.DateSign = newDate; db.Signature.Add(recSignature); // 添加到本地字典 _pendingSignatures.Add(key, recSignature); } } SignatureID = recSignature.SignatureID; } } else if (oldSignUser != null && newDate != null) { SignatureID = oldSignUser.SignatureID; } // 注意:在SaveChanges后需要清空字典,避免内存泄漏 // 可以在MyDbContext的SaveChangesAsync方法里添加清空逻辑 return SignatureID; }
这个方案的优势是查找更快,尤其在多次调用的场景下,但需要注意在SaveChanges后清空字典,避免内存泄漏或者缓存过期的问题。
方案3:封装扩展方法(复用性更强)
你可以把“查本地+查数据库”的逻辑封装成一个扩展方法,方便在其他地方复用:
public static async Task<TEntity> FirstOrDefaultLocalOrDatabaseAsync<TEntity>(this DbSet<TEntity> dbSet, Expression<Func<TEntity, bool>> predicate) where TEntity : class { // 先查本地 var localMatch = dbSet.Local.FirstOrDefault(predicate.Compile()); if (localMatch != null) { return localMatch; } // 再查数据库 return await dbSet.FirstOrDefaultAsync(predicate); }
然后在你的方法里直接使用:
var recSignature = await db.Signature.FirstOrDefaultLocalOrDatabaseAsync(s => s.UserID == userName && s.DateSign == newDate);
这样代码会更简洁,也更容易维护。
另外提个小细节:你的代码里用了IsNothing,这是VB的语法,在C#里应该改成== null哦,避免语法错误。
内容的提问来源于stack exchange,提问作者Disman38
相关产品推荐
相关产品推荐

