在Linq to SQL中处理DataContext后无法访问相关数据

重申了我的问题,旧文本如下

由于我仍然希望得到答案,我想重申我的问题。 Image我有一个带有两个列表的GUI,一个列表显示数据库tblOrders的所有条目列表,另一个列表显示每个订单中的项目。

我可以使用Linq2sql或EF从数据库中获取所有订单,如下所示:

 using (DataClasses1DataContext DataContext = new DataClasses1DataContext()) ListOfOrders = DataContext.tblOrder.ToList(); 

我可以在列表或datagridview显示这些顺序。 然后选择其中一个作业,我想从表tblItems访问实体集合。 我可以这样做:

 ListOfOrders.First().tblItems.toList(); 

除了我不能,因为我需要已经处理的DataContext 。 这在某种程度上是有道理的,因为我无法保证自从我检索ListOfOrders. So ideally I would like to check if there has been an addition to a tblOrder.tblItems以来没有添加到该订单的新项目ListOfOrders. So ideally I would like to check if there has been an addition to a tblOrder.tblItems ListOfOrders. So ideally I would like to check if there has been an addition to a tblOrder.tblItems集合,并且只在需要时从服务器重新检索该集合。

背景是,我的模型有点复杂:它由Orders组成,它由Parts组成,包括Tasks。 因此,为了评估每个订单的进度,我必须检索属于订单的所有部件,并且对于每个订单,我必须看到已经完成了多少任务。 在一个有200个作业的数据库中,每个都有1到10个部分,这只会让我的程序在响应性方面变慢…

谁能帮我?

原始问题

我发现了很多关于DataContext的问题,但我还没有找到解决问题的方法。 如果我执行以下操作:

 using (DataClasses1DataContext DataContext = new DataClasses1DataContext()) ListOfOrders = DataContext.tblOrder.ToList(); 

