胖胖滴加菲猫

导航

ORM之EF初识

之前有写过ef的codefirst,今天来更进一步认识EF!

一:EF的初步认识

ORM(Object Relational Mapping):对象关系映射,其实就是一种对数据访问的封装。主要实现流程如下图

 

EF:是一种通过映射操作实现数据交互的ORM框架技术

今天我们主要先初步认识一下EF6,EntityFramwork6能支持多数据库;支持函数,存储过程;并且跟VS集成的比较好;能够跟项目完美结合;能基本实现增删改查,里面有两个主要的组成部分:Context(映射数据库实例)和实体类(跟数据库的映射关系表)

Visual Studio可以使用四种方式来创建EF的项目,分别如下:

第一是:EntityFramework DBFirst,这个是数据库优先,传统的开发模式,有个很重的edmx

第二种是:EntityFramework codeFirst from db && codeFirst,这个代码先行,不关心数据库,从业务出发,然后能自动生成数据库。

 

二:ef中我们如果想要看到底层生成的sql语句,有两种方式:

1:使用sqlserver中的sqlprofiler 监测工具,这个每次执行都会得到相应的sql的

2:在项目中添加context.Database.Log += s => Console.WriteLine($"当前执行sql:{s}"); 这个会把每次操作数据库的日志全部打印出来的,有兴趣的可以自己试一下。

 

三:如果数据库的字段跟项目中实体的字段名字不匹配,可以通过下面三种方式来实现:

1:使用特性直接完成,比如在实体类头部或者字段头部增加对应的特性,如下面的【Table】和【Column】等特性:

 1  [Table("JD_Commodity_001")]
 2  public partial class JDCommodity001
 3  {
 4      public int Id { get; set; }
 5 
 6      public long? ProductId { get; set; }
 7 
 8      public int? CategoryId { get; set; }
 9 
10      [StringLength(500)]
11      [Column("Title")]
12      public string Text { get; set; }
13  }

 

2:在DbContext中的OnModelCreating中完成链式映射,具体代码如下:

1  protected override void OnModelCreating(DbModelBuilder modelBuilder)
2  {
3      Database.SetInitializer(new CreateDatabaseIfNotExists<CodeFirstFromDBContext>());
4 
5      modelBuilder.Entity<JDCommodity002>()
6          .ToTable("JD_Commodity_002")
7          .Property(c => c.Text).HasColumnName("Title");
8  }

3:DbContext中的OnModelCreating中增加配置文件,具体代码如下:

A:首先先创建JDCommodity003Mapping映射类然后继承于 EntityTypeConfiguration<JDCommodity003>,具体如下:

1 public class JDCommodity003Mapping : EntityTypeConfiguration<JDCommodity003>
2 {
3     public JDCommodity003Mapping()
4     {
5         this.ToTable("JD_Commodity_003");
6         this.Property(c => c.Text).HasColumnName("Title");
7     }
8 }

B:然后在DbContext中的OnModelCreating增加配置文件:

1  protected override void OnModelCreating(DbModelBuilder modelBuilder)
2  {
3      modelBuilder.Configurations.Add(new JDCommodity003Mapping());
4  }

通过上面三种方式都能实现实体类与数据库表对应的映射。

 

四:EF中复杂的查询以及写sql语句进行查询

