You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:35:03