使用实体框架提高效率

5

我一直在使用POCO First方法和Entity Framework。基本上,我遵循了Steve Sanderson在他的书“Pro ASP.NET MVC 3 Framework”中描述的模式,使用DI容器和DbContext类来连接SQL Server。

由于SQL Server中的底层表包含不同应用程序使用的非常大的数据集,因此我不得不为我应用程序中需要的实体创建视图:

class RemoteServerContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Order> Orders { get; set; }
    public DbSet<Contact> Contacts { get; set; }
    ...

    protected override void  OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Customer>().ToTable("vw_Customers");
        modelBuilder.Entity<Order>().ToTable("vw_Orders");
        ...
    }
}

我发现这对于我的大部分需求来说都运作良好。

但我有一个问题,某些视图中包含了大量的数据,因此当我调用类似以下的内容时:

var customers = _repository.Customers().Where(c => c.Location == location).Where(...);

看起来它正在返回整个数据集,这可能需要一些时间,然后LINQ查询才能将集合减少到我所需的内容。当条件仅适用于少数记录时,从SQL服务器获取整个数据集似乎非常低效。

我已经尝试通过使用存储过程来解决这个问题,例如

public IEnumerable<Customer> CustomersThatMatchACriteria(string criteria1, string criteria2, ...) //or an object passed in!
{
    return Database.SqlQuery<Customer>("Exec pp_GetCustomersForCriteria @crit1 = {0}, @crit2 = {1}...", criteria1, criteria2,...);
}

虽然这种方法速度更快,但问题在于它不返回DbSet,因此我失去了对象之间的所有连接性。例如,即使我包括它们的ID,我也无法引用任何关联的对象,如订单或联系人,因为返回类型是“Customers”的集合而不是它们的DbSet。

有没有更好的方法来获取SQL Server进行查询,以便我不会传递大量未使用的数据?

4个回答

4
var customers = _repository.Customers().Where(c => c.Location == location).Where(...
如果Customers()返回IQueryable,那么这条语句本身并不会实际地“带回”任何东西 - 在IQueryable上调用Where只会给你另一个IQueryable,只有在执行一些导致查询执行的操作(如ToListFirstOrDefault)之后才会实际执行查询并返回结果。
但是,如果这个Customers方法返回一个已实例化对象的集合,那么是的,因为你请求所有的对象,所以你得到了它们所有。
我从未使用过代码优先或仓储模式,所以我不知道该怎么建议,除了尽可能长时间地保持在IQueryable的领域内,并且只在应用所有相关筛选器后执行查询。

+1。为了更具可扩展性的方法,您可以编写一个函数,该函数接受一个谓词,并返回 _repository.Customers().Where(predicate) 或(如果它不再是IQueryable)编写一个单独的函数,使用 context.CreateQuery<Customer>("Customers").Where(predicate),并在末尾调用 .ToList() 的可能性。它应该构造一个漂亮的、优化的表达式。 - Patryk Ćwiek
1
嗨。你的建议保持在IQueryable领域是正确的。我之前使用的是IEnumerable,它不会将查询传递给服务器,而是获取所有记录然后进行筛选。请参考这篇文章:http://www.fascinatedwithsoftware.com/blog/post/2011/06/27/IEnumerable-IQueryable-and-the-Entity-Framework-40.aspx 我已经通过SQL Profiler进行了检查,他是对的,IQueryable会将参数作为查询传递进去。 - GrahamJRoy

2
我会把返回数据的方法改为以下方式:

只返回一组数据,我需要做的是:

var customers = (from x in Repository.Customers where <boolean statement> &&/|| <boolean statement select new {variableName = x.Name , ...).Take(<integer amount for amount of records you need>);

所以举个例子:
var customers = (from x in _repository.Customers where x.ID == id select new {variableName = x.Name} ).take(1000);

然后迭代结果以获取数据:(记住,Linq语句返回IQueryable)...

foreach (var data in customers)
{
   string doSomething = data.variableName; //to get data from your query.
}

希望这对你有所帮助,虽然方法不完全一样,但我在我的代码中发现这很方便。


1
可能是因为您存储库中的Customers()方法正在执行GetAll()操作并首先获取整个列表。这会阻止LINQ和SQL Server创建智能查询。
我不知道是否有一个好的解决方案适用于您的存储库,但如果您采取以下措施:
using(var db = new RemoteServerContext())
{
  var custs = db.Customers.Where(...);
}

我认为这样会更快。如果你的项目足够小,你可以不用仓库。当然,你会失去一个抽象层,但对于小项目来说,这可能不是一个大问题。

另一方面,你可以一次性加载所有客户到你的仓库中,并直接使用结果集合(而不是填充列表的方法调用)。但要注意添加、删除和修改客户。


0
你需要使用LINQ查询返回较少的数据,就像SQL分页中的"top"函数一样,或者使用存储过程手动查询。在任何情况下,你都需要重写你的查询机制。这也是我不使用EF的原因之一,因为似乎无法对代码进行很多控制。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接