用ASP.NET Core MVC 和 EF Core 构建Web应用 (十)

之前的学习中,已经以每个类一张表的方式实现了继承。 本节将会介绍在掌握开发基础 ASP.NET Core web 应用程序之后使用 Entity Framework Core 开发时需要注意的几个问题。

原生 SQL 查询

使用 Entity Framework 的优点之一是它可避免你编写跟数据库过于耦合的代码 它会自动生成 SQL 查询和命令,使得你无需自行编写。 但有一些特殊情况,你需要执行手动创建的特定 SQL 查询。 对于这些情况下, Entity Framework Code First API 包括直接传递 SQL 命令将到数据库的方法。 在 EF Core 1.0 中具有以下选项:

  • 使用DbSet.FromSql返回实体类型的查询方法。 返回的对象必须是DbSet对象期望的类型,并且它们会自动跟踪数据库上下文中除非你手动关闭跟踪。
  • 对于非查询命令使用Database.ExecuteSqlCommand

如果需要运行该返回类型不是实体的查询,你可以使用由 EF 提供的 ADO.NET 中使用数据库连接。 数据库上下文不会跟踪返回的数据,即使你使用该方法来检索实体类型也是如此。

在 Web 应用程序中执行 SQL 命令时,请务必采取预防措施来保护站点免受 SQL 注入攻击。 一种方法是使用参数化查询,确保不会将网页提交的字符串视为 SQL 命令。 在本教程中,将用户输入集成到查询中时会使用参数化查询。

调用返回实体的查询

DbSet<TEntity> 类提供了可用于执行查询并返回TEntity类型实体的方法。 若要查看实现细节,你需要更改部门控制器中Details方法的代码。

DepartmentsController.cs中的Details方法,通过代码调用FromSql方法检索一个部门,如以下高亮代码所示:

 1 public async Task<IActionResult> Details(int? id)
 2 {
 3     if (id == null)
 4     {
 5         return NotFound();
 6     }
 7
 8     string query = "SELECT * FROM Department WHERE DepartmentID = {0}";
 9     var department = await _context.Departments
10         .FromSql(query, id)
11         .Include(d => d.Administrator)
12         .AsNoTracking()
13         .SingleOrDefaultAsync();
14
15     if (department == null)
16     {
17         return NotFound();
18     }
19
20     return View(department);
21 }

为了验证新代码是否工作正常,请选择Department选项卡,然后点击某个部门的Detail。

调用返回其他类型的查询

之前你在“关于”页面创建了一个学生统计信息网格,显示每个注册日期的学生数量。 可以从学生实体集中获取数据 (_context.Students) ,使用 LINQ 将结果投影到EnrollmentDateGroup视图模型对象的列表。 假设你想要 SQL 本身编写,而不使用 LINQ。 需要运行 SQL 查询中返回实体对象之外的内容。 在 EF Core 1.0 中,执行该操作的另一种方法是编写 ADO.NET 代码,并从 EF 获取数据库连接。

HomeController.cs中,将About方法替换为以下代码:

public async Task<ActionResult> About()
{
    List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
    var conn = _context.Database.GetDbConnection();
    try
    {
        await conn.OpenAsync();
        using (var command = conn.CreateCommand())
        {
            string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
                + "FROM Person "
                + "WHERE Discriminator = ‘Student‘ "
                + "GROUP BY EnrollmentDate";
            command.CommandText = query;
            DbDataReader reader = await command.ExecuteReaderAsync();

            if (reader.HasRows)
            {
                while (await reader.ReadAsync())
                {
                    var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount = reader.GetInt32(1) };
                    groups.Add(row);
                }
            }
            reader.Dispose();
        }
    }
    finally
    {
        conn.Close();
    }
    return View(groups);
}

添加 using 语句:

using System.Data.Common;

运行应用并转到“关于”页面。 显示的数据和之前一样。

调用更新查询

