在 EF 的 POCO 中加载方法所需的导航属性

Load navigation properties necessary for a method in POCO in EF

请看下面我的问题的例子。

在我从 Calculate() 方法收到 result 变量后,EF 上下文被释放。如果我稍后在此 result 上调用 DoMethod(),我会收到错误消息,因为未加载 EF 导航 属性 SomeObjects

我可以想到以下解决方案来防止这个问题?

  1. Calculate() 方法中预先加载 SomeObjects (xyList = context.Xys.Include(x => x.SomeObjects).ToList();)(如果以后不使用,则不必要地加载此 属性)
  2. 不要关闭数据库上下文或使用全局上下文(非常糟糕!)
  3. DoMethod()
  4. 中加载缺少的 EF 导航 属性

我会选择第三个,因为 DoMethod() 并不总是被调用,因此如果不是,我不需要 SomeObjects

我的问题是如何实现第三种方案?这是正确的方法吗?从 POCO 中查询以获取必要的数据似乎有点奇怪。

class Program
{
  static void Main(string[] args)
  {
     ...
     Xy result = Calculation.Calculate();
     ...
     //Maybee this method is invoked
     result.DoMethod();
  }
}

// POCO class
public class XY
{
  public virtual List<Xz> SomeObjects { get; set; } 

  public void DoMethod()
  {
    foreach (var obj in SomeObjects)
    {
       ...
    }
  }
}

class Calculation
{
   public static Xy Calculate() {
      Xy result;
      using (var context = new MyContext())
      {
         xyList = context.Xys.ToList();
         ...
         result = xyList[calculatedIndex];
      }
      return result;
   }
}

以下是我尝试过或想到的一些选项。 #3 是对您指定的首选方法的一次尝试。


1.只在最后一刻计算。

这会产生每次需要结果时创建上下文的开销,但会推迟使用上下文直到需要结果为止。您的用例决定了这是否有帮助。

class Program
{
  static void Main(string[] args)
  {
     ...
     Calculation calc = new Calculation();
     ...
     //Maybe this method is invoked
     calc.GetResult().DoMethod();
  }
}

class Calculation
{      
    public Xy GetResult();
    {
      Xy result;
      using (var context = new MyContext())
      {
         xyList = context.Xys.ToList();
         ...
         result = xyList[calculatedIndex];
      }
      return result;
    }
}

2。缓存结果并保持上下文活动

这是您的选项 #2,但没有全局上下文(您理所当然地担心)。如果您担心不处理上下文,请看一下:http://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext.html。 除了加载上下文的内存开销之外,我看不出有什么缺点。 EF 将延迟加载 SomeObjects,直到您首先通过调用 DoMethod() 需要它们。您正在交易保留上下文,除非需要,否则不必加载 SomeObject。

class Program
{
  static void Main(string[] args)
  {
     ...
     Calculation calc = new Calculation(new MyContext());
     //use result, perhaps many times
     /*something with calc.Result; */

     ...
     //Maybe this method is invoked
     calc.Result.DoMethod();

     //context will not go away until Calculation does
  }
}
class Calculation
{
    private MyContext context = null;
    private Xy result = null;

    public Calculation(MyContext context)
    {
        this.context = context;
    }

    public Xy Result {
        get {
            if (result == null) {
                result = Calculate();
            }
            return result;
        }
    }

    private Xy Calculate();
    {
      Xy result;
      xyList = context.Xys.ToList();
      ...
      result = xyList[calculatedIndex];
      return result;
    }
}

3。通过动态代理实施您的选项 #3

这允许将 XY 包装在行为类似于 XY 的代理中,但拦截对 DoMethod 的调用以获取新上下文,以便 SomeObjects 可以在新上下文中解析。我使用了 Castle.Core 项目中提供的 Castle Dynamic Proxy,您只需通过 Nuget 添加即可。有足够的概念开销,我认为这可能是概念的反证明。也就是说,它表明保留上下文以便 SomeObjects 可以针对原始上下文进行延迟加载可能是最干净的想法。再一次,请参考 http://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext.html 中的论点,了解为什么保留上下文可能没问题。顺便说一句,那篇文章来自与 EF 开发团队的对话。

使用 Castle.DynamicProxy;

class Program
{
  static void Main(string[] args)
  {
     ...
     Calculation calc = new Calculation(new MyContext());
     //use result, perhaps many times
     /*something with calc.Result; */

     ...
     //Maybe this method is invoked
     calc.Result.DoMethod();


  }
}

// POCO class
public class XY
{
  public virtual List<Xz> SomeObjects { get; set; } 

  public virtual void DoMethod()
  {
    foreach (var obj in SomeObjects)
    {
       ...
    }
  }
}

public class XYInterceptor : XY, IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        if (invocation.Method.Name == "DoMethod")
        {
            //get a new context so that we can have SomeObjects resolve properly
            using (var context = new MyContext())
            {
                var newXy = context.Xys.Find(((XY)invocation.InvocationTarget).Id);
                newXy.DoMethod();
            }
        }
        else
        {
            //Any other method goes straight through
            invocation.Proceed();
        }
    }
}

public class Calculation
{
    private XY result = null;

    public XY Result {
        get {
            if (result == null) {
                result = Calculate();
            }
            return result;
        }
    }

    private XY Calculate()
    {
      XY proxyResult;
      using (var context = new MyContext())
      {
          xyList = context.Xys.ToList();
          ...
          Xy realResult = xyList[calculatedIndex];
          proxyResult = (new ProxyGenerator()).CreateClassProxyWithTarget<XY>(realResult, new XYInterceptor());
          return proxyResult;
      }
    }
}

我的第三个草图的一个烦人的方面是它没有用新的 XY 更新结果。在它真正准备好使用之前,需要让它发挥作用。