1:EF普通的查询,使用IQuerable和linq的方式查询,如下:

 1 #region 其他查询
 2             using (JDDbContext dbContext = new JDDbContext())
 3             {
 4                 {
 5                     var list = dbContext.Users.Where(u => new int[] { 1, 2, 3, 5, 8, 9, 10, 11, 12, 14, 17 }
 6                     .Contains(u.Id));//in查询
 7                     //这些都是延迟加载,只有用到list的话才会实际去查询数据库
 8                     foreach (var user in list) 
 9                     {
10                         Console.WriteLine(user.Name ?? "Name为空");
11                     }
12                 }
13                 {
14                     //没有任何差别,只有写法上的熟悉
15                     var list = from u in dbContext.Users
16                                where new int[] { 1, 2, 3, 5, 7, 8, 9, 10, 11, 12, 14 }.Contains(u.Id)
17                                where u.Id>14
18                                select u;
19                     //上面这个也是延迟加载,只有用到list的话才会实际去查询数据库
20                     foreach (var user in list)
21                     {
22                         Console.WriteLine(user.Name??"name为空");
23                     }
24                 }
25                 {
26                     var list = dbContext.Users.Where(u => new int[] { 1, 2, 3, 5, 7, 8, 9, 10, 11, 12, 14, 18, 19, 20, 21, 22, 23 }.Contains(u.Id))
27                                               .OrderBy(u => u.Id)
28                                               .Select(u => new
29                                               {
30                                                   Account = u.Account,
31                                                   Pwd = u.Password
32                                               }).Skip(3).Take(5);
33                     foreach (var user in list)
34                     {
35                         Console.WriteLine(user.Pwd);
36                     }
37                 }
38                 {
39                     var list = (from u in dbContext.Users
40                                 where new int[] { 1, 2, 3, 5, 7, 8, 9, 10, 11, 12, 14 }.Contains(u.Id)
41                                 orderby u.Id
42                                 select new
43                                 {
44                                     Account = u.Account,
45                                     Pwd = u.Password
46                                 }).Skip(3).Take(5);
47 
48                     foreach (var user in list)
49                     {
50                         Console.WriteLine(user.Account);
51                     }
52                 }
53 
54                 {
55                     var list = dbContext.Users.Where(u => u.Name.StartsWith("") && u.Name.EndsWith(""))
56                                                .Where(u => u.Name.EndsWith(""))
57                                                .Where(u => u.Name.Contains("小新"))
58                                                .Where(u => u.Name.Length < 5)
59                                                .OrderBy(u => u.Id);
60 
61                     foreach (var user in list)
62                     {
63                         Console.WriteLine(user.Name);
64                     }
65                 }
66                 {
67                     var list = from u in dbContext.Users
68                                join c in dbContext.Companies on u.CompanyId equals c.Id
69                                where new int[] { 1, 2, 3, 4, 6, 7, 10 }.Contains(u.Id)
70                                select new
71                                {
72                                    Account = u.Account,
73                                    Pwd = u.Password,
74                                    CompanyName = c.Name
75                                };
76                     foreach (var user in list)
77                     {
78                         Console.WriteLine("{0} {1}", user.Account, user.Pwd);
79                     }
80                 }
81                 {
82                     var list = from u in dbContext.Users
83                                join c in dbContext.Categories on u.CompanyId equals c.Id
84                                into ucList
85                                from uc in ucList.DefaultIfEmpty()
86                                where new int[] { 1, 2, 3, 4, 6, 7, 10 }.Contains(u.Id)
87                                select new
88                                {
89                                    Account = u.Account,
90                                    Pwd = u.Password
91                                };
92                     foreach (var user in list)
93                     {
94                         Console.WriteLine("{0} {1}", user.Account, user.Pwd);
95                     }
96                 }
97             }
98             #endregion
View Code

