《Entity Framework 6 Recipes》中文翻译系列 (12) -----第三章 查询之使用SQL语句
翻译的初衷以及为什么选择《Entity Framework 6 Recipes》来学习,请看本系列开篇
3-2使用原生SQL语句更新
问题
你想在实体框架中使用原生的SQL语句,来更新底层数据存储。
解决方案
假设你有一张如图3-2所示的Payment数据库表,使用实体框架设计器工具创建了一个如图3-2所示的模型。

图3-2 Payment表,包含一个供应商的付款信息

图3-3 包含一个Payment实体的模型
为了在底层的Payment表中执行一句和多句SQL语句,可以使用在DbContext类中的属性Database中的ExecuteSQlCommand()方法。 虽然我们能在模型中查询Payment实体,ExecuteSqlCommand方法能让我们直接查询底层的数据库,放弃实体框架的某些特性,比如变化跟踪。我们需要一个简单的模型对象,它包含一个用于执行SQL命令的上下文对象。
下面代码清单3-4执行一句或多句SQL语句
代码清单3-4 执行查询语句
// 删除之前的测试数据
using (var context = new EFRecipesEntities())
{
context.Database.ExecuteSqlCommand("delete from chapter3.payment");
}
//插入两行数据
using (var context = new EFRecipesEntities())
{
var sql = @"insert into Chapter3.Payment(Amount, Vendor)
values (@Amount, @Vendor)"; //这里可以使用@p0这样的参数占位符,ado.net为自动为我们创建参数对象
var parameters = new DbParameter[]
{
new SqlParameter {ParameterName = "Amount", Value = 99.97M},
new SqlParameter {ParameterName = "Vendor", Value = "Ace Plumbing"}
}; var rowCount = context.Database.ExecuteSqlCommand(sql, parameters); parameters = new DbParameter[]
{
new SqlParameter {ParameterName = "Amount", Value = 43.83M},
new SqlParameter
{
ParameterName = "Vendor",
Value = "Joe's Trash Service"
}
}; rowCount += context.Database.ExecuteSqlCommand(sql, parameters);
Console.WriteLine("{0} rows inserted", rowCount.ToString());
} // 获取并显示数据
using (var context = new EFRecipesEntities())
{
Console.WriteLine("Payments");
Console.WriteLine("========");
foreach (var payment in context.Payments)
{
Console.WriteLine("Paid {0} to {1}", payment.Amount.ToString(),
payment.Vendor);
}
} Console.WriteLine("\nPress <enter> to continue...");
Console.ReadLine();
代码清单3-4的输出如下:
rows inserted
Payments
========
Paid $99.97 to Ace Plumbing
Paid $43.83 to Joe's Trash Service
在代码清单3-4中,我们先删除之前的测试数据,然后使用在DbContext类中的属性Database中的ExecuteSQlCommand()方法,请注意这里是如何把一个原生的SQL语句传给这个方法的。
然后,我们创建一个字符串形式的SQL插入语句,这个语句包含了两个参数@Amout和@Vendor。它们只是一个占位符,当SQL语句执行时,会被具体的值给替换掉。
接下来,我们创建了两个DbParameter类型的参数对象,它们把参数占位符和具体的值绑定在一起。第一个插入,我将值99.97绑定到了占位符Amount上,将值"Ace Plumbing"绑定到占位符Vendor上。随后,我们创建了另一个记录。这里需要注意,这两个参数对象是如何分配到一个DbParameter类型的数组上的。为了执行SQL语句,我们传递包含SQL语句的字符串和DbParameter类型的参数数组给方法ExecuteSqlCommand(),方法ExecueSqlCommand返回SQL语句所影响的行数。在我们的示例中,我们每次调用ExecuteSqlCommand()方法插入一行数据。
如果你的SQL语句没有任何参数,ExecuteSqlCommand方法有另一个重载方法,它只包含一个接收字符串形式的SQL语句参数。
代码清单3-4中的模式跟我们在ADO.NET框架中使用SqlClient对象查询数据的方式有点相同。不同的是,我们不需要构造一个连接字符串和显式打开一个数据库连接。实体框架上下文对象会自动完成这项工作。需要注意的是,这里有两个版本的上下文对象:实体框架5、6和4.x中用于Code-First的DbContext上下文对象,和早期版本中的ObjectContext上下文对象。
需要记住的是,DbContext只是一个简单的包装器或者 “Façade,”(译注:这是法语,”外观模式“)它包装了ObjectContext上下文对象,使上下文对象更直接和更易于使用。ObjectContext的所有功能仍然有效。
表示SQL命令和参数的方式也有不同。在ADO.NET中的ExecuteNonQuery()方法中,命令文本和参数被设置到Command对象上,但在实体框架中传递给ExecuteSqlCommand()方法的是简单类型的参数。
细心的读者可能已经注意到(这是重点),我们没有查询模型。事实上,正如我们前面提到的那样,我们不需要如图3-3所示的Payment实体。方法ExceuteSqlCommand()只使用DbContext上下文对象,以及连接字符串。
最佳实践
使用参数化SQL语句和不使用参数化SQL语句是一个问题.....。你应该使用参数化的SQL语句,还是动态创建字符串形式的SQL语句呢?最佳实践是,尽可能地使用参数化的SQL语句。原因如下:
1、参数化SQL语句能帮助阻止SQL注入攻击。如果你使用用户界面上文本框控件中的输入字符,通过动态方式拼接SQL语句。那么你可能无意中就把自己暴露给了SQL注入语句,它会严重地损害你的数据库和泄露敏感信息。当你使用参数化的SQL语句时,它会帮你阻止这种情况的发生。
2、如果我们在示例中所示,参数化SQL语句,允许重用SQL语句。重用能让我们的代码更加简洁且易于阅读。
3、很多企业级数据库,像Oracle、DB2以及SQL Server 在某些情况下,它们能凭借参数化查询,重用被解析过的查询语句,即使是参数发生了改变。通过重用,提升了数据库的性能和降低了处理过程。
4、参数化的SQ语句能让代码更有可维护性和可配置性。例如,SQL语句可以来至配置文件中,这可以让你不修改代码就能改变应用程序。
3-3使用原生SQL语句获取对象
问题
你想使用原生SQL语句从数据库获取对象。
解决方案
假设你有如图3-4所示的一个拥有Student实体类型的模型。