假设 Contoso 大学管理员想要在数据库中执行全局更改,例如如更改的每个课程的可修读人数。 如果该大学有大量的课程,检索所有实体并单独更改会降低效率。 在本节中,你将实现一个页面,使用户能够指定一个参数,通过这个参数可以更改所有课程的可修读人数,在这里你会通过执行 SQL UPDATE 语句来进行更改。 页面外观类似于下图:

在 CoursesContoller.cs 中,为 HttpGet 和 HttpPost 添加 UpdateCourseCredits 方法:

public IActionResult UpdateCourseCredits()
{
    return View();
}
[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
    if (multiplier != null)
    {
        ViewData["RowsAffected"] =
            await _context.Database.ExecuteSqlCommandAsync(
                "UPDATE Course SET Credits = Credits * {0}",
                parameters: multiplier);
    }
    return View();
}

当控制器处理 HttpGet 请求时,ViewData["RowsAffected"]中不会返回任何东西,并且在视图中显示一个空文本框和提交按钮,如上图所示。

当单击Update按钮时,将调用 HttpPost 方法,且从文本框中输入的值获取乘数。 代码接着执行 SQL 语句更新课程,并向视图的ViewData返回受影响的行数。 当视图获取RowsAffected值,它将显示更新的行数。

在“解决方案资源管理器”中,右键单击“Views/Courses”文件夹,然后依次单击“添加”和“新建项”。

在添加新项对话框中,在已安装下单击ASP.NET,在左窗格中,单击MVC 视图页,并将新视图命名为UpdateCourseCredits.cshtml

Views/Courses/UpdateCourseCredits.cshtml中,将模板代码替换为以下代码:

@{
    ViewBag.Title = "UpdateCourseCredits";
}

<h2>Update Course Credits</h2>