2:EF自定义sql语句,然后EF框架自己调用sql执行sql,可以使用ado.net自带的事务来操作。

 1 #region 自定义sql,然后ef框架调用
 2             using (JDDbContext dbContext = new JDDbContext())
 3             {
 4                 {
 5                     DbContextTransaction trans = null;
 6                     try
 7                     {
 8                         trans = dbContext.Database.BeginTransaction();
 9                         string sql = "Update [User] Set Name='小新' WHERE Id=@Id";
10                         SqlParameter parameter = new SqlParameter("@Id", 1);
11                         dbContext.Database.ExecuteSqlCommand(sql, parameter);
12                         trans.Commit();
13                     }
14                     catch (Exception ex)
15                     {
16                         if (trans != null)
17                             trans.Rollback();
18                         throw ex;
19                     }
20                     finally
21                     {
22                         trans.Dispose();
23                     }
24                 }
25                 {
26                     DbContextTransaction trans = null;
27                     try
28                     {
29                         trans = dbContext.Database.BeginTransaction();
30                         string sql = "SELECT * FROM [User] WHERE Id=@Id";
31                         SqlParameter parameter = new SqlParameter("@Id", 1);
32                         List<User> userList = dbContext.Database.SqlQuery<User>(sql, parameter).ToList<User>();
33                         trans.Commit();
34                     }
35                     catch (Exception ex)
36                     {
37                         if (trans != null)
38                             trans.Rollback();
39                         throw ex;
40                     }
41                     finally
42                     {
43                         trans.Dispose();
44                     }
45                 }
46             }
47             #endregion
View Code

 

五:EF的IQuerable延迟和IEnumerable的延迟加载的比较跟区别,首先我们写了一个例子如下:

 1 #region
 2             //userList是IQueryable类型,数据在数据库里面,
 3             //这个list里面有表达式目录树---返回值类型--IQueryProvider(查询的支持工具,sqlserver语句的生成)
 4             //其实userList只是一个包装对象,里面有表达式目录树,有结果类型,有解析工具,还有上下文,
 5             //真需要数据的时候才去解析sql,执行sql,拿到数据的---因为表达式目录树可以拼装;
 6             IQueryable<User> sources = null;
 7             using (JDDbContext dbContext = new JDDbContext())
 8             {
 9                 sources = dbContext.Set<User>().Where(u => u.Id > 20);
10                 //延迟查询也要注意:a 迭代使用时,用完了关闭连接  b 脱离context作用域则会异常
11                 foreach (var user in sources)//sources必须在该dbContext的using范围内使用
12                 {
13                     Console.WriteLine(user.Name);
14                 }
15                 Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
16 
17                 var userList = dbContext.Set<User>().Where(u => u.Id > 10);//1 这句话执行完,没有数据库查询
18                 foreach (var user in userList)//2 迭代遍历数据才去数据库查询--在真实需要使用数据时,才去数据库查询的
19                 {
20                     Console.WriteLine(user.Name);
21                 }
22                 //IEnumerator
23                 //这就是延迟查询,可以叠加多次查询条件,一次提交给数据库;可以按需获取数据;
24                 userList = userList.Where(u => u.Id < 100);
25                 userList = userList.Where(u => u.State < 3);
26                 userList = userList.OrderBy(u => u.Name);
27 
28                 var list = userList.ToList<User>();//ToList() ,Count(), FitstOrDefalut()都会自动去调用数据库
29             }
30             //这个时候查询,已经超出作用域。会异常,所以必须在using范围内使用
31             //foreach (var user in sources)
32             //{
33             //    Console.WriteLine(user.Name);
34             //}
35 
36             //intList实现了IEnumerable类型,这里是延迟的,其实数据已经在内存里,利用的是迭代器的方式,每次去迭代访问时,才去筛选一次,委托+迭代器
37             {
38                 List<int> intList = new List<int>() { 123, 4354, 3, 23, 3, 4, 4, 34, 34, 3, 43, 43, 4, 34, 3 };
39                 var list = intList.Where(i =>
40                  {
41                      Thread.Sleep(i);
42                      return i > 10;
43                  });//没有过滤
44                 foreach (var i in list)//才去过滤
45                 {
46                     Console.WriteLine(i);
47                 }
48                 Console.WriteLine("*********************");
49             }
50             #endregion
View Code

1:IEnumerable:利用的是迭代器的方式,每次去迭代访问时,才去筛选一次,委托+迭代器

2:IQueryable:只是一个包装对象,里面有表达式目录树,有结果类型,有解析工具,还有上下文,真需要数据的时候才去解析sql,执行sql,拿到数据的---因为表达式目录树可以拼装;

 

