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

时间:2015-11-25 16:11:00

标签: c# asp.net-mvc entity-framework

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

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

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

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

在视图中回复用户(@model IEnumerable):

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

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

4 个答案:

答案 0 :(得分:12)

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

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

我们举一个例子。

假设我们有第二个表 UserType ,其中包含4列IdNameCodeIsAdmin以及您的用户table有一个名为UserTypeId的第三列,它有一个指向这个新UserType表的外键。每个用户记录都与UserType记录相关联。

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

1。延期执行,

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

var users = dbContext.Users;

并将用户传递到剃刀视图,在那里您将遍历该集合。

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

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

enter image description here

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

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

我在 UserType 表格中有3条不同的记录,我使用了用户表格中的所有记录。

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

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

UserTypeId

在这里,您要告诉实体框架从 UserType 表以及 用户 表进行查询。实体框架将在两个表之间生成INNER JOIN sql查询并执行它。

enter image description here

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

执行的SQL查询数:1

更好的解决方案?

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

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

我们的观点将绑定到用户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; } }

UserDto版本集合
entity

现在,从您的数据访问层,您将返回@model IEnumerable<YourCommonNamespace.User> @foreach (var user in Model) { <div> @user.Name - @user.UserType.Name </div> } 的集合,而不是实体框架创建的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 } }); 子句告诉EF我们真正需要哪些列。 EF将执行 INNER JOIN ,但只有那些列,我们指定了

enter image description here

执行的SQL查询数:1

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

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

答案 1 :(得分:5)

可能会导致性能问题,但并不总是导致这些问题。

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

请注意,这并不一定意味着多次访问数据库,但枚举一个集合可能带有各种隐藏的&#34;陷阱&#34;。

在这方面,将IList<T>传递给视图更为明确。两个接口之间的语义差异在于IList<T>是一个完整的列表,而不仅仅是一个模糊的枚举。它可以支持在必要时多次查询和枚举,而不会造成性能损失。 (当然不能保证实现类型提供此功能,但预计会这样做。)

这不是一个延迟执行的问题&#34;。无论是在控制器中还是在视图中,查询都将在非常接近的同时执行服务器端。它没有被推迟很长时间。这只是消费代码如何处理枚举的问题。

  

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

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

答案 2 :(得分:1)

你正在做什么对于快速而又脏兮兮的&#34;解。它很容易做到并且有效,但是如果你想做的话,它有一些缺点。代码:

您基本上是从视图进行数据库查询,如果您正在进行分层应用程序,那么绝对不是这个地方。您应该在控制器中为简单应用程序或应用程序服务甚至存储库执行数据库查询,以获得更结构化和复杂的应用程序。

在视图中实现查询在异常处理中也存在问题。数据库异常不会被抛出控制器,因此您无法在控制器或动作过滤器中处理它们。

在表现方面,我不知道它有何不同。你应该避免使用延迟加载。如果您的用户实体具有依赖实体或集合,则EF将为每个用户触发额外查询,这将真正影响性能。理想情况下,您应该禁用延迟加载。如果您遵循将所有数据发送到视图的规则(没有延迟查询),这还包括预先加载所有相关实体。

答案 3 :(得分:0)

按照自己的方式至少可以延长风险背景的生命(从不确定):

  • db lock(db上的跨上下文并发);
  • 连接池故障:在处理上下文时将连接返回给池
  • &#34;背景不再存在&#34;您视图中的例外
  • ...

保持您的上下文范围尽可能短。

顺便说一句,从mvc的角度来看,没有理由为视图和数据访问使用相同的模型。这不是性能问题,而是关注问题的分离,即可维护性问题。