@if (ViewData["RowsAffected"] == null)
{
    <form asp-action="UpdateCourseCredits">
        <div class="form-actions no-color">
            <p>
                Enter a number to multiply every course‘s credits by: @Html.TextBox("multiplier")
            </p>
            <p>
                <input type="submit" value="Update" class="btn btn-default" />
            </p>
        </div>
    </form>
}
@if (ViewData["RowsAffected"] != null)
{
    <p>
        Number of rows updated: @ViewData["RowsAffected"]
    </p>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

通过选择Courses选项卡运行UpdateCourseCredits方法,然后在浏览器地址栏中 URL 的末尾添加"/ UpdateCourseCredits"到 (例如: http://localhost:5813/Courses/UpdateCourseCredits)。 在文本框中输入数字:

单击Update。 你会看到受影响的行数:

单击Back To List可以看到课程列表,其中可修读人数已经替换成修改后的数字。

请注意生产代码将确保更新最终得到有效的数据。 此处所示的简化代码会使得相乘后可修读人数大于 5。 (Credits属性具有[Range(0, 5)]特性。)更新查询将起作用,但无效的数据会导致意外的结果,例如在系统的其他部分中加入可修读人数为 5 或更少可能会导致意外的结果。

检查发送到数据库的 SQL

有时能够以查看发送到数据库的实际 SQL 查询对于开发者来说是很有用的。 EF Core 自动使用 ASP.NET Core 的内置日志记录功能来编写包含 SQL 查询和更新的日志。

打开StudentsController.cs并在Details方法的if (student == null)语句上设置断点。

在调试模式下运行应用,并转到某位学生的“详细信息”页面。

转到输出窗口显示调试输出,就可以看到查询语句:

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (56ms) [Parameters=[@__id_0=‘?‘], CommandType=‘Text‘, CommandTimeout=‘30‘]
SELECT TOP(2) [s].[ID], [s].[Discriminator], [s].[FirstName], [s].[LastName], [s].[EnrollmentDate]
FROM [Person] AS [s]
WHERE ([s].[Discriminator] = N‘Student‘) AND ([s].[ID] = @__id_0)
ORDER BY [s].[ID]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (122ms) [Parameters=[@__id_0=‘?‘], CommandType=‘Text‘, CommandTimeout=‘30‘]
SELECT [s.Enrollments].[EnrollmentID], [s.Enrollments].[CourseID], [s.Enrollments].[Grade], [s.Enrollments].[StudentID], [e.Course].[CourseID], [e.Course].[Credits], [e.Course].[DepartmentID], [e.Course].[Title]
FROM [Enrollment] AS [s.Enrollments]
INNER JOIN [Course] AS [e.Course] ON [s.Enrollments].[CourseID] = [e.Course].[CourseID]
INNER JOIN (
    SELECT TOP(1) [s0].[ID]
    FROM [Person] AS [s0]
    WHERE ([s0].[Discriminator] = N‘Student‘) AND ([s0].[ID] = @__id_0)
    ORDER BY [s0].[ID]
) AS [t] ON [s.Enrollments].[StudentID] = [t].[ID]
ORDER BY [t].[ID]

你会注意到一些可能会让你觉得惊讶的操作: SQL 从 Person 表最多选择 2 行 (TOP(2)) 。 SingleOrDefaultAsync方法服务器上不会解析为 1 行。 原因是:

  • 如果查询返回多个行,该方法会返回 null。
  • 如果想知道查询是否返回多个行,EF 必须检查是否至少返回 2。

请注意,你不必使用调试模式,并在断点处停止,然后在输出窗口获取日志记录。 这种方法非常便捷,只需在想查看输出时停止日志记录即可。 如果不进行此操作,程序将继续进行日志记录,要查看感兴趣的部分则必须向后滚动。

存储库和工作单元模式

许多开发人员编写代码实现存储库和工作模式单元以作为使用 Entity Framework 代码的包装器。 这些模式用于在应用程序的数据访问层和业务逻辑层之间创建抽象层。 实现这些模式可让你的应用程序对数据存储介质的更改不敏感,而且很容易进行自动化单元测试和进行测试驱动开发 (TDD)。 但是,编写附加代码以实现这些模式对于使用 EF 的应用程序并不总是最好的选择,原因有以下几个:

  • EF 上下文类可以为使用 EF 的数据库更新充当工作单位类。
  • 对于使用 EF 进行的数据库更新,EF 上下文类可充当工作单元类。
  • EF 包括用于无需编写存储库代码就实现 TDD 的功能。

自动脏值检测

Entity Framework 通过比较的实体的当前值与原始值来判断更改实体的方式 (因此需要发送更新到数据库)。查询或附加该实体时会存储的原始值。 如下方法会导致自动脏值:

  • DbContext.SaveChanges
  • DbContext.Entry
  • ChangeTracker.Entries

如果正在跟踪大量实体,并且这些方法之一在循环中多次调用,通过使用ChangeTracker.AutoDetectChangesEnabled属性暂时关闭自动脏值检测,能够显著改进性能。 例如:

_context.ChangeTracker.AutoDetectChangesEnabled = false;

Entity Framework Core 源代码与开发计划

Entity Framework Core 源位于 https://github.com/aspnet/EntityFrameworkCore。 仓库中除了有源代码,还可包括每夜生成、 问题跟踪、 功能规范、 设计会议备忘录和将来的开发路线图。 你可以归档或查找 bug 并进行更改。

尽管源代码处于开源状态, Entity Framework Core 是由 Microsoft 完全支持的产品。 Microsoft Entity Framework 团队将控制接受哪些贡献和测试所有的代码更改,以确保每个版本的质量。

现有数据库逆向工程

若想要通过对现有数据库中的实体类反向工程得出数据模型,可以使用scaffold-dbcontext

使用动态 LINQ 来简化对所选内容排序的代码

本系列的第三节演示如何通过在switch语句中对列名称进行硬编码来编写 LINQ 。 如果只有两列可供选择,这种方法可行,但是如果拥有许多列,代码可能会变得冗长。 要解决该问题,可使用 EF.Property 方法将属性名称指定为一个字符串。 要尝试此方法,请将 StudentsController 中的 Index 方法替换为以下代码。

 public async Task<IActionResult> Index(
     string sortOrder,
     string currentFilter,
     string searchString,
     int? page)
 {
     ViewData["CurrentSort"] = sortOrder;
     ViewData["NameSortParm"] =
         String.IsNullOrEmpty(sortOrder) ? "LastName_desc" : "";
     ViewData["DateSortParm"] =
         sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" : "EnrollmentDate";

     if (searchString != null)
     {
         page = 1;
     }
     else
     {
         searchString = currentFilter;
     }

     ViewData["CurrentFilter"] = searchString;

     var students = from s in _context.Students
                    select s;

     if (!String.IsNullOrEmpty(searchString))
     {
         students = students.Where(s => s.LastName.Contains(searchString)
                                || s.FirstMidName.Contains(searchString));
     }

     if (string.IsNullOrEmpty(sortOrder))
     {
         sortOrder = "LastName";
     }

     bool descending = false;
     if (sortOrder.EndsWith("_desc"))
     {
         sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
         descending = true;
     }

     if (descending)
     {
         students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
     }
     else
     {
         students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
     }

     int pageSize = 3;
     return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(),
         page ?? 1, pageSize));
 }