六:EF状态的跟踪变化

1:ef内置的监控

 1 User userNew = new User()
 2 {
 3     Account = "Admin",
 4     State = 0,
 5     CompanyId = 4,
 6     CompanyName = "万达集团",
 7     CreateTime = DateTime.Now,
 8     CreatorId = 1,
 9     Email = "loverwangshan@qq.com",
10     LastLoginTime = null,
11     LastModifierId = 0,
12     LastModifyTime = DateTime.Now,
13     Mobile = "18664876671",
14     Name = "intitName",
15     Password = "12356789",
16     UserType = 1
17 };
18 using (JDDbContext context = new JDDbContext())
19 {
20     Console.WriteLine(context.Entry<User>(userNew).State);//实体跟context没关系 Detached
21     userNew.Name = "小鱼";
22     context.SaveChanges();//如果状态为Detached的时候,SaveChanges什么也不会做的
23 
24     context.Users.Add(userNew);
25     Console.WriteLine(context.Entry<User>(userNew).State);//Added
26 
27     context.SaveChanges();//插入数据(自增主键在插入成功后,会自动赋值过去)
28     Console.WriteLine(context.Entry<User>(userNew).State);//Unchanged(跟踪,但是没变化)
29 
30     userNew.Name = "加菲猫";//一旦任何字段修改----内存clone 
31     Console.WriteLine(context.Entry<User>(userNew).State);//Modified
32     context.SaveChanges();//更新数据库,因为状态是Modified
33     Console.WriteLine(context.Entry<User>(userNew).State);//Unchanged(跟踪,但是没变化)
34 
35     context.Users.Remove(userNew);
36     Console.WriteLine(context.Entry<User>(userNew).State);//Deleted
37     context.SaveChanges();//删除数据,因为状态是Deleted
38     Console.WriteLine(context.Entry<User>(userNew).State);//Detached已经从内存移除了
39 }
View Code

通过执行上面的代码,我们能看到每一步的操作状态,其实EF本身是依赖监听变化,如果有任何字段发生改变(会拿当前字段的值跟内存进行比较因此晓得是否发生改变),则会把context.Entry<User>(user20).State修改为Modified的,然后SaveChanges是以context为标准的,如果监听到任何数据的变化,会一次性的保存到数据库去,而且会开启事务!

2:因为EF默认会对与context的对象有关系的一些实体进行监控,如果仅仅是做查询而不会做更新,则不需要监控,以提高效率,下面代码是取消监控:

1  using (JDDbContext context = new JDDbContext())
2  {
3      //如果获取对象仅仅为了查询,而不会对其修改,则没有必要进行监听某个对象,
4      //可以使用AsNoTracking取消监听,这样可以提高一些效率
5      User user21 = context.Users.Where(u => u.Id == 21).AsNoTracking().FirstOrDefault();
6      Console.WriteLine(context.Entry<User>(user21).State); //Detached
7  }

3:EF追踪对象的三种方式如下:

 1 User user = null;//声明一个新的对象
 2 using (JDDbContext context = new JDDbContext())
 3 {
 4     User user20 = context.Users.Find(20);
 5     Console.WriteLine(context.Entry<User>(user20).State);
 6     user = user20;
 7 }
 8 
 9 user.Name = "滑猪小板123456789";
10 using (JDDbContext context = new JDDbContext())
11 {
12     Console.WriteLine(context.Entry<User>(user).State); //因为user是新的字段,跟context么有关系,所以是Detached
13 
14     //第一种:如果user是新的对象,与context没有关系,则先使用Attach建立关系,然后修改字段
15     //context.Users.Attach(user);//使user跟context建立关系
16     //Console.WriteLine(context.Entry<User>(user).State);//Unchanged
17     //user.Name = "滑猪小板";//只能更新这个字段
18     //Console.WriteLine(context.Entry<User>(user).State);//Modified
19 
20     //第二种:强制指定State的状态为:EntityState.Modified,这个是默认所有的字段都会追踪
21     context.Entry<User>(user).State = EntityState.Modified;//全字段更新
22     Console.WriteLine(context.Entry<User>(user).State);//Modified
23 
24     //第三种:使用context直接查询出来的字段,是默认监听的
25     user = context.Users.Find(user.Id);//查出来自然是监听
26     user.Name = "ddds";
27     Console.WriteLine(context.Entry<User>(user).State);//Modified
28 
29     context.SaveChanges();
30 }
View Code

