在 Linq to SQL 中处理 DataContext 后无法访问相关数据
Can't access related data after disposal of DataContext in Linq to SQL
重述我的问题,下面是旧文本
因为我仍然希望得到答案,所以我想重申一下我的问题。我有一个带有两个列表的 GUI,一个显示数据库中所有条目的列表 tblOrders
,另一个显示每个订单中的项目。
我可以使用 Linq2sql 或 EF 从数据库中获取所有订单,如下所示:
using (DataClasses1DataContext DataContext = new DataClasses1DataContext())
ListOfOrders = DataContext.tblOrder.ToList();
我可以在列表或 datagridview
中显示这些顺序。然后在选择一项工作时,我想从 table tblItems
访问实体集合。我可以这样做:
ListOfOrders.First().tblItems.toList();
除了我不能,因为我需要已经处理掉的DataContext
。这在某种程度上是有道理的,因为我不能保证自从我检索 ListOfOrders. So ideally I would like to check if there has been an addition to a tblOrder.tblItems
集合以来没有新项目被添加到该订单中,并且只在需要时才从服务器新检索该集合。
背景是,我的模型有点复杂:它由订单组成,订单由零件组成,零件由任务组成。因此,为了评估每个订单的进度,我必须检索属于订单的所有部分,并且对于每个部分,我必须查看已经完成了多少任务。在一个有 200 个作业的数据库中,每个作业有 1 到 10 个部分,这只会使我的程序在响应速度方面变慢......
谁能帮帮我?
原题
我发现了很多关于 DataContext
的问题,但我还没有找到解决问题的方法。
如果我执行以下操作:
using (DataClasses1DataContext DataContext = new DataClasses1DataContext())
ListOfOrders = DataContext.tblOrder.ToList();
这为我提供了 tblOrder
table 中的实体列表。
但是现在我想这样做:
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
table 中与通过外键的订单相关的所有实体似乎没有被存储。
哪个有效:
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();
但据我了解,这是不可取的。
编辑
我将示例扩展为控制器视图模式。
using System.Collections.Generic;
using System.Linq;
namespace TestApplication
{
class Controller
{
private List<tblOrder> _orders;
public IList<tblOrder> Orders
{
get
{
return _orders;
}
}
public Controller()
{
using (var DataContext = new DataClasses1DataContext())
{
_orders = DataContext.tblOrder.ToList();
}
}
}
}
视图现在从控制器检索订单:
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
table 中的所有记录。 Entity Framework 不会在您的程序中预读,并了解您将在处理上下文后查看 tblItem
table,因此它假定它可以加载 tblItem
数据稍后。由于懒惰,它加载了最低要求:tblOrder
.
中的记录列表
有两种方法是一种解决方法:
禁用延迟加载
using (var context = new DataClasses1DataContext())
{
data.Configuration.LazyLoadingEnabled = false;
_orders = context.tblOrder.ToList();
}
使用 LazyLoadingEnabled=false
Entity Framework 将 select tblOrder
table 的全部内容以及所有 table 通过外键。这可能需要一段时间并使用大量内存,具体取决于相关 table 的大小和数量。
(编辑:我的错误,disabling LazyLoading does not enable eager loading, and there is no default configuration for eager loading。为错误信息道歉。下面的 .Include
命令看起来是唯一的方法。)
包括额外的 tables
using (var context = new DataClasses1DataContext())
{
data.Configuration.LazyLoadingEnabled = false;
_orders = context.tblOrder.Include("tblItems").ToList();
}
这告诉 Entity Framework 在加载 tblOrders
table 数据时预先从 tblItems
加载所有相关数据。 EF 仍然不会从其他相关 table 加载任何数据,因此在释放上下文后其他数据将不可用。
但是,这并不能解决陈旧数据的问题——也就是说,随着时间的推移,dataGridView1
中的记录将不再是最新的。您可以有一个触发刷新的按钮或计时器。最简单的刷新方法是重新执行整个过程——重新加载 _orders
,然后 select 主动重新填充 dataGridView1
。
我建议您创建包含您的数据的新结果 class:
class Result
{
int ID {get;set;}
string OrderName {get;set;}
IEnumerable<string> ItemNames {get;set;}
}
Select 需要的数据:
class Controller
{
private List<Result> _orders;
public IList<Result> 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 所说,您在 [unit of] 工作之后设置了上下文([=33= 中的必须],WinForm/WPF 中的良好实践)。
using (DataClasses1DataContext DataContext = new DataClasses1DataContext())
ListOfOrders = DataContext.tblOrder.ToList();
在此之后,如果您尝试 运行 此声明
ListOfOrders.First().tblItems.toList();
EF 以这种方式工作:
- 获取作为对象代理的 ListOfOrders 的第一个元素。
- 尝试获取与 LazyLoad 相关的 tblItems。
此时,要检索 tblItems,需要使用从 ListOfOrder 第一个元素的代理中检索到的上下文进行数据库访问,但上下文已被释放。
所以你不能使用这种方法。
有 2 个解决方案和它们的一些变体:
- 您可以在处理上下文之前读取所有 tblItems(您可以在另一个答案中看到它)但这是一种不可扩展的方法。
- 当您需要访问 tblItems 时,您可以从新的上下文中检索订单并在处理它之前执行您需要的操作。
你的情况
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 的默认行为是延迟加载实体。
总的来说,这是一个好主意,我认为如果你没有很好的理由,你不应该禁用它。
EF 还提供了 Eager Loading 指定实体的方法:
// Add this!
using System.Data.Entity;
然后就可以使用Include方法了:
public static IList<Order> GetOrdersAndItems()
{
List<Order> 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<Order> disconnectedOrders = GetOrdersAndItems();
foreach (var order in disconnectedOrders)
{
Console.WriteLine(order.Name);
foreach (var item in order.Items)
{
Console.WriteLine("--" + item.Name);
}
}
}
有关更多示例和多级包含,请查看 here
注意:使用helper或controller或repository对于理解这个机制并不重要,所以我简单地使用了静态方法。
重述我的问题,下面是旧文本
因为我仍然希望得到答案,所以我想重申一下我的问题。我有一个带有两个列表的 GUI,一个显示数据库中所有条目的列表 tblOrders
,另一个显示每个订单中的项目。
我可以使用 Linq2sql 或 EF 从数据库中获取所有订单,如下所示:
using (DataClasses1DataContext DataContext = new DataClasses1DataContext())
ListOfOrders = DataContext.tblOrder.ToList();
我可以在列表或 datagridview
中显示这些顺序。然后在选择一项工作时,我想从 table tblItems
访问实体集合。我可以这样做:
ListOfOrders.First().tblItems.toList();
除了我不能,因为我需要已经处理掉的DataContext
。这在某种程度上是有道理的,因为我不能保证自从我检索 ListOfOrders. So ideally I would like to check if there has been an addition to a tblOrder.tblItems
集合以来没有新项目被添加到该订单中,并且只在需要时才从服务器新检索该集合。
背景是,我的模型有点复杂:它由订单组成,订单由零件组成,零件由任务组成。因此,为了评估每个订单的进度,我必须检索属于订单的所有部分,并且对于每个部分,我必须查看已经完成了多少任务。在一个有 200 个作业的数据库中,每个作业有 1 到 10 个部分,这只会使我的程序在响应速度方面变慢......
谁能帮帮我?
原题
我发现了很多关于 DataContext
的问题,但我还没有找到解决问题的方法。
如果我执行以下操作:
using (DataClasses1DataContext DataContext = new DataClasses1DataContext())
ListOfOrders = DataContext.tblOrder.ToList();
这为我提供了 tblOrder
table 中的实体列表。
但是现在我想这样做:
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
table 中与通过外键的订单相关的所有实体似乎没有被存储。
哪个有效:
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();
但据我了解,这是不可取的。
编辑
我将示例扩展为控制器视图模式。
using System.Collections.Generic;
using System.Linq;
namespace TestApplication
{
class Controller
{
private List<tblOrder> _orders;
public IList<tblOrder> Orders
{
get
{
return _orders;
}
}
public Controller()
{
using (var DataContext = new DataClasses1DataContext())
{
_orders = DataContext.tblOrder.ToList();
}
}
}
}
视图现在从控制器检索订单:
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
table 中的所有记录。 Entity Framework 不会在您的程序中预读,并了解您将在处理上下文后查看 tblItem
table,因此它假定它可以加载 tblItem
数据稍后。由于懒惰,它加载了最低要求:tblOrder
.
有两种方法是一种解决方法:
禁用延迟加载
using (var context = new DataClasses1DataContext())
{
data.Configuration.LazyLoadingEnabled = false;
_orders = context.tblOrder.ToList();
}
使用 LazyLoadingEnabled=false
Entity Framework 将 select tblOrder
table 的全部内容以及所有 table 通过外键。这可能需要一段时间并使用大量内存,具体取决于相关 table 的大小和数量。
(编辑:我的错误,disabling LazyLoading does not enable eager loading, and there is no default configuration for eager loading。为错误信息道歉。下面的 .Include
命令看起来是唯一的方法。)
包括额外的 tables
using (var context = new DataClasses1DataContext())
{
data.Configuration.LazyLoadingEnabled = false;
_orders = context.tblOrder.Include("tblItems").ToList();
}
这告诉 Entity Framework 在加载 tblOrders
table 数据时预先从 tblItems
加载所有相关数据。 EF 仍然不会从其他相关 table 加载任何数据,因此在释放上下文后其他数据将不可用。
但是,这并不能解决陈旧数据的问题——也就是说,随着时间的推移,dataGridView1
中的记录将不再是最新的。您可以有一个触发刷新的按钮或计时器。最简单的刷新方法是重新执行整个过程——重新加载 _orders
,然后 select 主动重新填充 dataGridView1
。
我建议您创建包含您的数据的新结果 class:
class Result
{
int ID {get;set;}
string OrderName {get;set;}
IEnumerable<string> ItemNames {get;set;}
}
Select 需要的数据:
class Controller
{
private List<Result> _orders;
public IList<Result> 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 所说,您在 [unit of] 工作之后设置了上下文([=33= 中的必须],WinForm/WPF 中的良好实践)。
using (DataClasses1DataContext DataContext = new DataClasses1DataContext())
ListOfOrders = DataContext.tblOrder.ToList();
在此之后,如果您尝试 运行 此声明
ListOfOrders.First().tblItems.toList();
EF 以这种方式工作:
- 获取作为对象代理的 ListOfOrders 的第一个元素。
- 尝试获取与 LazyLoad 相关的 tblItems。
此时,要检索 tblItems,需要使用从 ListOfOrder 第一个元素的代理中检索到的上下文进行数据库访问,但上下文已被释放。
所以你不能使用这种方法。
有 2 个解决方案和它们的一些变体:
- 您可以在处理上下文之前读取所有 tblItems(您可以在另一个答案中看到它)但这是一种不可扩展的方法。
- 当您需要访问 tblItems 时,您可以从新的上下文中检索订单并在处理它之前执行您需要的操作。
你的情况
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 的默认行为是延迟加载实体。 总的来说,这是一个好主意,我认为如果你没有很好的理由,你不应该禁用它。
EF 还提供了 Eager Loading 指定实体的方法:
// Add this!
using System.Data.Entity;
然后就可以使用Include方法了:
public static IList<Order> GetOrdersAndItems()
{
List<Order> 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<Order> disconnectedOrders = GetOrdersAndItems();
foreach (var order in disconnectedOrders)
{
Console.WriteLine(order.Name);
foreach (var item in order.Items)
{
Console.WriteLine("--" + item.Name);
}
}
}
有关更多示例和多级包含,请查看 here
注意:使用helper或controller或repository对于理解这个机制并不重要,所以我简单地使用了静态方法。