常见错误

ContosoUniversity.dll 被另一个进程使用

错误消息:

无法打开“...bin\Debug\netcoreapp1.0\ContosoUniversity.dll‘ for writing -- ”进程无法访问文件“...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll”,因为它正在由其他进程使用。

解决方案:

停止 IIS Express 中的站点。 请转到 Windows 系统任务栏中,找到 IIS Express 并右键单击其图标、 选择 Contoso 大学站点,然后单击停止站点。

迁移基架的 Up 和 Down 方法中没有代码

可能的原因:

EF CLI 命令不会自动关闭并保存代码文件。 如果在运行migrations add命令时,你未保存更改,EF 将找不到所做的更改。

解决方案:

运行migrations remove命令,保存你更改的代码并重新运行migrations add命令。

运行数据库更新时出错

在有数据的数据库中进行架构更改时,很有可能发生其他错误。 如果遇到无法解决的迁移错误,你可以更改连接字符串中的数据库名称,或删除数据库。 若要迁移,创建新的数据库,在数据库尚没有数据时使用更新数据库命令更有望完成且不发生错误。

最简单方法是在appsettings.json中重命名数据库。 下次运行database update时,会创建一个新数据库。

若要在 SSOX 中删除数据库,右键单击数据库,单击删除,然后在删除数据库对话框框中,选择关闭现有连接,单击确定。

若要使用 CLI 删除数据库,可以运行database dropCLI 命令:

dotnet ef database drop

定位 SQL Server 实例出错

错误消息:

建立到 SQL Server 的连接时出现与网络相关或特定于实例的错误。 未找到或无法访问服务器。 请验证实例名称是否正确,SQL Server 是否已配置为允许远程连接。 (提供程序:SQL 网络接口,错误:26 - 定位指定的服务器/实例时出错)

解决方案:

请检查连接字符串。 如果你已手动删除数据库文件,更改数据库的构造字符串中数据库的名称,然后从头开始使用新的数据库。

*****************************
*** Keep learning and growing. ***
*****************************

原文地址:https://www.cnblogs.com/gangle/p/9246100.html

时间: 2024-10-06 08:05:42

用ASP.NET Core MVC 和 EF Core 构建Web应用 (十)的相关文章

用ASP.NET Core MVC 和 EF Core 构建Web应用 (一)

系统必备 .NET Core 2.0.0 SDK 或更高版本. 已安装 ASP.NET 和 Web 开发工作负载的 Visual Studio 2017 15.3 版或更高版本. 创建Web应用程序 打开 Visual Studio 并创建一个新 ASP.NET Core C# web 项目名为"ContosoUniversity". 从文件菜单上,选择新建 > 项目. 从左窗格中,选择已安装 > Visual C# > Web. 选择"ASP.NET Co

Building a Web App with ASP.NET Core, MVC, Entity Framework Core, Bootstrap, and Angular

Since I have spent many years on Windows Application development in my first three years of software career.  I was interested in the C#, had tried to understand the basic knowledge about C#. The programs, the patterns, the object-oriented methodolog

