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
,Code
和IsAdmin
你的用户表有一个称为第三列UserTypeId
其中有一个外键到这个新UserType
表.每个用户记录都与UserType
记录相关联.
现在,您要显示具有UserType名称的所有用户.让我们看看不同的方法.
您将执行此代码以获取所有用户
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
表中的每个唯一UserTypeId
set(users
).如果运行SQL事件探查器,则可以看到此信息.
你可以看到EF正在传递UserTypeId
(我的图片中有2个).我在User表中使用了3个不同的UserTypeId,因此它查询了UserType表3次,每次一个UserTypeId
.
执行的SQL查询数:4(用户表为1,UserType表为3)
我在UserType表中有3个不同的记录,我使用了我的用户表中的所有记录.
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语句(例如:你在循环中的视图中渲染一些属性值).
这可能会导致性能问题,但并不总是导致它们.
如果IEnumerable<T>
将视图传递给视图,则视图不一定了解有关后备数据源的任何信息.可能存在一个未说出的假设,即消费代码(视图)将仅枚举该集合一次. 如果多次枚举集合,则可能会多次查询后备数据存储.
请注意,这并不一定意味着多次访问数据库,但枚举一个集合可能会带来各种隐藏的"陷阱".
IList<T>
在这方面,传递到视图更加明确.两个接口之间的语义差异在于它IList<T>
是一个完整的列表,而不仅仅是一个模糊的枚举.预计在没有性能损失的情况下,可以根据需要多次查询和枚举.(当然不能保证实现类型提供此功能,但预计会这样做.)
这不是"延期执行"的问题.查询将在非常接近的同时执行服务器端,无论是在控制器中还是在视图中.它不会被推迟很长时间.这只是消费代码如何处理枚举的问题.
我的新老板告诉我,这样做是不符合MVC,性能会非常糟糕.
你的新老板真的应该更好地解释一下.这与MVC无关,而且IEnumerable<T>
与IList<T>
LINQ以及LINQ预期的行为方式有关.在使用这些技术的任何应用程序中也是如此.您将模型从控制器传递到视图的事实使得这个MVC成为现实.如果你不知道为什么会这样,那么表现可能会很糟糕.然而,通过测量它,只有一种真正的方式来了解性能.
归档时间: |
|
查看次数: |
1023 次 |
最近记录: |