刚刚有个设置整个对象的字段更新,可以通过:context.Entry<User>(user5).Property("Name").IsModified = true来指定某字段被改过 !

 

七:EF内置的一些缓存

 1  using (JDDbContext context = new JDDbContext())
 2  {
 3      var userList = context.Users.Where(u => u.Id > 10).ToList();
 4      //var userList = context.Users.Where(u => u.Id > 10).AsNoTracking().ToList();
 5      Console.WriteLine(context.Entry<User>(userList[3]).State);
 6      Console.WriteLine("*********************************************");
 7      var user5 = context.Users.Find(5);
 8      Console.WriteLine("*********************************************");
 9      var user1 = context.Users.Find(30);
10      Console.WriteLine("*********************************************");
11      var user2 = context.Users.FirstOrDefault(u => u.Id == 30);
12      Console.WriteLine("*********************************************");
13      var user3 = context.Users.Find(30);
14      Console.WriteLine("*********************************************");
15      var user4 = context.Users.FirstOrDefault(u => u.Id == 30);
16  }
View Code

Find可以使用缓存,优先从内存查找(限于同一个context),但是linq时不能用缓存,每次都是要查询的

 

 八:DbContext的一些声明周期以及用法

1:DbContext的SaveChanges是开启事务的,任何一个失败直接全部失败,如下:

 1 #region 多个数据操作一次savechange,任何一个失败直接全部失败
 2 using (JDDbContext dbContext = new JDDbContext())
 3 {
 4     User userNew = new User()
 5     {
 6         Account = "Admin",
 7         State = 0,
 8         CompanyId = 4,
 9         CompanyName = "万达集团",
10         CreateTime = DateTime.Now,
11         CreatorId = 1,
12         Email = "dddd@qq.com",
13         LastLoginTime = null,
14         LastModifierId = 0,
15         LastModifyTime = DateTime.Now,
16         Mobile = "2222",
17         Name = "aaa",
18         Password = "12356789",
19         UserType = 1
20     };
21     dbContext.Users.Add(userNew);
22 
23     User user17 = dbContext.Users.FirstOrDefault(u => u.Id == 17);
24     user17.Name += "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
25 
26     User user18 = dbContext.Set<User>().Find(18);
27     user18.Name += "bbb";
28 
29     //Company company2019 = dbContext.Set<Company>().Find(2019);
30     //dbContext.Companies.Remove(company2019);
31 
32     dbContext.SaveChanges();
33 }
34 #endregion
View Code

2:多context实例 join 不行,因为上下文环境不一样;除非把数据都查到内存,再去linq,如下代码会报错:

 1 using (JDDbContext dbContext1 = new JDDbContext())
 2 using (JDDbContext dbContext2 = new JDDbContext())
 3 {
 4     var list = from u in dbContext1.Users
 5                join c in dbContext2.Companies on u.CompanyId equals c.Id
 6                where new int[] { 1, 2, 3, 4, 6, 7, 10 }.Contains(u.Id)
 7                select new
 8                {
 9                    Account = u.Account,
10                    Pwd = u.Password,
11                    CompanyName = c.Name
12                };
13     foreach (var user in list)
14     {
15         Console.WriteLine("{0} {1}", user.Account, user.Pwd);
16     }
17 }
View Code

