简体   繁体   English

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

[英]Can't access related data after disposal of DataContext in Linq to SQL

Restated my Question, old Text Below 重述了我的问题,下面是旧文本

As I am still hoping for an answer I would like to restate my question. 我仍然希望得到答案,所以我想重申一下我的问题。 Image I have a GUI with two lists, one that shows a List of all entries to a database tblOrders and another one that shows the items in each order. 我有一个包含两个列表的GUI,一个显示一个数据库tblOrders的所有条目的列表,另一个显示每个订单中的项目的GUI。

I can use Linq2sql or EF to get all orders from the database, like so: 我可以使用Linq2sql或EF从数据库中获取所有订单,如下所示:

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

I can display these order in a list or datagridview . 我可以在列表或datagridview显示这些顺序。 Then on a selection of one of the jobs I want to access the collection of entities from the table tblItems . 然后在选择一项作业后,我要从表tblItems访问实体的集合。 I can do this like so: 我可以这样做:

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

Except I can not, because I need the DataContext which has been disposed. 除了我不能,因为我需要已处理的DataContext This makes sense in a way, since I can not guarantee that there is not a new items that has been added to that order since I retrieved the 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 ListOfOrders. So ideally I would like to check if there has been an addition to a tblOrder.tblItems collection and only newly retrieve that collection from the server if required. 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集合,并且仅在需要时才从服务器新检索该集合。

The background is, that my model is a bit more complex: It consists of Orders, which consist of Parts, which consist of Tasks. 背景是我的模型要复杂一些:它由订单组成,订单由零件组成,零件由任务组成。 So to assess the progress of each order I have to retrieve all parts that belong to an order and for each of them I have to see how many of the tasks have been completed. 因此,要评估每个订单的进度,我必须检索属于一个订单的所有零件,并且对于每个零件,我必须查看已完成多少任务。 In a database with 200 job, each with 1 to 10 parts this simply makes my program to slow in terms of responsiveness ... 在具有200个工作的数据库中,每个工作只有1到10个部分,这只会使我的程序的响应速度变慢...

Can anyone help me? 谁能帮我?

Original Question 原始问题

I found a lot of questions concerning DataContext , but I have not found a solution to my problem yet. 我发现了许多有关DataContext的问题,但还没有找到解决问题的方法。 If I do the following: 如果我执行以下操作:

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

This gives me a list of the entities in the tblOrder table. 这给了我tblOrder表中实体的列表。 But now I want to do this: 但是现在我要这样做:

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);
}

And I can not because accessing all entities in the tblItem table that are related to orders by foreign key don't seem to be stored. 而且我不能,因为似乎没有存储通过外键访问tblItem表中与订单相关的所有实体。

What which is working: 什么工作:

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();

But as I understand this is not desirable. 但是据我了解,这是不可取的。

EDIT 编辑

I extended my example into a Controller-View-Pattern. 我将示例扩展为Controller-View-Pattern。

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();
            }
        }
    }
}

And the view now retrieves the Orders from the controller: 现在,该视图从控制器检索订单:

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);
            }
        }
    }
}

Sadly the problem remains the same ... 可悲的是问题仍然存在...

Entity Framework lazy-loads object data, meaning it loads the minimum amount of data it has to as late as possible. Entity Framework延迟加载对象数据,这意味着它加载的数据量必须尽可能晚。 Take your query: 进行查询:

ListOfOrders = context.tblOrder.ToList();

Here you are requesting all of the records in the tblOrder table. 在这里,您需要tblOrder表中的所有记录。 Entity Framework doesn't read ahead in your program and understand that you will be looking at the tblItem table after the context has been disposed, so it assumes it can load the tblItem data later. 实体框架不会在您的程序中提前阅读,并且了解您将在处理tblItem上下文后查看tblItem表,因此它假定以后可以加载tblItem数据。 Being lazy, it loads the bare minimum requested: The list of records in tblOrder . 懒惰地,它加载请求的最低要求: tblOrder中的记录列表。

There are two ways is a way around this: 两种方法可以解决此问题:

Disable lazy loading 禁用延迟加载

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

With LazyLoadingEnabled=false Entity Framework will select the entire contents of the tblOrder table and all tables connected with it via a foreign key. 使用LazyLoadingEnabled=false实体框架将选择tblOrder表的全部内容以及通过外键与其连接的所有表。 This may take a while and use a lot of memory, depending on the size and number of related tables. 根据相关表的大小和数量,这可能会花费一些时间并占用大量内存。

