是否在Asp.net MVC中延期执行查看非常糟糕的事情?

Ben*_*tin 7 c# asp.net-mvc entity-framework

假设我有通过实体框架获得的以下模型:

public class User{
 public string Name {get;set;}
 public int Id {get;set;}
}
Run Code Online (Sandbox Code Playgroud)

从用户表中获取所有用户:

IEnumerable<User> users = from u in myDbContext.Users
                          select u;
Run Code Online (Sandbox Code Playgroud)

在视图中重新查看用户(@model IEnumerable):

@foreach(var item in Model) 
 {
 <tr>@item.Id</tr>
 <tr>@item.Name</tr>
 }
Run Code Online (Sandbox Code Playgroud)

我的新老板告诉我,这样做是不符合MVC,性能会非常糟糕.我应该做的是使用.ToList()将Controller中的所有USERS资格化.我不确定什么是更好的,因为无论哪种方式用户必须实现,我没有看到任何性能损失.

Shy*_*yju 12

延迟执行意味着,在您开始访问users集合中的项目(在循环中迭代这些项目)之前,不会执行LINQ表达式后面的实际SQL查询foreach.这意味着,如果您尝试访问User实体上的导航属性(并且它是唯一记录),则会执行SELECT查询

如果您只有一个用户表(就像您在问题中显示的那样)没有任何外键/移动属性,使用上面的代码,EF将只执行一个查询以从User表中获取数据.但通常情况并非如此,您可能拥有User表中许多不同表的外键.在这种情况下,实体框架会做不同的事情.

我们来举个例子吧.

假设我们有一个第二个表,用户类型其中有4列,Id,Name,CodeIsAdmin你的用户表有一个称为第三列UserTypeId其中有一个外键到这个新UserType表.每个用户记录都与UserType记录相关联.

现在,您要显示具有UserType名称的所有用户.让我们看看不同的方法.

1.延期执行,

您将执行此代码以获取所有用户

var users = dbContext.Users;
Run Code Online (Sandbox Code Playgroud)

并将用户传递到剃刀视图,您将遍历该集合.

@model IEnumerable<YourEntityNameSpace.User>
@foreach (var user in Model)
{
    <div>
        @user.Name - @user.UserType.Name
    </div>
}
Run Code Online (Sandbox Code Playgroud)

当我们执行这个页面时,Entity框架将在User表上运行1个select查询以获取用户记录以及UserTypeId当执行foreach块时,它将从原始结果查询UserType表中的每个唯一UserTypeIdset(users).如果运行SQL事件探查器,则可以看到此信息.

在此输入图像描述

你可以看到EF正在传递UserTypeId(我的图片中有2个).我在User表中使用了3个不同的UserTypeId,因此它查询了UserType表3次,每次一个UserTypeId.

执行的SQL查询数:4(用户表为1,UserType表为3)

我在UserType表中有3个不同的记录,我使用了我的用户表中的所有记录.

2.查询数据时使用Include方法

Include关键字用于实现预先加载.预先加载是一种过程,其中对一种类型的实体的查询也将相关实体作为查询的一部分加载.

var users = dbContext.Users.Include(s=>s.UserType);
Run Code Online (Sandbox Code Playgroud)

在这里,您要告诉Entity框架从UserType表和User表中查询.实体框架将在两个表之间生成INNER JOIN sql查询并执行它.

在此输入图像描述

你可以看到,它查询了UserType表的所有列)

执行的SQL查询数:1

更好的解决方案?

通常,在其他层中使用Entity框架生成的实体类并不是一个好主意.这使您的代码紧密耦合.我建议只查询所需的数据(列)并将其映射到POCO类(简单的DTO)并在视图/其他层中使用它.您可以将这些DTO类保存在可以在其他项目中引用的公共项目中(您的数据访问项目和UI项目)

public class UserDto
{
  public int Id {get;set;}
  public string Name {get;set;} 
  public UserTypeDto UserType { set; get; }    
}
public class UserTypeDto
{
    public int Id { set; get; }
    public string Name { set; get; }
}
Run Code Online (Sandbox Code Playgroud)

我们的观点将被绑定到集合UserDto用户的insted的entity

@model IEnumerable<YourCommonNamespace.User>
@foreach (var user in Model)
{
    <div> @user.Name - @user.UserType.Name </div>
}
Run Code Online (Sandbox Code Playgroud)

现在,从您的数据访问层,您将返回一个集合UserDto而不是User实体框架创建的实体.

var users = dbContext.Users.Select(s => new UserDto
{
    Id = s.Id,
    Name = s.Name,
    UserType = new UserTypeDto
    {
        Id = s.UserType.Id,
        Name = s.UserType.Name
    }
});
Run Code Online (Sandbox Code Playgroud)

在这里,您可以看到我们正在使用该Select子句告诉EF我们真正需要哪些列.EF将执行INNER JOIN,但只有那些列,我们指定

在此输入图像描述

执行的SQL查询数:1

这种方法的好处是,如果您想要将数据访问实现从Entity框架切换到其他技术(Pure ADO.NET/NHibernate),出于自己的原因,您将只更新GetUsers方法,所有其他层(您的Razor视图/其他业务层代码等.)不需要更新,因为它们不使用实体框架创建的实体.

如果你这样做ToList(),EF马上执行SQL并返回结果.但是如果你不这样做,因为延迟执行,它将在实际需要数据时执行SQL语句(例如:你在循环中的视图中渲染一些属性值).


Dav*_*vid 5

可能会导致性能问题,但并不总是导致它们.

如果IEnumerable<T>将视图传递给视图,则视图不一定了解有关后备数据源的任何信息.可能存在一个未说出的假设,即消费代码(视图)将仅枚举该集合一次. 如果多次枚举集合,则可能会多次查询后备数据存储.

请注意,这并不一定意味着多次访问数据库,但枚举一个集合可能会带来各种隐藏的"陷阱".

IList<T>在这方面,传递到视图更加明确.两个接口之间的语义差异在于它IList<T>是一个完整的列表,而不仅仅是一个模糊的枚举.预计在没有性能损失的情况下,可以根据需要多次查询和枚举.(当然不能保证实现类型提供此功能,但预计会这样做.)

这不是"延期执行"的问题.查询将在非常接近的同时执行服务器端,无论是在控制器中还是在视图中.它不会被推迟很长时间.这只是消费代码如何处理枚举的问题.

我的新老板告诉我,这样做是不符合MVC,性能会非常糟糕.

你的新老板真的应该更好地解释一下.这与MVC无关,而且IEnumerable<T>IList<T>LINQ以及LINQ预期的行为方式有关.在使用这些技术的任何应用程序中也是如此.您将模型从控制器传递到视图的事实使得这个MVC成为现实.如果你不知道为什么这样,那么表现可能会很糟糕.然而,通过测量它,只有一种真正的方式来了解性能.