关于网上Entity Framework知识点
什么是EF?
EF是一种ORM(Object-relational mapping)框架,它能把我们在编程时使用对象映射到底层的数据库结构。比如,你可以在数据库中建立一个Order表,让它与程序中的Order类建立映射关系,这样一来,程序中的每个Order对象都对应着Order表中的一条记录,ORM框架负责把从数据库传回的记录集转换为对象,也可以依据对象当前所处的具体状态生成相应的SQL命令发给数据库,完成数据的存取工作(常见的数据存取操作可简称为CRUD:Create、Read、Update、Delete)。
EF的三种开发方式
EF支持三种开发模式: Code First、Database First和Model First。
方式一:Code First
对于初次接触的人,EF的Code First实在很有点魔幻色彩。下面就让我们来体会一下。 创建两个类:Book(书)和BookReview(书评)。一本书可以有多条书评,因此,它们是一对多的关系:
- public class Book
- {
- public virtual int Id {get; set;}
- public virtual string Name { get; set; }
- public virtual List<BookReview> Reviews { get; set; }
- }
- public class BookReview
- {
- public int Id{get; set;}
- public int BookId { get; set; }
- public virtual string Content { get; set; }
- public virtual Book AssoicationWithBook { get; set; }
- }
好了,现在创建一个派生自DbContext的子类:
- public class BookDb : DbContext
- {
- public DbSet<Book> Books { get; set; }
- public DbSet<BookReview> Reviews { get; set; }
- }
现在可以在程序中随意写几行代码从数据库中提取数据:
- static void Main(string[] args)
- {
- using (var context = new BookDb())
- {
- Console.WriteLine("数据库中有{0}本书",context.Books.Count());
- }
- }
运行一下,如果计算机上安装有SqlExpress,那么或者是在应用程序文件夹,或者是打开SQL Server Management Studio(SSME)查看本机SQLServer,你就会发现,数据库己经创建好,其中的表及表的关联也帮助你完成了:
运行结果
貌似我什么也没干,一切就OK了!
方式二:Database First
这是EF从1.0开始就支持的特性,其思路是:先设计并建好数据库,然后使用Visual Studio的向导创建EF数据模型并生成实体类代码。 这是最成熟稳定的方式,其设计器相当地完善,基本上能满足实际开发中的各种需求。 我个人认为这是开发正式项目最合适的方式。
方式三:Model First
这种模式是先在可视化设计器中创建实体和它们间的关联,然后设计器生成SQL命令并保存于一个SQL文件中,通过执行这一SQL文件完成数据库的创建和修改工作。
模型设计器
PS:这种方式在EF7中好像以被取消,所以也不推荐再使用了。
上面部分内容摘录自bitfan的专栏
Entity Framework走马观花之把握全局
比较CodeFirst和Database First
通过Code First构建数据库虽然方便,不过EF帮我们生成的数据表结构真的很一般。在上面的例子中,在Code First生成的表内Name、Content、BookId都是可以为空的,也就是说没有为它们建立约束。可要是使用Code First来建立约束就有点麻烦了,所以我们一般都不是通过代码来生成数据库,而是通过数据库来反向生成代码(Database First)。
这一节只是做个介绍,下一节我将演示一下如何使用EF连接数据库,特别是MySQL数据库。EF通过向导连接SQL Server基本上都是下一步,可连MySQL还需要些配置。
使用EntityFramework6连接MySQL
不是微软的亲儿子这待遇就是不一样,其中的坑可真实不少,第一次连MySQL足足折腾了我大半天。
废话不多说直接开始。
安装MySQL
从官网上下载最新版,下载好以后直接安装就可以了。最新版的MySQL已经包含了所有开发需要的功能(包括MySQL连接器),相信会用MySQL,怎么装应该都没有问题,这里就不啰嗦了。不过有一点值得提醒的是,在选择要安装的内容时记得将所有功能都装上,之后出现问题多半都是哪个模块漏装了。
安装好之后,在开始菜单中找到MySQL Workbench,这是MySQL自带的一款管理工具,个人也比较喜欢,一直用的也都是这个。以前要单独安装,不过现在自带了。
创建数据库
为了演示我先创建一个叫test的数据库,并创建一张学生表Student
使用Entity Framework6 连接MySQL数据库
准备工作都就绪了,接下来我创建一个项目来演示如何连接MySQL数据库。
考虑到有些同学可能并不使用.NET MVC,所以这里我们创建一个控制台项目来演示。别担心,在.NET MVC中,配置的过程都是完全一样的。
一、添加EF包
打开NuGet包管理工具后,点击浏览,然后搜索EntityFramework。先按装EntityFramework,再安装MySQL.Data.Entity.
通过NuGet来安装EF可以帮我们省去很多麻烦的配置环节。
二、通过向导来连接MySQL数据库
按照习惯,我先在项目中建立了一个Modules文件夹,用于保存所有生成的数据模型文件。之后右击,新建项。
之后选择ADO.NET实体数据模型,我取名为DBModel。
选择来自数据库的CodeFirst模型,点击下一步。
PS:EF有三种方式来运行。
第一种是通过EF设计器,类似于传统的数据库设计器,通过画出的数据结构图来生成对应的类(数据模型)和数据库,不过这种方式好像在EF7中将被废弃掉了,所以不推荐使用。
第二种是通过先创建好数据库,然后根据数据库来生成对应的数据模型,这种方式称之为数据库优先DB Frist。
第三种是先手工编写好需要的数据模型,然后根据数据模型逆向生成数据库,这种方式称之为代码优先 Code First。
注意:不管你是习惯数据库优先还是代码优先。都请先用数据库优先来生成代码,这样会省去很多中间配置的环节。
之后选择新建连接
输入你的数据库地址(默认是localhost)和账号、密码,勾选保存密码,在数据库名称中选择你对应的数据库,在这我的是test。完成后点击测试连接,提示没有问题后点击确定。这一步如果出错一般都是数据库配置的问题,跟EF没有关系。
选择,是包含敏感信息
选择你需要的数据库和表,在这我选择的是我之前在数据库中创建的Student表。确定所生产对象名称的单复数形式这个选项随意。最开始我也没理解到它的作用,其实就是英语的单复数,就是在名词后面加S和不加S。
点击完成之后如果你幸运的话,到这一步就成功了。不过事情一般没那么简单,傲娇的EF就提示我什么主键值为空。
我第一次弄的时候这东西折磨了我大半天,什么办法都试尽了,好在最后在Stack Overflow上找到了办法。
打开MySQL管理器,执行
- use `test`; /*你的数据库名*/
- set global optimizer_switch='derived_merge=OFF';
执行完之后再按照上面的步骤从头一遍应该就可以了,如果还是不行就重启一下MySQL服务,再执行一下上面的代码,再试试。
三、分析生成的代码
回到解决方案资源管理器,这时我们的项目下就应当多了两个文件,一个是DBModel数据库上下文(到时都是通过它来操作数据库),一个是student数据模型。
打开DBModel.cs
student数据模型中对数据做了些描述(你不用代码优先CodeFirst这些一般都不用管)
使用EF进行增删改查
到这一步EF连接MySQL就已经完成了,之后你就可以像操作SQL Server一样使用EF操作MySQL了。不过我还是演示下如何使用EF操作数据库。
- class Program
- {
- static void Main(string[] args)
- {
- using (var db = new DBModel())
- {
- var student = (from t in db.students //查询张三
- where t.name == "张三"
- select t).First();
- Console.WriteLine("张三的年龄是:" + student.age); //输出:张三的年龄是:15
- db.students.Remove(student); //删除张三
- //db.Entry(student).State = System.Data.Entity.EntityState.Deleted; //或者这样删除张三
- db.students.Add(new student { name = "小明", age = 21 }); //向数据库中添加小明
- student = (from t in db.students //查询李四
- where t.name == "李四"
- select t).First();
- student.age = 999; //更改李四的年龄
- //db.Database.ExecuteSqlCommand("delete from test.student where true"); //执行普通的SQL,删除全部数据
- //var result = db.Database.SqlQuery<student>("select * from test.student"); //执行普通的SQL,查询所有学生
- db.SaveChanges(); //保存结果
- }
- Console.Read();
- }
- }
使用EntityFramework6完成增删查改和事务
上一节我们已经学习了如何使用EF连接数据库,并简单演示了一下如何使用EF6对数据库进行操作,这一节我来详细讲解一下。
使用EF对数据库进行操作,整个过程就像操作数组一样,我们只管修改或向集合中添加值,最后通知EF保存修改后的结果就可以了。
准备工作
为了演示,我在数据库中建了两张表。class表用于表示班级,class_id是班级编号,class_name是班级名称。第二张表是学生表student,student_id为学生编号,name为姓名,age为年龄,class_id是学生所属班级,student表中的class_id与class表中的class_id存在外联关系。
数据结构关系图
数据库建好后,按照上一节介绍的步骤我们直接通过向导来生成EF代码。
执行完成后VS为我们生成了三个类,分别是DBModel、_class、student,其中DBModel代表的是数据库,而_class和student则分别代表班级表和学生表。
先来看看DBModel类,DBModel相当于一个数据库,之后你new一个DBModel就相当于打开了一次数据库,跟数据库建立了一次连接。
再来看看student与_class类。类中的属性上添加很多特性(Attribute),用于描述数据约束。值得注意的是在student类中,EF将我们的外键class_id分解成了两个属性,其中_class前面使用了virtual关键字,使用了virtual关键字描述的属性在查询时并不会马上从数据库中读取数据,而是当你真正需要用到它的值时再单独从数据库中查询出来。
使用EF读取数据
我先在学生表中添加了一些数据。
接下来让我们使用EF来查询年龄大于10岁的同学的学号、姓名、年龄、班级名称。
- class Program
- {
- static void Main(string[] args)
- {
- using (var db = new DBModel())
- {
- var result = from i in db.students //使用LINQ查询年龄大于10岁的学生
- where i.age > 10
- select i;
- foreach (var student in result)
- {
- Console.WriteLine("学号:" + student.Student_id + " 姓名:" + student.name
- + " 年龄:" + student.age + " 班级名称:" + student._class.class_name);
- }
- Console.Read();
- }
- }
- }
其中使用了LINQ,对LINQ不熟的可以看看这篇文章。
使用EF添加数据
向数据库中添加数据就跟往List<>集合添加数据一样,不过最后需要调用SaveChanges()向数据库保存一下数据。
- class Program
- {
- static void Main(string[] args)
- {
- using (var db = new DBModel())
- {
- var s = new student();
- //因为数据库中的student_id是自动增长的所以可以不用赋值
- s.name = "张三";
- s.age = 15;
- s.class_id = 1;
- db.students.Add(s);
- db.SaveChanges(); //将修改保存到数据库中
- }
- }
- }
使用EF修改数据
先查询出你要修改的那条数据,之后直接更改其中的值就可以了。
- class Program
- {
- static void Main(string[] args)
- {
- using (var db = new DBModel())
- {
- var student = db.students.FirstOrDefault(s => s.name == "萝莉");
- student.age = 13; //将萝莉的年龄改为13岁
- db.SaveChanges();
- }
- }
- }
使用EF删除数据
使用EF删除数据就和在List<>集合中删除元素一样
使用TransactionScope
- class Program
- {
- static void Main(string[] args)
- {
- using (var db = new DBModel())
- {
- var student = db.students.FirstOrDefault(s => s.name == "萝莉"); //查找萝莉
- db.students.Remove(student); //删除萝莉
- db.SaveChanges();
- }
- }
- }
在EF使用事务
事务就是确保一次数据库操作,所有步骤都成功,如果哪一步出错了,整个操作都将回滚。
在EF使用事务有两种方案,一种是EF自带的.BeginTransaction()方法,另一种是使用TransactionScope类。
使用.BeginTransaction()
使用.BeginTransaction()实现事务
- class Program
- {
- static void Main(string[] args)
- {
- using (var db = new DBModel())
- {
- var tran = db.Database.BeginTransaction(); //开启事务
- try
- {
- var student = db.students.FirstOrDefault(s => s.name == "萝莉");
- db.students.Remove(student); //删除萝莉
- db.SaveChanges();
- tran.Commit(); //必须调用Commit(),不然数据不会保存
- }
- catch (Exception ex)
- {
- tran.Rollback(); //出错就回滚
- }
- }
- }
- }
使用TransactionScope类
使用之前记得引入System.Transactions.dll
使用TransactionScope
- class Program
- {
- static void Main(string[] args)
- {
- using (var db = new DBModel())
- {
- using (var tran = new TransactionScope()) //开启事务
- {
- var student = db.students.FirstOrDefault(s => s.name == "萝莉");
- db.students.Remove(student); //删除萝莉
- db.SaveChanges();
- tran.Complete(); //必须调用.Complete(),不然数据不会保存
- } //出了using代码块如果还没调用Complete(),所有操作就会自动回滚
- }
- }
- }
两种都可以,不过我觉得使用TransactionScope要方便一点。
到此使用EF6实现CRUD以及事务就介绍完了,下一节我们再来讨论一下如何在EF执行SQL,以实现更加灵活的数据操作。
在EntityFramework6中执行SQL语句
在上一节中我介绍了如何使用EF6对数据库实现CRDU以及事务,我们没有写一句SQL就完成了所有操作。这一节我来介绍一下如何使用在EF6中执行SQL语句。
你可能要问,我用EF不就为了避免写SQL吗?如果要写SQL我不如直接用ADO.NET得了。话虽然这么说没错,可有些时候使用EF操作数据还是有一些不方便,例如让你根据条件删除一组记录,如果按照正常的流程来走,你就得先把这些数据查出来,然后再一条一条地删除它们,这样不仅麻烦而且性能也比较低。这种情况下SQL就显示出它的威力了。
而使用EF执行SQL又比ADO.NET方便,特别是在执行查询语句的时候,EF会把查询到的数据自动保存到数据实体中,省去了使用DataReader的麻烦。同时查询出来的数据还会进行跟踪,如果你修改了查询出的值,之后就可以很方便的使用.SaveChanges()直接更新到数据库了。
ExecuteSqlCommand与SqlQuery
在数据上下文DBModel的实例中有个Database属性,其中有两组方法.ExecuteSqlCommand()和.SqlQuery()。它们都可以执行SQL语句,只不过.ExecuteSqlCommand()是不返回结果的,只返回受影响的行数,所以.ExecuteSqlCommand()更适合执行创建、更新、删除操作。.SqlQuery()则会返回查询到的结果,并将结果保存在数据实体中,所以更适合执行查询操作。
使用.ExecuteSqlCommand()实现创建、更新、删除
.ExecuteSqlCommand()的使用方法也很简单,直接传入SQL语句就可以了,执行完成后会返回受影响的行数。
- using (var db = new DBModel()) //创建数据库上下文
- {
- //同步的方式执行SQL,并返回受影响的行数
- int result = db.Database.ExecuteSqlCommand(@"CREATE TABLE `test`.`test` (
- `id` INT NOT NULL,
- PRIMARY KEY(`id`)); ");
- //使用SqlParameter传值可以避免SQL注入
- var p_name = new SqlParameter("@name", "萝莉");
- var p_age = new SqlParameter("@age", 13);
- //如果使用的是MySql数据库 需要SqlParameter把替换为MySqlParameter
- //var p_name = new MySqlParameter("@name", "萝莉");
- //var p_age = new MySqlParameter("@age", 13);
- //更改学生年龄
- result = db.Database.ExecuteSqlCommand(@"UPDATE `test`.`student`
- SET `age` = @age
- WHERE `name` = @name;", p_age, p_name);
- //异步的方式执行SQL,并返回受影响的行数
- Task<int> result2 = db.Database.ExecuteSqlCommandAsync("DROP TABLE `test`.`test`;");
- }
PS:如果需要创建或删除当前数据库,Database属性中还存在.Create() 和.Delete()方法,它们不接受参数,返回一个bool值表示执行成功或失败。
使用.SqlQuery()查询数据
从名字就看的出来.SqlQuery()是用来执行查询的。.SqlQuery()使用前需指定返回值的数据类型,比如我查询寻一条学生的完整信息,类型就可以指定为student类型。如果是统计有多少个学生,返回值是个整数,就以设置为int。
注意:不仅返回值的个数必须与传入类型中属性值的个数相同,而且名称还必须一样,不然会出错。那么如果我只想获取姓名和年龄,那就得单独定义一个类(其中包含一个string类型的name和int类型的age),来保存数据了。
- class temp
- {
- public string name { get; set; }
- public int age { get; set; }
- }
- static void Main(string[] args)
- {
- using (var db = new DBModel()) //创建数据库上下文
- {
- //查询叫萝莉的学生信息,并指定返回值类型为student
- DbRawSqlQuery<student> result1 = db.Database.SqlQuery<student>("SELECT * FROM test.student WHERE name = '萝莉'");
- //也可以这样指定返回值类型
- //DbRawSqlQuery result1 = db.Database.SqlQuery(typeof(student), "SELECT * FROM test.student WHERE name = '萝莉'");
- Console.WriteLine(result1.FirstOrDefault().name); //打印姓名
- DbRawSqlQuery<int> result2 = db.Database.SqlQuery<int>("SELECT count(*) FROM test.student");
- Console.WriteLine(result2.FirstOrDefault()); //打印有多少学生
- //只查询学生的年龄与姓名
- var result3 = db.Database.SqlQuery<temp>("SELECT `name`,`age` FROM test.student;");
- foreach (temp item in result3)
- {
- Console.WriteLine(item.name + ":" + item.age);
- }
- }
使用DbSet<T>下的.SqlQuery()
在每个数据实体集合DbSet<T>下也有一个.SqlQuery(),功能与上面介绍的一样,只不过DbSet<T>下的.SqlQuery()只能返回DbSet<T>中包含的类型。但DbSet<T>下的.SqlQuery()在返回数据的同时还会让数据库上下文(DBModel)跟踪返回数据的状态,如果返回的数据发生了修改,就可以使用.SaveChanges()将结果直接保存回数据库。而.Database.SqlQuery()查出的结果则是做不到的。
- using (var db = new DBModel()) //创建数据库上下文
- {
- //查询叫萝莉的学生信息,并修改她的年龄
- student result1 = db.students.SqlQuery("SELECT * FROM test.student WHERE name = '萝莉'").FirstOrDefault();
- result1.age = 13; //通过实体集合下.SqlQuery查询到的数据,修改之后是可以保存到数据库的
- student result2 = db.Database.SqlQuery<student>("SELECT * FROM test.student WHERE name = '旺财'").FirstOrDefault();
- result2.age = 21; //因为使用的是.Database.SqlQuery查询到的,所以这里的修改不会保存到数据库
- //如果希望.Database.SqlQuery下查出的数据在修改后也能保存到数据库
- student result3 = db.Database.SqlQuery<student>("SELECT * FROM test.student WHERE name = '小明'").FirstOrDefault();
- result3.age = 36;
- db.Entry<student>(result3).State = System.Data.Entity.EntityState.Modified; //通知数据上下文,这条记录也被修改了
- db.SaveChanges();
- }
到此为止,EF6中的常用操作就全部讲完了,之后如果发现还有什么值得拿出来讲的,到时候再加。