【从0开始.NET CORE认证】-2 使用.Net Core Identity和EF Core

回顾 朋友们,距离上次从0开始.NET CORE认证-1发布已经过去一周了,上次第一篇文章,其实并没有涉及到Net Core Identity,就是简单的搭了一个项目,让大家对Identity中各种术语有个理解,明白他们出现的位置,已经他们出现能够达到某种功能.以及出现的位置顺序不同,则会出现什么不同的情况. 回顾一下上次写的主要的知识点 Authentication和Authorization 是什么,怎么解释他们 Claim和ClaimType又是什么,能举例子说明吗? ClaimsIden

EF Core 快速上手——EF Core 入门

EF Core 快速上手--EF Core 介绍 本章导航 从本书你能学到什么 对EF6.x 程序员的一些话 EF Core 概述 1.3.1 ORM框架的缺点 第一个EF Core应用 ??本文是对<Entity framework in action>部分章节的翻译,某些场景也会附上笔者实践的Demo.尽管很认真的斟酌,但是水平有限,还请各位批评和斧正. ??Entity Framework Core, 或者 EF Core,是一个方便软件工程师访问数据库的库.有很多方法来构建这样的一个库

在ASP.NET Core中通过EF Core实现一个简单的全局过滤查询

前言 不知道大家是否和我有同样的问题: 一般在数据库的设计阶段,会制定一些默认的规则,其中有一条硬性规定就是一定不要对任何表中的数据执行delete硬删除操作,因为每条数据对我们来说都是有用的,并且是值得分析的. 所以我们一般会在每张表中加一个"是否删除IsDeleted"或者"是否有效IsValid"的字段,来标识这条数据的状态是否可用! 那么疑问来了,在写SQL或者Linq的时候我们到底是要加上这个条件还是忽略这个条件呢?答案当然是根据实际业务需求和情况来决定.

EF Core 2.0使用MsSql/Mysql实现DB First和Code First

参考地址 Entity Framework官网 ASP.NET Core MVC 和 EF Core - 教程系列 环境 Visual Studio 2017 最新版本的.NET Core 2.0 SDK 最新版本的 Windows PowerShell 开始搭建 1.在 Visual Studio 2017 中创建新项目 "文件">"新建">"项目" 从左侧菜单中选择"已安装">"模板"

ASP.NET Core 开发-Entity Framework (EF) Core 1.0 Database First

ASP.NET Core 开发-Entity Framework Core 1.0 Database First,ASP.NET Core 1.0 EF Core操作数据库. Entity Framework Core 1.0 也已经发布了,可以适用于 .NET Core 1.0 及ASP.NET Core 1.0 . EF Core RC2 时,使用的Code First: http://www.cnblogs.com/linezero/p/EntityFrameworkCore.html E

asp.net core ef core mysql 新增数据并发异常处理

net core 2.0发布后,一直想体验下,因种种原因,一直在拖着没进行. 前阵子公司要加个新的内部管理后台,正好可以用asp.net core来做下,体验下net core的魅力. 啃过文档后就上手了,一切很顺利. 直到周五,出现了一个并发异常的问题,本以为可以很快处理掉的,但没想到一直花费了很长时间才解决掉,现在记录下情况,有相同经历的伙伴以后可以参考. 先上异常截图. 异常提示: Microsoft.EntityFrameworkCore.DbUpdateConcurrencyExcep

EF core (code first) 通过自定义 Migration History 实现多租户使用同一数据库时更新数据库结构

前言 写这篇文章的原因,其实由于我写EF core 实现多租户的时候,遇到的问题. 具体文章的链接: Asp.net core下利用EF core实现从数据实现多租户(1) Asp.net core下利用EF core实现从数据实现多租户(2) : 按表分离   (主要关联文章) 这里我遇到的最主要问题是:由于多租户的表使用的是同一个数据库.由于这个原因,无法通过 Database.EnsureCreated() 自动创建多个结构相同但名字不同的表. 所以我在文中提到,需要自己跑脚本去创建多有的