查缺补漏系统学习 EF Core 6 - 原始 SQL 查询

这是 EF Core 系列的第五篇文章,上一篇文章盘点了 EF Core 中的几种数据查询方式。

但是有有时候,我们可能无法用标准的 LINQ 方法完成查询任务。

或者编译后的 LINQ 查询,没有我们想要的那么高效;又或者我们想要调用一个存储过程。.

这些情况下,我们希望可以编写原始 SQL 语句,让 EF Core 去执行。

因此,这篇文章就简要的讲一讲如何在 EF Core 中使用原始 SQL 语句进行查询。

FromSqlRaw

让我们来看一个简单的示例:

var account = 
  _context.Accounts
    .FromSqlRaw(@"SELECT * FROM Account WHERE Name = {0}", "Zilor")
    .FirstOrDefault();

FromSqlRaw 方法允许我们将原始 SQL 语句,添加到 EF Core 查询中。

还可以执行存储过程:

var account = 
  _context.Accounts
    .FromSqlRaw("EXECUTE dbo.MyCustomProcedure")
    .ToList();

需要注意的是 FromSqlRaw 方法有一些限制:

  • 结果中的列名,必须与属性被映射到的列名相匹配

  • 查询必须为实体或查询类型的所有属性返回数据

  • SQL 查询不能包含导航关系,但我们总是可以把 FromSqlRaw 和 Include 方法结合起来。

如果想在查询中包含导航关系,可以这样做:

var account = 
  _context.Accounts
    .FromSqlRaw("SELECT * FROM Account WHERE Name = {0}", "Zilor")
    .Include(e => e.AccountSubjects)
    .FirstOrDefault();

ExecuteSqlRaw

FromSqlRaw 可以执行原始 SQL 语句查询,但不能执行插入、删除、更新等 SQL 语句,这需要使用 ExecuteSqlRaw 方法实现,比如这样:

var rowsAffected = 
  _context.Database
    .ExecuteSqlRaw(
    @"UPDATE Account
    SET Age = {0} 
    WHERE Name = {1}",
    20, "Zilor");

这个方法会返回受影响的行数,无论是从数据库中更新、插入还是删除记录,行为都一样。

需要注意是,这里我们使用了 Database 属性,来调用 ExecuteSqlRaw 方法

而在之前的例子中,我们都是使用 Account 属性,来调用 FromSqlRaw 方法。

另一件重要的事情是,我们在 FromSqlRaw 和 ExecuteSqlRaw 方法中,都使用了查询字符串插值功能。

它允许我们在查询字符串中放置一个变量名,然后 EF Core 会检查这些参数。

检查参数的目的,是为了以防止 SQL 注入攻击。

因此,我们不能在 EF Core 原始 SQL 查询方法之外,使用字符串插值组装 SQL 语句。因为,这样会失去 SQL 参数注入攻击的检测。

虽然直接执行 SQL 语句的方式比较直接,但缺点就是在代码中直接操作数据库的方式,不符合 ORM 的编程模式与思想。

如果我们直接操作数据库表,那么就无法利用 EF Core 强类型的特性。

如果实体模型发生改变,那么必须手动变更 SQL 语句。

而且如果调用了一些某些数据库特有的语法和函数,那么一旦程序迁移到其他数据库,就可能需要重新编写 SQL 语句。

这样也就无法利用 EF Core 强大的 SQL 翻译机制,来屏蔽不同底层数据库的差异。

所以,在能不用的情况下,最好不要使用执行原生 SQL 语句的功能。

重新加载

假设我们有一个已经加载的实体,然后使用 ExecuteSqlRaw 方法,对数据库中的实体做了一些修改,那此时我们加载的实体肯定是过时。

比如这样:

var accountForUpdate =
  _context.Accounts
    .FirstOrDefault(s => s.Name.Equals("Zilor"));

var rowsAffected =
  _context.Database
    .ExecuteSqlRaw(
    @"UPDATE Account
    SET Age = {0}
    WHERE Name = {1}",
    22, accountForUpdate.Name);

只要我们执行这个查询,数据库中的 Age 就会变成 22

但 accountForUpdate 对象中的 Age 属性则不会改变,尽管它在数据库中已经被改变了。

所以,现在的问题是,如果我们想让它在执行 ExecuteSqlRaw 方法后,实体对象的值随之改变,该怎么办?。

其实很简单,我们只需要在 SQL 语句执行完成后,使用 Reload 方法重新加载这个实体即可:

_context.Entry(accountForUpdate).Reload();

小结

这篇文章主要讲了 EF Core 的原始 SQL 语句查询,下篇文章讲继续讲述 EF Core 的数据修改。