在Linq to SQL

时间:2015-08-11 10:15:02

标签: c# sql-server entity-framework 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集合,并且只在需要时才从服务器重新检索该集合。

背景是,我的模型有点复杂:它由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<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);
            }
        }
    }
}

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

4 个答案:

答案 0 :(得分:4)

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

ListOfOrders = context.tblOrder.ToList();

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

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

禁用延迟加载

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

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

(编辑:我的错误,disabling LazyLoading does not enable eager loadingthere is no default configuration for eager loading。对错误信息道歉。下面的.Include命令看起来是唯一的方法。)

加入其他表格

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

这告诉Entity Framework在加载tblItems表数据时,从tblOrders预先加载所有相关数据。 EF仍然不会从其他相关表中加载任何数据,因此在处理完上下文后其他数据将不可用。

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

答案 1 :(得分:1)

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

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

选择所需数据:

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

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

答案 2 :(得分:0)

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

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

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

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

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

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

有两种解决方案和一些变体:
- 您可以在处理上下文之前读取所有tblItem(您可以在另一个答案中看到它),但这是一种不可扩展的方法。
- 当您需要访问tblItem时,您可以从新上下文中检索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();
}

答案 3 :(得分:0)

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

EF还提供预先加载指定实体的方法:

// 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

注意:使用帮助器或控制器或存储库对理解这种机制并不重要,所以我只是使用静态方法。