本文介绍了Asp.net MVC View 中的延迟执行是一件非常糟糕的事情吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

公共类用户{公共字符串名称 {get;set;}公共整数 ID {get;set;}}

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

IEnumerableusers = from u in myDbContext.Users选择你;

重新定义视图中的用户(@model IEnumerable):

@foreach(模型中的var item){<tr>@item.Id</tr><tr>@item.Name</tr>}

我的新老板告诉我,这样做不符合MVC,性能会很差.我应该做的是使用 .ToList() 将控制器中的所有用户材料化.我不确定什么更好,因为无论哪种方式,用户都必须具体化,而且我没有看到任何性能损失.

解决方案

延迟执行意味着,在您开始访问用户集合中的项目(当您在foreach 循环).这意味着,如果您尝试访问 User 实体上的导航属性(并且它是唯一记录),则会执行 SELECT 查询

如果您只有一个没有任何外键/虚拟属性的用户表(如您在问题中所展示的),使用上面的代码,EF 将仅执行一个查询以从您的用户表中获取数据.但通常情况并非如此,您可能有来自 User 表的许多不同表的外键.在这种情况下,实体框架的做法有所不同.

举个例子.

假设我们有第二个表,UserType,它有 4 列,Id, Name ,CodeIsAdmin 并且您的 User 表有一个名为 UserTypeId 的第三列,它具有这个新的 UserType 表的外键.每个用户记录都与一个 UserType 记录相关联.

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

1.延迟执行,

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

var users = dbContext.Users;

并将用户传递到 razor 视图,在那里您将遍历集合.

@model IEnumerable@foreach(模型中的 var 用户){<div>@user.Name - @user.UserType.Name

}

当我们执行这个页面时,实体框架将在 User 表上运行 1 个选择查询以获取用户记录以及 UserTypeId 并且当 foreach 块被执行时,它会从原始结果集 (users) 中查询每个唯一的 UserTypeIdUserType 表.如果您运行 SQL 分析器,您可以看到这一点.

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

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

我在 UserType 表中有 3 条不同的记录,我在我的 User 表中使用了所有这些记录.

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

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

执行的 SQL 查询数:1

更好的解决方案?

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

公共类 UserDto{公共整数 ID {get;set;}公共字符串名称 {get;set;}public UserTypeDto UserType { set;得到;}}公共类 UserTypeDto{公共 int Id { 设置;得到;}公共字符串名称{设置;得到;}}

我们的视图将绑定到用户 entity

插入的 UserDto 集合

@model IEnumerable@foreach(模型中的 var 用户){<div>@user.Name - @user.UserType.Name </div>}

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

var users = dbContext.Users.Select(s => new UserDto{Id = s.Id,姓名 = s.Name,UserType = 新的 UserTypeDto{Id = s.UserType.Id,名称 = s.UserType.Name}});

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

执行的 SQL 查询数:1

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

如果执行 ToList(),EF 会立即执行 SQL 并返回结果.但是,如果您不这样做,由于延迟执行,它将在实际需要数据时执行 SQL 语句(例如:您在循环内的视图中呈现一些属性值).

Let's say i have the following model which is obtained via Entity Framework:

public class User{
 public string Name {get;set;}
 public int Id {get;set;}
}

Get all the users from the user table:

IEnumerable<User> users = from u in myDbContext.Users
                          select u;

Reder the users in the View (@model IEnumerable):

@foreach(var item in Model) 
 {
 <tr>@item.Id</tr>
 <tr>@item.Name</tr>
 }

My new boss is telling me that doing it this way is not MVC conform and the performance will be very bad. What i should do is to materalize all USERS in the Controller with .ToList(). I am not sure what is better, because either way the users have to be materialized and i dont see any performance losses.

解决方案

Deferred execution means, your actual SQL query behind the LINQ expression won't be executed until you start accessing the items in the users collection (when you iterate those in the foreach loop). That means, There will be a SELECT query executed if you are trying to access a navigational property (and it is a unique record) on the User entity

If you only have a single user table (like what you have showed in your question) without any foreign keys/vanigational properties, With the above code you have, EF will execute only one query to get data from your User table. But typically this is not the case, You might have foreign keys to a lot of different tables from the User table. In that case Entity framework does thing differently.

Let's take an example.

Assume that we have a second table, UserType which has 4 columns, Id, Name ,Code and IsAdmin and your User table has a third column called UserTypeId which has a foreign key to this new UserType table. Every user record is associated with a UserType record.

Now you want to display all the Users with the UserType name. Let's see different approaches.

1. Deferred execution,

You will execute this code to get all the users

var users = dbContext.Users;

And pass the users to the razor view where you will iterate through the collection.

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

When we execute this page, Entity framework is going to run 1 select query on the User table to get the user records along with the UserTypeId and when the foreach block is being executed, it is going to query the UserType table for each unique UserTypeId from the original result set(users). You can see this if you run a SQL profiler.

You can see that EF is passing UserTypeId (2 in my picture). I had 3 different UserTypeId's being used in the User table, so it queried the UserType table 3 times, one for each UserTypeId.

Number of SQL queries executed : 4 (1 for user table + 3 for UserType table)

I had 3 different records in the UserType table and i used all those in my User Table.

2. Using Include method when querying data

Include keyword is used to achieve eager loading. Eager loading is the process where a query for one type of entity also loads related entities as part of the query.

var users = dbContext.Users.Include(s=>s.UserType);

Here you are telling Entity framework to query from UserType table along with User table. Entity framework will produce an INNER JOIN sql query between both the tables and execute it.

You can see that, It queried all the columns of the UserType table)

Number of SQL queries executed : 1

A Better solution ?

Usually, It is not a good idea to use the entity classes generated by Entity framework in other layers so much. That makes your code tightly coupled.I would suggest querying only data(columns) which is needed and map that to a POCO class (A simple DTO) and use that in your views /other layers. You may keep these DTO classes in common project which can be referred in other projects (Your Data Access project and UI project)

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

Our view will be bound to a collection of UserDto insted of the User entity

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

And now from your Data access layer, you will be returning a collection of UserDto instead of the User entity created by Entity framework.

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

Here, You can see that We are using the Select clause to tell EF which columns we really need. EF will execute an INNER JOIN, but with only those columns, we specified

Number of SQL queries executed : 1

The benefit of this approach is, If you ever want to switch your data access implementation from Entity framework to some other technologoy (Pure ADO.NET/ NHibernate) for your own reasons, You will be only updating your GetUsers method, All other layers (your Razor views/ Other Business layer code etc..) don't need an update because they are not using the entities created by entity framework.

If you do a ToList(), EF executes the SQL right away and return the result. But instead if you are not doing that, because of deferred execution, It is going to execute the SQL statement(s) when it actually needs the data (ex : You render some property value in your view inside the loop).

这篇关于Asp.net MVC View 中的延迟执行是一件非常糟糕的事情吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-30 02:27