(Edit: My mistake, disabling LazyLoading does not enable eager loading , and there is no default configuration for eager loading . Apologies for the misinformation. The .Include command below looks like the only way to go.) (编辑:我的错, 禁用LazyLoading不会启用紧急加载 ,并且没有默认配置用于 .Include 加载 。对于错误信息,我们.Include 。下面的.Include命令似乎是唯一的解决方法。)

Include additional tables 包括其他表格

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

This tells Entity Framework to load all related data from tblItems up front while it's loading the tblOrders table data. 这告诉Entity Framework在加载tblOrders表数据时先从tblItems加载所有相关数据。 EF still doesn't load any data from other related tables, so that other data will be unavailable after the context is disposed. EF仍然不会从其他相关表中加载任何数据,因此在处理上下文之后,其他数据将不可用。

However, this does not solve the problem of stale data -- that is, over time the records in dataGridView1 will no longer be up-to-date. 但是,这不能解决陈旧数据的问题-也就是说,随着时间的流逝, dataGridView1的记录将不再是最新的。 You could have a button or timer that triggers a refresh. 您可能具有触发刷新的按钮或计时器。 The simplest method of refreshing would be to do the entire process over again -- reload _orders , then selectively repopulate dataGridView1 . 最简单的刷新方法是重新执行整个过程-重新加载_orders ,然后有选择地重新填充dataGridView1

I advice you to create new result class, that contains your data: 我建议您创建一个新的结果类,其中包含您的数据:

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

Select needed data: 选择所需的数据:

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();
        }
    }
}

And after that bind this collection do grid view. 然后绑定该集合,然后执行网格视图。 So, you'll get all your data before disposing context and you have no more dependencies. 因此,您将在处理上下文之前获取所有数据,并且不再有依赖关系。

Just an answer to the first question. 只是第一个问题的答案。
Like EF says, you disposed the context after the [unit of] work (a must in ASP, a good practice in WinForm/WPF). 就像EF所说的那样,您将上下文放置在[工作单元]之后(在ASP中必须这样做,这是WinForm / WPF中的一种良好做法)。

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

after this, if you try to run this statement 之后,如果您尝试运行此语句

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

EF works in this way: EF的工作方式如下:
- get the first element of ListOfOrders that is a proxy to your object. -获取ListOfOrders的第一个元素,该元素是对象的代理。
- try to get the tblItems related with LazyLoad. -尝试获取与LazyLoad相关的tblItems。
At this point, to retrieve the tblItems needs a database access with a context that is retrieved from the proxy of the ListOfOrder first element BUT the context is disposed. 在这一点上,要检索tblItem,需要使用上下文访问数据库,该上下文是从ListOfOrder第一元素的代理检索的,但是要处理上下文。

So you can't use this approach. 因此,您不能使用这种方法。

There are 2 solutions and some variations on them: 有2个解决方案,并且有一些变体:
- You can read all the tblItems before disposing the context (you can see it in another answer) but is a not scalable approach. -您可以在处置上下文之前阅读所有tblItems(可以在另一个答案中看到它),但这是不可扩展的方法。
- When you need to access to tblItems you retrieve the Order from a new context and do what you need before disposing it. -当您需要访问tblItems时,可以从新的上下文中检索Order,并在处置它之前执行所需的操作。 In your case 就你而言

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

A variation on this approach is to read only tblItems. 这种方法的一种变化是只读取tblItems。 You can do this if tblItems has exposed also the foreign key field and not only the navigation property (usually I don't). 如果tblItems还公开了外键字段,而不只是公开了导航属性(通常我没有),则可以执行此操作。

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

The default behavior of EF is to Lazy Load entities. EF的默认行为是延迟加载实体。 In general this is a good idea and I think you should not disable it if you don't have very good reasons. 一般来说,这是个好主意,如果您没有很好的理由,我认为您不应该禁用它。

EF also provides methods for Eager Loading specified entities: EF还提供了用于快速加载指定实体的方法:

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

Then you can use the Include method: 然后,您可以使用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;
}

Now you can use GetOrdersAndItems to load a list with all your orders and each order will contain all the Items: 现在,您可以使用GetOrdersAndItems加载所有订单的列表,每个订单将包含所有Items:

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);
        }
    }
}

For more examples and multiple level includes take a look here 有关更多示例和多个级别的内容,请查看此处

Note: using a helper or a controller or a repository is not important in the understanding of this mechanism, so I simply used a static method. 注意:使用助手,控制器或存储库对理解这种机制并不重要,因此我只使用了静态方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM