MVC 5在Visual Basic - 高级实体框架方案中具有EF 6

本教程是一系列12中的最后一个,它教导您如何使用实体框架构建MVC 5应用程序进行数据访问和Visual Basic。此最终教程介绍了几种有用的主题,即超越首先开发使用实体框架代码的ASP.NET Web应用程序的基础知识。逐步说明使用代码并使用Visual Studio Express for Web获取以下主题:

原辅导系列,由汤姆Dykstra生产 里克安德森 ( @rickandmsft. )使用C#语言编写。我的版本尽可能靠近原件,只改变编码语言。叙述文本从原始文本保持不变,并与Microsoft权限一起使用。

本教程系列教授如何使用实体框架6创建ASP.NET MVC 5应用程序 Visual Studio 2013 Express for web。该系列使用代码第一工作流程。有关如何在代码第一,数据库首先选择的信息的信息,请参阅 实体框架开发工作流程.

教程系列总共包括12个部分。它们旨在顺序顺序排列,因为每个部分都在上一节中赋予的知识。通过这些部分的进步反映在Visual Studio Express for Web项目下载,该部分伴随着具有您通过该系列构建的Web应用程序的每个部分。

下载代码

可用本节的代码 这里。拯救 。压缩 文件到方便的位置,然后提取内容。确保您安装了一个版本的Visual Studio 2013(Express for Web,Professional,Premium或Ultimate),然后双击 .sln. 文件。项目在IDE中打开后,按 Shift + Ctrl + B 建立解决方案。这将确保从Nuget恢复所有包,并且可能需要一段时间根据您的互联网连接速度。

通过该系列的导航路径如下:

  1. 创建实体框架数据模型
  2. 实施基本的CRUD功能
  3. 排序,过滤和分页
  4. 连接弹性和命令拦截
  5. 代码首次迁移和部署
  6. 创建更复杂的数据模型
  7. 阅读相关数据
  8. 更新相关数据
  9. 异步和存储过程
  10. 处理并发
  11. 实现 - 继承
  12. 高级实体框架方案

本教程引入了几个主题,简要介绍,然后有关资源的链接有关更多信息:

对于大多数主题来说,您将使用您已创建的页面。要使用RAW SQL进行批量更新,您将创建一个新页面,该页面更新数据库中所有课程的贷记数量:

MVC5与EF6.

要使用无跟踪查询,您将为部门编辑页面添加新验证逻辑:

MVC5与EF6.

执行原始SQL查询

实体框架代码First API包括使您能够直接将SQL命令传递给数据库的方法。您有以下选项:

  • 使用 dbset.sqlquery. 返回实体类型的查询方法。返回的对象必须是预期的类型 DBSET. 对象,并且数据库上下文自动跟踪它们,除非您关闭跟踪。 (见下面的部分 Asnotracking. method.)
  • 使用 database.sqlquery. 查询的方法返回不是实体的类型。即使使用此方法检索实体类型,数据库上下文也未跟踪返回的数据。
  • 使用 database.executesqlcommand. 对于非查询命令。

使用实体框架的一个优点是它避免了密切地绑定代码,以密切地绑定到存储数据的特定方法。它通过为您生成SQL查询和命令来实现这一目标,这也释放您自己自己。但是,当您需要运行手动创建的特定SQL查询时,存在异议的方案,并且这些方法使您可以处理此类异常。

当在Web应用程序中执行SQL命令时始终为真,您必须采用预防措施来保护您的站点免受SQL注入攻击。这样做的一种方法是使用参数化查询来确保网页提交的字符串无法解释为SQL命令。在本教程中,将用户输入输入到查询时,您将使用参数化查询。

调用返回实体的查询

这 DBSET.<TEntity> 类提供了一种方法,您可以使用来执行返回类型的实体的查询 约束。要查看此工作方式,您将更改代码 细节 管制员的方法。

在  Departmencontroller.vb., 更换 db.departments.find. method call with a db.departments.sqlquery. 方法调用,如以下代码所示:

Async Function 细节(ByVal id As Integer?) As Task(Of ActionResult)
    If IsNothing(id) 这n
        Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
    End If
    ' Commenting out original code to show how to use a raw SQL query.
    'Dim department As Department = Await db.Departments.FindAsync(id)

    ' Create and execute raw SQL query.
    Dim query = "SELECT * FROM Department WHERE DepartmentID = @p0"
    Dim department As 部门 = Await db.departments.sqlquery.(query, id).SingleOrDefaultAsync()
    If IsNothing(department) 这n
        Return HttpNotFound()
    End If
    Return View(department)
