在 EF6 中重新加载导航属性

Posted

技术标签:

【中文标题】在 EF6 中重新加载导航属性【英文标题】:Reloading navigation properties in EF6 【发布时间】:2019-06-16 18:27:33 【问题描述】:

我已经阅读了很多关于使用 Context.Entry(entity).Collection(p=>p.Property).Load() 重新加载导航属性的 *** 问题,但在我的情况下,它没有给我数据库中的更新值。

LazyLoading 和 ProxyCreation 选项设置为其默认值,我已阅读它们默认为 ON。

我有一个实体 Test 对象,我使用以下方法从数据库中获取了所有相关属性的预加载:

var test = Repository.GetById(testId, null, true, new Expression<Func<Test,object>>[] 
    bt=>bt.Baselining,
    ct=>ct.Baselining.BaselineTestCase,
    dt=>dt.Baselining.BaselineTestCase.Baseline,
    ft=>ft.Baselining.Transaction,
    gt=>gt.Baselining.Transaction>Select(x=>x.Fields)
); 

public virtual T GetById<T>(int id, Func<T,bool> where = null, bool trackChanges = false, params Expression<Func<T,object>>[] includeProps) 

    T item = null;
    IQuerable<T> dbQuery = Context.Set<T>();

    if(includeProps != null)
        foreach(Expression<Func<T,object>> navProp in includeProps)
            dbQuery = dbQuery.Include<T,object>(navProp);

    if(where == null)
        
        if(!trackChanges) item = dbQuery.AsNoTracking().FirstOrDefault(t=>t.Id == id);
        else item = dbQuery.FirstOrDefault(t=>t.Id == id);
    
    else
    
        if(!trackChanges) item = dbQuery.AsNoTracking().Where(where).FirstOrDefault(t=>t.Id == id);
        else item = dbQuery.Where(where).FirstOrDefault(t=>t.Id == id);
    
    return item;

我正在尝试使用

重新加载 Baselining.Transaction.Fields
Repository.Reload(test);
Repository.Reload(test.Baselining.BaselineTestCase);
Repository.Reload(test.Baselining.BaselineTestCase.Baseline);
Repository.ReloadNavigationProperties(test.Baselining, x=>x.Transaction);
foreach(var tq in test.Baselining.Transaction)
    Repository.ReloadNavigationProperties(tq, x=>x.Fields);

存储库方法类似于:

public virtual void ReloadNavigationProperties(TEntity,TElement>(TEntity entity, Expression<Func<TEntity,ICollection<TElement>>> navProp) where TEntity : class where TElement : class

    Context.Entry(entity).Collection(navProp).Load();


public virtual T Reload<T>(T entity) where T : class 
    Context.Entry(entity).Reload();
    return entity;

我已经使用 SQL Profiler 调试了上述 ReloadNavigationProperties 执行,它确实会转到数据库并生成一个带有连接的 SQL 查询,但即使在重新加载后我的对象也没有更新。

在使用上述方法无法重新加载导航属性后,我采用了这种简单的方法,它有效...

foreach(var tq in test.Baselining.Transaction)
    foreach(var tqfs in tq.Fields) 
        Repository.Reload(tqfs);

因为我为每个 TransactionQuery 大约有 100 个 TransactionQueryFields,并且这个嵌套的 foreach 需要 100 次重新加载每个字段,而且它非常非常慢而且应该很慢......

这里是实体

public class Test 

    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int id get;set;
    public virtual Baselining Baselining get;set;
    public virtual ICollection<TestExecutionResult> TestCaseResults get;set; = new List<TestExecutionResult>();


public class Baselining 

    public Baselining() 
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int id get;set;
    public DateTime BaseliningDate get;set;
    public virtual BaselineTestCase BaselineTestCase get;set;
    public virtual ICollection<TransactionQuery> Transaction get;set; = new List<TransactionQuery>();


public class BaselineTestCase 

    public BaselineTestCase() 
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int id get;set;
    public virtual Baseline Baseline get;set;
    public virtual ICollection<Baselining> Baselinings get;set; = new List<Baselining>();
    public Baselining LastBaselining 
        get return Baselinings.OrderBy(x=>x.BaseliningDate).LastOrDefault(); 
    


public class TransactionQuery : TransactionItem 

    public TransactionQuery() 
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public override int id get;set;
    public virtual Baselining Baselining get;set;
    public virtual ICollection<TransactionQueryField> Transaction get;set; = new List<TransactionQueryField>();


public class TransactionQueryField : TransactionItem 

    public TransactionQueryField() 
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public override int id get;set;
    public bool Compare get;set;
    public bool Critical get;set;


public abstract class TransactionItem : BaseEntity<int> 
    public string TestResultCode get;set;


public abstract class BaseEntity<T> 
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public virtual T Id get;set;

我想了解为什么使用导航属性预先加载不会加载更新的数据。因为根据我的理解,如果我渴望加载任何东西,它应该往返于数据库并获取更新的数据。但即使是通过重新加载进行急切加载,它也不会为我提供更新的值。

如果我没有正确解释,我很抱歉,但我将不胜感激有关如何更快地加载我的字段的任何帮助,可能是一次数据库往返。

【问题讨论】:

【参考方案1】:

这只是一个假设,但我认为您的导航属性未重新加载的原因是跟踪实体。 我已经经历过类似的事情,关于Load 方法doc 说:

从数据库加载实体集合。请注意,实体 上下文中已经存在的不会被来自的值覆盖 数据库。

当您调用GetById 时,您将参数trackChanges 设置为true。从现在开始,所有预先加载的实体都会被跟踪。 正如我所说,我不确定这是否是问题,但您可以尝试将所有跟踪实体的状态设置为 EntityState.Detached

【讨论】:

我已经阅读了这篇文章,但关键是如果我不跟踪更改,那么我将无法注入我在 OnSave 和 OnLoad 期间配置的实体 Hook。所以我必须跟踪更改为真。虽然我已经尝试过 Object context.Refresh 并且它正在工作,但我很想知道用导航属性处理这种情况的正确方法是什么。 我从未使用过 EntityHooks 包,但文档对Load 方法非常清楚,除了使用EntityState 或@987654328 上的属性外,我想不出其他方法来克服这种情况@.

以上是关于在 EF6 中重新加载导航属性的主要内容,如果未能解决你的问题,请参考以下文章

EF6虚拟导航属性是否导致SQL查询?

Entity Framework Core 5 重新加载问题

如果在 WebView Flutter 中导航失败,则重新加载页面

使用 segue 向后导航时如何重新加载容器视图?

EF6(代码优先)单个外键属性上的多个导航属性

角度路由器导航然后重新加载