图3-4 一个拥有Student实体类型的模型
你想通过执行原生的SQL语句返回实体类型Student的实例集合。正如在前面小节中看到的那样,实体框架中的ExecuteSqlCommand()方法和ADO.NET中SQLCommand的ExcuteNonQuery方法有点相似。它执行给定的SQL语句,返回受影响的行数。为了让实体框架实现无类型数据到强类型的实体转换,我可以使用方法SqlQuery()。
作为开始,本示例凭借实体框架中的Code-First。代码清单3-5,创建了一个Student实体类。
代码清单3-5 Student 实体类
public class Student
{
public int StudentId { get; set; }
public string Degree { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
接下来,代码清单3-6,创建了用于Code-First的上下文对象。
代码清单3-6 上下文对象DbContext
public class EFRecipesEntities : DbContext
{
public EFRecipesEntities()
: base("ConnectionString")
{
} public DbSet<Student> Students { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().ToTable("Chapter3.Student");
base.OnModelCreating(modelBuilder);
}
}
为了执行SQL语句以及返回实体类型Student的实例集合,使用代码清单3-7的模式。
代码清单3-7 使用ExecuteStoreQuery()(译注:应该是使用SqlQuery()方法)方法执行SQL语句并返回对象。
using (var context = new EFRecipesEntities())
{
// 删除出测试数据
context.Database.ExecuteSqlCommand("delete from chapter3.student"); // 插入数据
context.Students.Add(new Student
{
FirstName = "Robert",
LastName = "Smith",
Degree = "Masters"
});
context.Students.Add(new Student
{
FirstName = "Julia",
LastName = "Kerns",
Degree = "Masters"
});
context.Students.Add(new Student
{
FirstName = "Nancy",
LastName = "Stiles",
Degree = "Doctorate"
});
context.SaveChanges();
} using (var context = new EFRecipesEntities())
{
var sql = "select * from Chapter3.Student where Degree = @Major";
var parameters = new DbParameter[]
{
new SqlParameter {ParameterName = "Major", Value = "Masters"}
};
var students = context.Database.SqlQuery<Student>(sql, parameters);
Console.WriteLine("Students...");
foreach (var student in students)
{
Console.WriteLine("{0} {1} is working on a {2} degree",
student.FirstName, student.LastName, student.Degree);
}
} Console.WriteLine("\nPress <enter> to continue...");
Console.ReadLine();
}
代码清单3-7的输出如下:
Students...
Robert Smith is working on a Masters degree
Julia Kerns is working on a Masters degree
原理
在代码清单3-7中,我们添加了3个Students到DbContext上下文中并使用SaveChanges()方法保存到数据库。
为了获取正在攻读硕士学位的学生,我们使用了SqlQuery()方法、一个参数化的SQL语句,一个被设置成“Masters.“值的参数。我们枚举返回的Stuendts集合并打印输出。注意相关的上下文对象为这些值实现的变化跟踪。
这里在查询语句中使用“*”表示所有的列名,实体框架会将返回的列匹配到合适的属性上。一般情况下,这会工作得很好。但是,查询中只有部分列返回时,实体框架会在实例化对象时抛出一个异常。一个更好的方法和最佳实践是,在你的查询语句中显式枚举所有列(也就是说,指定所有的列名)。
如果你的SQL语句返回的列多于实例化实体所需数量(也就是说,列值数量多于实体对象属性数量),实体框架会忽略掉多于的列。如果你仔细想想,这不是一个令人满意的行为。再一次重申,在SQL语句中显式枚举你所期望返回的列名,确保它们与实体类型匹配。
SqlQuery()方法有很多限制,如果你在使用TPH继承映射,你的SQL语句返回的行要映射到不同的派生类型上,实体框架不能使用鉴别列来将行映射到正确的派生类型。你可能会得到一个运行时异常,因为行中可能不包含正在实例化类型所需的值。
有趣的是,我们可以使用SqlQuery()方法实例化根本就不是实体的类型。例如,我们创建一个StudentName类,它只包含姓,和名两个属性民。如果我们的SQL语句也只返回这两个列,我们可以使用SqlQuery<StudentName>()方法和指定的SQL语句获取类型StudentName的实例集合。
我们很小心地使用短语,SQL语句,而不是查询语句,是因为SqlQuery()方法可以接受任何返回行集合的SQL语句。这当然包含查询语句,但也包含执行存储过程的SQL语句。
实体框架交流QQ群: 458326058,欢迎有兴趣的朋友加入一起交流
谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/
《Entity Framework 6 Recipes》中文翻译系列 (12) -----第三章 查询之使用SQL语句的更多相关文章
- 《Entity Framework 6 Recipes》中文翻译系列 (11) -----第三章 查询之异步查询
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第三章 查询 前一章,我们展示了常见数据库场景的建模方式,本章将向你展示如何查询实体 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (17) -----第三章 查询之分页、过滤和使用DateTime中的日期部分分组
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-12 分页和过滤 问题 你想使用分页和过滤来创建查询. 解决方案 假设你有如图3 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (13) -----第三章 查询之使用Entity SQL
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-4使用实体SQL查询模型 问题 你想通过执行Entity SQL语句来查询你的实 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (14) -----第三章 查询之查询中设置默认值和存储过程返回多结果集
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-6在查询中设置默认值 问题 你有这样一个用例,当查询返回null值时,给相应属性 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (15) -----第三章 查询之与列表值比较和过滤关联实体
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-8与列表值比较 问题 你想查询一个实体,条件是给定的列表中包含指定属性的值. 解 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (16) -----第三章 查询之左连接和在TPH中通过派生类排序
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-10应用左连接 问题 你想使用左外连接来合并两个实体的属性. 解决方案 假设你有 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (18) -----第三章 查询之结果集扁平化和多属性分组
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-14 结果集扁平化 问题 你有一对多关联的两个实体,你想通过一个查询,获取关联 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (19) -----第三章 查询之使用位操作和多属性连接(join)
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-16 过滤中使用位操作 问题 你想在查询的过滤条件中使用位操作. 解决方案 假 ...
- 《Entity Framework 6 Recipes》翻译系列 (1) -----第一章 开始使用实体框架之历史和框架简述
微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF ...
随机推荐
- Linux service命令
service命令(其实与其说是命令,不如说是脚本),因为service本身就是一个脚本,这个脚本在/sbin路径下,待后续shell脚本功底好了将去认真去看看这个脚本的内容(可不能被人忽悠了.) s ...
- 优化Web中的性能
优化Web中的性能 简介 web的优化就是一场阻止http请求最终访问到数据库的战争. 优化的方式就是加缓存,在各个节点加缓存. web请求的流程及节点 熟悉流程及节点,才能定位性能的问题.而且优化的 ...
- SQL 语句与性能之联合查询和联合分类查询
select * from t1 left join t2 on t2.sysno =t1.ASysNo left join t3 on t3.sysno =t2.ASysNo left join t ...
- SQL常见优化Sql查询性能的方法有哪些?
常见优化Sql查询性能的方法有哪些? 1.查询条件减少使用函数,避免全表扫描 2.减少不必要的表连接 3.有些数据操作的业务逻辑可以放到应用层进行实现 4.可以使用with as 5.使用“临时表”暂 ...
- Codeforces Round #388 (Div. 2) - C
题目链接:http://codeforces.com/contest/749/problem/C 题意:给定一个长度为n的D/R序列,代表每个人的派别,然后进行发表意见,顺序是从1到n.每个人到他的回 ...
- c# 如何中List<object>中去掉object对象中的重复列数据?
//去掉重复 var title = modelList.GroupBy(m => m.Title.ToLower().Trim()).Select(m => new { ID = m.F ...
- 代替jquery $.post 跨域提交数据的N种形式
跨域的N种形式: 1.直接用jquery中$.getJSON进行跨域提交 优点:有返回值,可直接跨域: 缺点:数据量小: 提交方式:仅get (无$.postJSON) $.getJSON(" ...
- java中注解的使用与实例 (二)
java 注解,从名字上看是注释,解释.但功能却不仅仅是注释那么简单.注解(Annotation) 为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后 某个时刻方便地使用这些数据(通过 解 ...
- 解决Xcode真机测试时ineligible devices的问题
升级了Xcode到6.3,连接真机测试时,出现不能选择设备.如图: 设备系统版本是8.3的,Xcode连接其他低系统版本的设备做真机测试时就不会有这个问题. 有人说这是Xcode6.3的bug. 我的 ...
- ubuntu常用命令大全
一.文件/文件夹管理 ls 列出当前目录文件(不包括隐含文件) ls -a 列出当前目录文件(包括隐含文件) ls -l 列出当前目录下文件的详细信息 cd .. 回当前目录的上一级目录 cd - 回 ...