End Function

要验证新代码是否正确工作,请选择 部门 tab and then 细节 对于其中一个部门。

调用返回其他类型对象的查询

早期您为关于每个注册日期的学生人数创建了一个学生统计网格。这样做的代码  homecontroller.vb. uses LINQ:

Dim data As IQueryable(Of EnrollmentDateGroup) = db.Students.GroupBy _
                                                         (Function(s) s.EnrollmentDate).Select _
                                                         (Function(g) New EnrollmentDateGroup With _
                                                                      {.EnrollmentDate = g.Key, .StudentCount = g.Count()})

假设您要编写在SQL中直接检索此数据的代码,而不是使用LINQ。要执行此操作,您需要运行返回实体对象以外的内容的查询,这意味着您需要使用 database.sqlquery. method.

在  homecontroller.vb.,用SQL语句替换关于关于方法的关于方法的LINQ语句,如以下突出显示的代码所示:

'Dim data As IQueryable(Of EnrollmentDateGroup) = db.Students.GroupBy _
'                                                 (Function(s) s.EnrollmentDate).Select _
'                                                 (Function(g) New EnrollmentDateGroup With _
'                                                              {.EnrollmentDate = g.Key, .StudentCount = g.Count()})

Dim query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount " & _
             "FROM Person " & _
             "WHERE Discriminator = 'Student' " & _
             "GROUP BY EnrollmentDate"
Dim data As IEnumerable(Of EnrollmentDateGroup) = db.Database.SqlQuery(Of EnrollmentDateGroup)(query)
返回 View(data.ToList())

运行关于页面。它显示它之前所做的相同数据。

MVC5与EF6.

调用更新查询

假设Contoso大学管理员希望能够在数据库中执行批量更改,例如更改每个课程的学分数。如果大学有大量课程,则将所有作为实体作为实体检索并单独改变它们会效率低下。在本节中,您将实现一个网页,使用户能够指定要通过执行SQL来更改所有课程的学分数的因素 更新 陈述。网页看起来像下图:

MVC5与EF6.

在  coursecontoller.vb., 添加 更新Coursecredits. methods for httpGet. and httppost:

Public Function 更新Coursecredits.ActionResult() As ActionResult
    返回 View()
End Function


<httppost>
Public Function 更新Coursecredits.ActionResult(ByVal multiplier As Integer?) As ActionResult
    If Not IsNothing(multiplier) 这n
        ViewBag.Rowscaffacted. = db.database.executesqlcommand.("UPDATE Course SET Credits = Credits * {0}", multiplier)
    End If
    Return View()
End Function

当控制器处理一个 httpGet. 请求,没有任何东西归还 ViewBag.Rowscaffacted. 变量,视图显示空文本框和提交按钮,如前一个图示所示。

当。。。的时候 更新 单击按钮,调用HttpPost方法,乘法器具有在文本框中输入的值。然后代码执行更新课程的SQL,并将受影响行的数量返回到视图中 ViewBag.Rowscaffacted. 多变的。当视图获取该变量中的值时,它会显示更新的行数而不是文本框并提交按钮,如下图所示:

MVC5与EF6.

在  coursecontroller.vb.,右键单击其中一个 更新Coursecredits. 方法,然后单击 添加.

MVC5与EF6.

在  视图\课程\ updatecoursecredits.vbhtml,用以下代码替换模板代码:

@Code
    ViewBag.Title = "UpdateCourseCredits"
End Code

<h2>更新 Course Credits</h2>

@If IsNothing(ViewBag.RowsAffected) 这n

    Using Html.BeginForm()
    
        @<p>
            Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
        </p>
        @<p>
            <input type="submit" value="Update" />
        </p>
    End Using
End If
    @If Not IsNothing(ViewBag.RowsAffected) 这n

    @<p>
        Number of rows updated: @ViewBag.Rowscaffacted.
    </p>
End If
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