这给了我tblOrder表中的实体列表。 但现在我想这样做:

 DataTable Orders = new DataTable(); Orders.Columns.Add("Order-ID", typeof(int)); Orders.Columns.Add("Order-Name", typeof(string)); Orders.Columns.Add("Order-Items", typeof(string)); dataGridView1.DataSource = Orders; foreach (tblOrder Order in ListOfOrders) { var newRow = Orders.NewRow(); newRow["Order-ID"] = Order.orderID; newRow["Order-Name"] = Order.orderName; newRow["Order-Items"] = string.Join(", ", Order.tblItem.Select(item=> item.itemName).ToList()); // System.ObjectDisposedException (dataGridView1.DataSource as DataTable).Rows.Add(newRow); } 

我不能,因为访问tblItem表中与外键相关的所有实体似乎没有存储。

什么工作:

 DataClasses1DataContext DataContext = new DataClasses1DataContext(); ListOfOrders = DataContext.tblOrder.ToList(); DataTable Orders = new DataTable(); Orders.Columns.Add("Order-ID", typeof(int)); Orders.Columns.Add("Order-Name", typeof(string)); Orders.Columns.Add("Order-Items", typeof(string)); dataGridView1.DataSource = Orders; foreach (tblOrder Order in ListOfOrders) { var newRow = Orders.NewRow(); newRow["Order-ID"] = Order.orderID; newRow["Order-Name"] = Order.orderName; newRow["Order-Items"] = string.Join(", ", Order.tblItem.Select(item=> item.itemName).ToList()); (dataGridView1.DataSource as DataTable).Rows.Add(newRow); } DataContext.Dispose(); 

但据我所知,这是不可取的。

编辑

我将我的示例扩展为Controller-View-Pattern。

 using System.Collections.Generic; using System.Linq; namespace TestApplication { class Controller { private List _orders; public IList Orders { get { return _orders; } } public Controller() { using (var DataContext = new DataClasses1DataContext()) { _orders = DataContext.tblOrder.ToList(); } } } } 

视图现在从控制器检索Orders:

 using System.Data; using System.Linq; using System.Windows.Forms; namespace TestApplication { public partial class Form1 : Form { public Form1() { InitializeComponent(); Controller controller = new Controller(); DataTable Orders = new DataTable(); Orders.Columns.Add("Order-ID", typeof(int)); Orders.Columns.Add("Order-Name", typeof(string)); Orders.Columns.Add("Order-Items", typeof(string)); dataGridView1.DataSource = Orders; foreach (tblOrder Order in controller.Orders) { var newRow = Orders.NewRow(); newRow["Order-ID"] = Order.orderID; newRow["Order-Name"] = Order.orderName; newRow["Order-Items"] = string.Join(", ", Order.tblItem.Select(item=> item.itemName).ToList()); (dataGridView1.DataSource as DataTable).Rows.Add(newRow); } } } } 

可悲的是问题仍然存在……

entity framework延迟加载对象数据,这意味着它尽可能晚地加载最少量的数据。 接受你的询问:

 ListOfOrders = context.tblOrder.ToList(); 

在这里,您要求tblOrder表中的所有记录。 entity framework不会在您的程序中tblItem读,并且理解您将在处理tblItem上下文后查看tblItem表,因此它假定它可以在以后加载tblItem数据。 懒惰,它加载所请求的最低限度: tblOrder中的记录列表。

两种方法可以解决这个问题:

禁用延迟加载

  using (var context = new DataClasses1DataContext()) { data.Configuration.LazyLoadingEnabled = false; _orders = context.tblOrder.ToList(); } 

使用LazyLoadingEnabled=falseentity framework将选择tblOrder表的全部内容以及通过外键与其连接的所有表。 这可能需要一段时间并使用大量内存,具体取决于相关表的大小和数量。

(编辑:我的错误, 禁用LazyLoading不会启用急切加载 ,并且没有默认配置用于急切加载 。对错误信息抱歉。下面的.Include命令看起来是唯一的方法。)

包括其他表格

  using (var context = new DataClasses1DataContext()) { data.Configuration.LazyLoadingEnabled = false; _orders = context.tblOrder.Include("tblItems").ToList(); } 

这告诉entity framework在加载tblOrders表数据时从前面加载tblOrders中的所有相关数据。 EF仍然不会从其他相关表中加载任何数据,因此在处理上下文后其他数据将不可用。

但是,这并不能解决过时数据的问题 – 也就是说,随着时间的推移, dataGridView1的记录将不再是最新的。 您可以使用触发刷新的按钮或计时器。 最简单的刷新方法是重新执行整个过程 – 重新加载_orders ,然后选择性地重新填充dataGridView1

我建议你创建包含你的数据的新结果类:

 class Result { int ID {get;set;} string OrderName {get;set;} IEnumerable ItemNames {get;set;} } 

选择所需数据:

 class Controller { private List _orders; public IList Orders { get { return _orders; } } public Controller() { using (var DataContext = new DataClasses1DataContext()) { _orders = DataContext.tblOrder.Select(o=> new Result { ID = o.orderID, OrderName = o.orderName, ItemNames = o.tblItem.Select(item=> item.itemName) }).ToList(); } } } 

然后绑定此集合进行网格视图。 因此,在处理上下文之前,您将获得所有数据,并且您没有更多依赖项。

只是第一个问题的答案。
就像EF说的那样,你在[工作单元]之后处理了上下文(在ASP中是必须的,WinForm / WPF中的一个好习惯)。

 using (DataClasses1DataContext DataContext = new DataClasses1DataContext()) ListOfOrders = DataContext.tblOrder.ToList(); 

在此之后,如果您尝试运行此语句

 ListOfOrders.First().tblItems.toList(); 

EF以这种方式工作:
– 获取作为对象代理的ListOfOrders的第一个元素。
– 尝试获取与LazyLoad相关的tblItems。
此时,要检索tblItem需要具有从ListOfOrder第一元素的代理检索的上下文的数据库访问,而是放置上下文。

所以你不能使用这种方法。

它们有两种解决方案和一些变体:
– 您可以在处理上下文之前读取所有tblItem(您可以在另一个答案中看到它),但这是一种不可扩展的方法。
– 当您需要访问tblItems时,您可以从新上下文中检索Order并在处理它之前执行所需操作。 在你的情况下

 using (DataClasses1DataContext DataContext = new DataClasses1DataContext()) { int Id = ListOfOrders.First().Id; var myListOftblItems = DataContext.tblOrder.Find(Id).tblItems.ToList(); } 

这种方法的一种变体是只读取tblItems。 如果tblItems也暴露了外键字段而不仅仅是导航属性(通常我没有),你可以这样做。

 using (DataClasses1DataContext DataContext = new DataClasses1DataContext()) { int Id = ListOfOrders.First().Id; var myListOftblItems = DataContext.tblItems.Where(t => t.IdOrder == Id).ToList(); } 

EF的默认行为是Lazy Load实体。 一般来说这是一个好主意,我认为如果你没有很好的理由你不应该禁用它。

EF还提供了Eager Loading指定实体的方法:

 // Add this! using System.Data.Entity; 

然后你可以使用Include方法:

 public static IList GetOrdersAndItems() { List orders; using (var context = new ShopDC()) { context.Database.Log = Console.WriteLine; Console.WriteLine("Orders: " + context.Orders.Count()); orders = context.Orders .Where(o => o.OrderID > 0) // Tells EF to eager load all the items .Include(o => o.Items) .ToList(); } return orders; } 

现在,您可以使用GetOrdersAndItems加载包含所有订单的列表,每个订单将包含所有项目:

 public static void Run() { IList disconnectedOrders = GetOrdersAndItems(); foreach (var order in disconnectedOrders) { Console.WriteLine(order.Name); foreach (var item in order.Items) { Console.WriteLine("--" + item.Name); } } } 

有关更多示例和多个级别,请查看此处

注意:使用帮助器或控制器或存储库对理解此机制并不重要,因此我只使用了静态方法。