zoukankan      html  css  js  c++  java
  • 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来强制加载:

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

  • 相关阅读:
    python 统计gitlab代码量
    whistle 模拟用户网络超时
    Node.js 快速转换 url 链接
    实用的 Git 命令
    前端常用 prettier 配置 + vetur 配置
    vue 项目 eslint + prettier 配置
    lodash 方法 debounce 防连点 防抖按钮点击
    npm 包实现自动登录 CICD 流程
    git push --follow-tags 命令
    小程序 iOS webview 中网页使用 iframe 无法滚动问题
  • 原文地址:https://www.cnblogs.com/loverwangshan/p/10956777.html
Copyright © 2011-2022 走看看