3:context的一些使用建议:

  • DbContext是个上下文环境,里面内置对象跟踪,会开启链接(就等于一个数据库链接)
  • 一次请求,最好是一个context;
  • 多个请求 /多线程最好是多个实例;
  • 用完尽快释放;

 

 九:多个数据源的事务

在第八条我们晓得不同的DbContext是没办法jioin联合查询的,那现在如果有一个需求,如果我想操作不同的数据源,又想放在一个事务里面统一操作,这个需要怎么做呢,.net帮我们提供了一个TransactionScope,具体实现如下:

 1  using (JDDbContext dbContext1 = new JDDbContext())
 2  using (JDDbContext dbContext2 = new JDDbContext())
 3  {
 4      using (TransactionScope trans = new TransactionScope())
 5      {
 6          User userNew1 = new User()
 7          {
 8              Account = "Admin",
 9              State = 0,
10              CompanyId = 2031,
11              CompanyName = "wwww",
12              CreateTime = DateTime.Now,
13              CreatorId = 1,
14              Email = "dddd@qq.com",
15              LastLoginTime = null,
16              LastModifierId = 0,
17              LastModifyTime = DateTime.Now,
18              Mobile = "adfadf",
19              Name = "民工甲123333333",
20              Password = "12356789",
21              UserType = 1
22          };
23          dbContext1.Set<User>().Add(userNew1);
24          dbContext1.SaveChanges();
25 
26          SysLog sysLog = new SysLog()
27          {
28              CreateTime = DateTime.Now,
29              CreatorId = userNew1.Id,
30              LastModifierId = 0,
31              LastModifyTime = DateTime.Now,
32              Detail = "12345678",
33              Introduction = "sadsfghj",
34              LogType = 1,
35              UserName = "zhangsanan2zhangsanan2zhangsanan2zhangsanan2333333333"
36          };
37          dbContext2.Set<SysLog>().Add(sysLog);
38          dbContext2.SaveChanges();
39 
40          trans.Complete();//能执行这个,就表示成功了;
41      }
42  }
View Code

这样就能实现不同的数据源使用同一个事务了。

 

十:EF的导航属性

如果EF中有表对应的关系,一对多一对一,则如果想要查询出来一个表的同时把另外一个表也对应的查询出来,则可以使用导航属性,比如我们一个用户表属于某个公司,再查询用户表的时候想要把公司的信息给带出来,则可以进行如下操作:

 1 [Table("User")]
 2     public partial class User
 3     {
 4         public int Id { get; set; }
 5 
 6         public int CompanyId { get; set; }
 7 
 8         [StringLength(500)]
 9         public string CompanyName { get; set; }
10 
11         public int State { get; set; }
12 
13         public int UserType { get; set; }
14 
15         public DateTime? LastModifyTime { get; set; }
16 
17         [ForeignKey("CompanyId")]
18         public virtual Company Company { get; set; }
19     }
View Code

注意:一般导航属性,实体与主键对应最好使用ForeignKey来标识一下,不要使用.netFramwork对应的框架那种默认方式去做,比较不明确。

 

然后查询如下:

1  using (JDDbContext dbContext = new JDDbContext())
2  {
3      var userList = dbContext.Set<User>().Where(c => c.Id < 20);
4      foreach (var user in userList)//只差company
5      {
6          Console.WriteLine($"用户Id:{user.Id}");
7          Console.WriteLine($"公司名字:{user.Company?.Name}"); //每次会重新去加载用户信息
8      }
9  }

导航加载是懒加载,就是只有用到才会去读取。比如每次使用到user.Company都会重新去查询数据。所以这样会增加了对数据库的频繁打开跟关闭。如果要禁止懒加载,可以通过下面的Include来强制加载:

如果想要禁止懒加载,即每次查询的时候,也不想去查询数据,可以通过下面的方式来配置:

 

posted on 2019-06-06 17:20  胖胖滴加菲猫  阅读(386)  评论(0编辑  收藏  举报