C# 使用EF6优化LINQ查询

C# 使用EF6优化LINQ查询,c#,wpf,entity-framework,linq,entity-framework-6,C#,Wpf,Entity Framework,Linq,Entity Framework 6,我第一次尝试LINQ,只是想发布一个小问题,以确定这是否是最好的解决方法。我想要一个表中每个值的列表。到目前为止,这就是我所拥有的,而且很有效,但这是以一种对LINQ友好的方式收集所有东西的最佳方式吗 public static List<Table1> GetAllDatainTable() { List<Table1> Alldata = new List<Table1>(); using (var con

我第一次尝试LINQ,只是想发布一个小问题,以确定这是否是最好的解决方法。我想要一个表中每个值的列表。到目前为止,这就是我所拥有的,而且很有效,但这是以一种对LINQ友好的方式收集所有东西的最佳方式吗

    public static List<Table1> GetAllDatainTable()
    {
        List<Table1> Alldata = new List<Table1>();

        using (var context = new EFContext())
        {
           Alldata = context.Tablename.ToList();
        }
        return Alldata;
    }
公共静态列表GetAllDatainTable() { List Alldata=新列表(); 使用(var context=new EFContext()) { Alldata=context.Tablename.ToList(); } 返回所有数据; }
对于简单实体,即不引用其他实体(导航属性)的实体,您的方法基本上是正确的。它可以浓缩为:

public static List<Table1> GetAllDatainTable()
{
    using (var context = new EFContext())
    {
       return context.Table1s.ToList();
    }
}
然而,这种方法有两个缺点。首先,这意味着每次我们获取订单时都要加载相当多的数据。消费代码可能不关心客户或订单行,但我们已经加载了所有代码。其次,随着系统的发展,可能会引入新的关系,当越来越多的相关数据被包含时,旧代码不一定会被更新以包含潜在的
NullReferenceException
s、bug或性能问题。视图或最初使用此实体的任何对象可能不会引用这些新关系,但一旦开始将实体传递给视图、视图和其他方法,任何接受实体的代码都应该依赖于这样一个事实,即实体已完成或可以完成。无论数据是否加载,订单都可能以不同的“完整性”和代码处理级别加载,这可能是一场噩梦。作为一般性建议,我建议不要在加载实体的DbContext范围之外传递实体

更好的解决方案是利用投影从适合代码使用的实体填充视图模型。WPF通常使用MVVM模式,因此这意味着使用EF的
Select
方法或Automapper的
ProjectTo
方法来根据每个消费者的需求填充视图模型。当您的代码使用包含数据视图和此类需求的ViewModels时,然后根据需要加载和填充实体,这将允许您生成更高效(快速)和更具弹性的查询以获取数据

如果我有一个视图,其中列出了带有创建日期、客户名称和产品/数量列表的订单,我们将为该视图定义一个视图模型:

[Serializable]
public class OrderSummary
{
    public int OrderId { get; set; }
    public string OrderNumber { get; set; }
    public DateTime CreatedAt { get; set; }
    public string CustomerName { get; set; }
    public ICollection<OrderLineSummary> OrderLines { get; set; } = new List<OrderLineSummary>();
}

[Serializable]
public class OrderLineSummary
{
    public int OrderLineId { get; set; }
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
}
请注意,我们不需要担心如何快速加载相关实体,如果以后订单、客户或类似客户获得了新的关系,则上述查询将继续工作,仅当新的关系信息对其所服务的视图有用时才会更新。它可以组成一个更快、内存占用更少的查询,从数据库到应用程序的连接中获取更少的字段,并且可以使用索引进一步优化此查询,以实现高使用率查询

更新:

其他性能提示:通常避免将
GetAll*()
等方法作为最低公分母方法。我在使用此类方法时遇到的太多性能问题表现为:

var ordersToShip = GetAllOrders()
    .Where(o => o.OrderStatus == OrderStatus.Pending)
    .ToList();
foreach(order in ordersToShip)
{
    // do something that only needs order.OrderId.
}
其中
GetAllOrders()
返回
List
IEnumerable
。有时会出现类似于
GetAllOrders().Count()>0
之类的代码

像这样的代码效率极低,因为GetAllOrders()从数据库中获取*所有记录,然后将它们加载到应用程序的内存中,以便稍后进行筛选或计数等

如果要通过方法将EF DbContext和实体抽象到服务/存储库中,那么应该确保服务公开方法以生成高效查询,或者放弃抽象,直接在需要数据的地方利用DbContext

var orderIdsToShip = context.Orders
    .Where(o => o.OrderStatus == OrderStatus.Pending)
    .Select(o => o.OrderId)
    .ToList();


var customerOrderCount = context.Customer
    .Where(c => c.CustomerId == customerId)
    .Select(c => c.Orders.Count())
    .Single();

EF功能极其强大,当您选择为应用程序提供服务时,应将其作为应用程序的一部分,以提供最大的好处。我建议避免纯粹为了抽象而编写代码将其抽象出来,除非您希望使用单元测试来隔离对数据的依赖性和mock。在这种情况下,我建议利用DbContext的工作单元包装器和利用
IQueryable
的存储库模式,使隔离业务逻辑变得简单。

否。您正在使用“new EFContext()”创建数据库的新实例。上下文应该是静态的,您希望静态数据不是一个新的空实例。@jdweng我明白了,我遵循的一个教程就是这样做的,这就是为什么我认为这是正确的方法,我需要更改什么才能使用预先存在的上下文?您的方法非常好。您正在创建一个短期上下文,从该表中获取所有数据,然后处理该上下文
new EFContext()
不一定要创建数据库的新实例。如果已经有数据库,当然不会。您需要找到正在使用的名称。通常它类似于dbContext@JohnFo6,不要使用静态上下文!每次需要时都要创建和处置它。
var ordersToShip = GetAllOrders()
    .Where(o => o.OrderStatus == OrderStatus.Pending)
    .ToList();
foreach(order in ordersToShip)
{
    // do something that only needs order.OrderId.
}
var orderIdsToShip = context.Orders
    .Where(o => o.OrderStatus == OrderStatus.Pending)
    .Select(o => o.OrderId)
    .ToList();


var customerOrderCount = context.Customer
    .Where(c => c.CustomerId == customerId)
    .Select(c => c.Orders.Count())
    .Single();