跑过 更新Coursecredits. 选择方法 课程 tab, then adding "/UpdateCourseCredits"在浏览器地址栏中的URL的末尾(例如: http:// localhost:50205 /课程/ updatecoursecredits)。在文本框中输入一个数字:

MVC5与EF6.

点击 返回目录 查看课程列表,并附上经过修订的信贷人数。

MVC5与EF6.

有关RAW SQL查询的更多信息,请参阅 原始的SQL查询 on MSDN.

无跟踪查询

当数据库上下文检索表行并创建表示它们的实体对象时,默认情况下,它会跟踪内存中的实体是否与数据库中的内容同步。内存中的数据充当缓存,并在更新实体时使用。在Web应用程序中通常不需要这种缓存,因为上下文实例通常是短寿命(为每个请求创建和设置的新一个),并且读取实体的上下文通常在再次使用该实体之前被配置。

您可以使用该方法禁用内存中的实体对象跟踪 Asnotracking. 方法。您可能希望执行此操作的典型方案包括以下内容:

  • 查询检索这样的大量数据,关闭跟踪可能明显增强性能。
  • 您希望附加实体才能更新它,但您之前检索了不同的目的同一实体。由于数据库上下文已跟踪实体,因此无法附加要更改的实体。处理这种情况的一种方法是使用 Asnotracking. 使用前面查询的选项。

在本节中,您将实现业务逻辑,该业务逻辑示出了这些方案的第二个。具体来说,您将强制执行一个业务规则,表明教师不能成为多个部门的管理员。 (取决于您到目前为止使用的部门页面所做的内容,您可能已经有一些具有相同管理员的部门。在生产应用程序中,您也将新规则应用于现有数据,但是本教程不是必要的。)

在  Departmencontroller.vb.,添加一个可以从中拨打的新方法 编辑 and 创造 方法以确保没有两个部门具有相同的管理员:

Private Sub validateoneadministratorassigpmentperinstructor.(department As 部门)
    If department.InstructorID IsNot Nothing Then
        Dim duplicateDepartment As 部门 = db.Departments.Include("Administrator").Where(Function(d) d.InstructorID = department.InstructorID).FirstOrDefault()
        If duplicateDepartment IsNot Nothing AndAlso duplicateDepartment.DepartmentID <> department.DepartmentID 这n
            Dim errorMessage As String = String.Format("Instructor {0} {1} is already administrator of the {2} department.", duplicateDepartment.Administrator.FirstMidName, duplicateDepartment.Administrator.LastName, duplicateDepartment.Name)
            ModelState.AddModelError(String.Empty, errorMessage)
        End If
    End If
End Sub

在try块中添加代码 httppost编辑 如果没有验证错误,则调用此新方法的方法。 Try块现在看起来像以下示例:

<httppost()>
<ValidateAntiForgeryToken()>
Async Function 编辑(<Bind(Include:="DepartmentID,Name,Budget,StartDate,RowVersion,InstructorID")> ByVal department As 部门) As Task(Of ActionResult)
    Try
        If ModelState.IsValid 这n
            validateoneadministratorassigpmentperinstructor.(department)
        End If
        If ModelState.IsValid 这n
            db.Entry(department).State = EntityState.Modified
            db.SaveChanges()
            返回 RedirectToAction("Index")
        End If
    Catch ex As DbUpdateConcurrencyException
        Dim entry = ex.Entries.Single()
        Dim clientValues = DirectCast(entry.Entity, 部门)

运行部门编辑页面并尝试将部门的管理员更改为已有不同部门管理员的教师。您获得了预期的错误消息:

MVC5与EF6.

现在再次运行部门编辑页面,此时间更改 预算 数量。当你点击 保存,您会看到一个错误页面,结果来自您添加的代码 validateoneadministratorassigpmentperinstructor.:

MVC5与EF6.

异常错误消息是:

附加“contosooniventsity.models.department”类型的实体失败,因为相同类型的另一个实体已经具有相同的主键值。如果使用“附加”方法或将实体的状态设置为“未改变”或“修改”,如果图表中的任何实体具有冲突的键值,则会发生这种情况。这可能是因为某些实体是新的,并且尚未收到数据库生成的键值。在这种情况下,使用“添加”方法或“添加”实体状态跟踪图形,然后将非新实体的状态设置为适当的“不变”或“修改”。

发生这种情况,因为以下事件序列:

  • 编辑方法调用 validateoneadministratorassigpmentperinstructor. 方法,检索将Kim Abercrombive作为管理员的所有部门。这会导致英国部门读取。由于此读取操作,数据库上下文正在跟踪从数据库中读取的英语部门实体。
  • 这 编辑 方法试图设置 修改的 由MVC模型粘合剂创建的英语部门实体上的标志,它隐式导致上下文尝试附加该实体。但上下文无法附加模型粘合剂创建的条目,因为上下文已经跟踪英语部门的实体。

此问题的一个解决方案是将上下文保留从验证查询检索的内存部门实体。这样做没有缺点,因为你不会更新这个实体或以一种将受益于内存中缓存的方式再次读取它。

在  Departmencontroller.vb., 在里面 validateoneadministratorassigpmentperinstructor. 方法,指定无跟踪,如下所示:

Dim duplicateDepartment As 部门 = db.Departments _
                                        .Include("Administrator") _
                                        .AsNoTracking() _
                                        .Where(Function(d) d.InstructorID = department.InstructorID) _
                                        .FirstOrDefault()

重复您尝试编辑 预算 部门的金额。这次操作成功,并且网站将按预期返回到部门索引页面,显示修订后的预算值。

检查发送到数据库的SQL

有时能够看到发送到数据库的实际SQL查询是有用的。在早期的教程中,您在拦截器代码中看到了如何在拦截器代码中进行操作;现在,如果没有编写拦截代码,您会看到某些方法可以进行。要尝试出来,您将查看一个简单的查询,然后在添加选项如此急切加载,过滤和排序时查看它会发生什么。

在  控制器/ Coursecontroller., 更换 指数 具有以下代码的方法,以便暂时停止急于加载:

Function 指数() As ActionResult
    Dim courses = db.Courses
    Dim SQL. = courses.ToString()
    返回 View(courses.ToList())
End Function

现在设置了一个断点 返回 声明(F9带有该线上的光标)。按F5以调试模式运行项目,然后选择课程索引页面。当代码到达断点时,检查SQL变量。您可以看到发送到SQL Server的查询。这是一个简单的 选择 statement.

SELECT 
    [Extent1].[CourseID] AS [CourseID], 
    [Extent1].[Title] AS [Title], 
    [Extent1].[Credits] AS [Credits], 
    [Extent1].[DepartmentID] AS [DepartmentID] 
    FROM [dbo].[Course] AS [Extent1]

单击放大类以查看查询 文本可视化器.

MVC5与EF6.

现在,您将添加一个下拉列表到课程索引页面,以便用户可以过滤特定部门。您将按标题对课程进行排序,您将指定为部门导航属性的急切加载。

在  coursecontroller.vb., 更换 指数 具有以下代码的方法:

Function 指数(ByVal 选择了 As Integer?) As ActionResult
    Dim departments = db.Departments.OrderBy(Function(q) q.Name).ToList()
    ViewBag.SelectedDepartment = New 选择列表(departments, "DepartmentID", "Name", SelectedDepartment)
    Dim departmentID = SelectedDepartment.GetValueOrDefault()

    Dim courses As IQueryable(Of 课程) = db.Courses _
        .Where(Function(c) Not 选择了.HasValue Or c.DepartmentID = departmentID) _
        .OrderBy(Function(d) d.CourseID) _
        .Include(Function(d) d.Department)
    返回 View(courses.ToList())
End Function

恢复断点 返回 statement.

该方法在下拉列表中接收所选值 选择了 范围。如果未选择任何内容,则此参数将为null。

选择列表 包含所有部门的集合将传递给下拉列表的视图。参数传递给 选择列表 构造函数指定值字段名称,文本字段名称和所选项目。

为了 得到 method of the 课程 存储库,代码指定过滤器表达式,排序顺序和急于加载 部门 导航属性。如果在下拉列表中选择任何内容(即, 选择了 is null).

在  视图\ index \ index.vbhtml,立即在打开表标记之前,添加以下代码以创建下拉列表和提交按钮:

@Using Html.BeginForm()
    @<p>选择 Department: @Html.DropDownList("SelectedDepartment","All")
    <input type="submit" value="Filter" /></p>
End Using

MVC5与EF6.

这次第一个断点将为DropTown列表查询部门查询。跳过它并查看 SQL. 可变下次代码到达断点以查看什么 课程 查询现在看起来像。你会看到以下内容:

MVC5与EF6.

您可以看到查询现在是一个 加入 query that loads 部门 data along with the 课程 数据,它包括一个 在哪里 clause.

去除那个 Dim SQL = Courses.Tostring() line.

存储库和工作模式

许多开发人员编写代码,以实现与实体框架合作的代码的包装器作为包装器的存储库和单位。这些模式旨在在数据访问层和应用程序的业务逻辑层之间创建抽象层。实现这些模式可以帮助将应用程序与数据存储的更改绝对,并且可以促进自动单元测试或测试驱动的开发(TDD)。但是,编写额外的代码来实现这些模式并不总是使用EF的应用程序的最佳选择,因为有几个原因:

  • EF Context类本身将您的代码从特定于数据存储的代码中绝缘。
  • EF Context类可以充当使用EF所做的数据库更新的工作类别。
  • EF DBSET类已经是存储库模式的实现。
  • 实体框架6中引入的功能使得在不编写自己的存储库代码的情况下更容易实现TDD。

有关如何实现存储库和工作模式的更多信息,请参阅 本教程系列的实体框架5版本(C#)。有关在实体框架6中实现TDD的方法的信息,请参阅以下资源:

代理课程

当实体框架创建实体实例时(例如,执行查询时),它通常会作为动态生成的派生类型的实例创建,该类型充当实体的代理。例如,请参阅以下两个调试器图像。在第一张图片中,您看到学生变量是预期的学生类型在实体中立即立即。在第二个图像中,在EF已用于从数据库中读取学生实体之后,您可以看到代理类。

MVC5与EF6.

MVC5与EF6.

此代理类覆盖实体的某些虚拟属性以在访问属性时自动插入用于执行操作的挂钩。一个功能此机制用于延迟加载。

大多数时候,您无需了解此代理的使用,但有例外:

  • 在某些情况下,您可能希望防止实体框架创建代理实例。例如,当您序列化实体时,您通常想要POCO类,而不是代理类。避免序列化问题的一种方法是序列化数据传输对象(DTO)而不是实体对象,如图所示 使用Web API与实体框架 教程。另一种方式是  禁用代理创建.
  • 当您使用新运算符实例化实体类时,您就不会获得代理实例。这意味着您没有获得延迟加载和自动更改跟踪等功能。这通常是好的;您通常不需要延迟加载,因为您正在创建一个不在数据库中的新实体,如果您明确标记实体,通常不需要更改跟踪 添加。但是,如果您确实需要延迟加载并且需要更改跟踪,则可以使用使用代理创建新的实体实例 创造 method of the DBSET. class.
  • 您可能希望从代理类型获取实际实体类型。你可以使用 getObjectType. method of the ObjectContext. 获取代理类型实例的实际实体类型的类。

有关更多信息,请参阅 使用代理 on MSDN.

自动变更检测

实体框架通过将实体的当前值与原始值进行比较,确定实体如何改变(以及需要将哪种更新发送到数据库)。查询或附加实体时,将存储原始值。导致自动变化检测的一些方法如下:

  • DBSET..Find
  • DBSET..Local
  • DBSET..Remove
  • DBSET..Add
  • DBSET..Attach
  • dbcontext.savechanges.
  • dbcontext.getValidationErrors.
  • dbcontext.entry.
  • dbchangetracker.entries.

如果您正在跟踪大量实体并且您在循环中多次调用其中一个方法,则可能通过临时关闭自动变更检测来获得显着的性能改进 autodeTectChangeSenabled. 财产。有关更多信息,请参阅 自动检测更改 on MSDN.

自动验证

当你打电话的时候 保存更改 方法,默​​认情况下,实体框架在更新数据库之前验证所有已更改实体的所有属性中的数据。如果您已经更新了大量实体并且您已经验证了数据,则不需要此工作,您可以通过临时关闭验证,使更改更少的更改的过程取得更少的时间。你可以使用它 validateonsaveenabled. 财产。有关更多信息,请参阅 验证 on MSDN.

实体框架电动工具

实体框架电动工具 是一个Visual Studio加载项,它用于创建这些教程中显示的一些数据模型图。该工具还可以根据现有数据库中的表来执行其他功能,例如生成实体类,以便您可以先使用代码的数据库。

笔记:此工具与Web的Visual Studio Express或Visual Studio的任何其他快递版本不兼容。

安装工具后,上下文菜单中会出现一些其他选项。例如,当您右键单击上下文类时 解决方案资源管理器,您可以选择生成图表。当您使用代码首先,您无法在图中更改数据模型,但您可以移动到某些事情以使其更容易理解。

EF在上下文菜单中

EF图

实体框架源代码

实体框架6的源代码可用 http://entityframework.codeplex.com/。除了源代码外,您还可以获得 夜间建立问题跟踪特征规格设计会议备注, 和更多。您可以提交错误,您可以为EF源代码贡献自己的增强功能。

虽然源代码是开放的,但实体框架被完全支持作为Microsoft产品。 Microsoft Intity Framework Team保证已接受哪些贡献并测试所有代码更改以确保每个版本的质量。 

概括

这将在Visual Basic ASP.NET MVC应用程序中使用实体框架完成本系列教程。有关如何使用实体框架使用数据的更多信息,请参阅 MSDN上的EF文档页面 and ASP.NET数据访问 - 推荐资源.