.NET EF Core(Entity Framework Core)

马肤
这是懒羊羊

文章目录

    • EF Core与其他ORM比较
    • EF Core与EF比较
    • Migration数据库迁移
      • 反向工程
      • Migrations其他命令
      • 修改表结构
      • EF Core操作数据库
        • 插入数据
        • 查询数据
        • 修改、删除
        • 其他数据库
        • EF Core实体的配置
          • Data Annotation配置方式
          • Fluent API配置方式
          • Fluent API
          • 通过代码查看EF Core的sql语句
            • 方法1:标准日志
            • 方法2:简单日志
            • 方法3:ToQueryString
            • 悲观并发控制
            • 乐观并发控制:并发令牌
            • 乐观并发控制:RowVersion

              EF Core与其他ORM比较

              1、Entity Framework Core(EF Core)是微软官方的ORM框架。优点:功能强大、官方支持、生产效率高、力求屏蔽底层数据库差异;缺点:复杂、上手门槛高、不熟悉EFCore的话可能会进坑。

              2、Dapper。优点:简单,N分钟即可上手,行为可预期性强;缺点:生产效率低,需要处理底层数据库差异。

              3、EF Core是 模型驱动 (Model-Driven)的开发思想,Dapper是 数据库驱动(DataBase-Driven)的开发思想的。没有优劣,只有比较。

              4、性能: Dapper等≠性能高;EF Core≠性能差。

              5、EF Core是官方推荐、推进的框架,尽量屏蔽底层数据库差异,.NET开发者必须熟悉,根据的项目情况再决定用哪个。

              EF Core与EF比较

              1、EF有DB First、Model First、Code First。EF Core不支持模型优先,推荐使用代码优先,遗留系统可以使用Scaffold-DbContext来生成代码实现类似DBFirst的效果,但是推荐用Code First 。

              2、EF会对实体上的标注做校验,EF Core追求轻量化,不校验。

              3、熟悉EF的话,掌握EFCore会很容易,很多用法都移植过来了。EF Core又增加了很多新东西。

              4、EF中的一些类的命名空间以及一些方法的名字在EF Core中稍有不同。

              5、EF不再做新特性增加。

              Migration数据库迁移

              面向对象的ORM开发中,数据库不是程序员手动创建的,而是由Migration工具生成的。关系数据库只是盛放模型数据的一个媒介而已,理想状态下,程序员不用关心数据库的操作

              根据对象的定义变化,自动更新数据库中的表以及表结构的操作,叫做Migration(迁移)。

              迁移可以分为多步(项目进化),也可以回滚。

              1、Nuget安装Install-Package Microsoft.EntityFrameworkCore.SqlServer,Microsoft.EntityFrameworkCore.Tools

              2、搭建工程:

              创建实体类Book.cs

              public class Book
              {
                  public long Id { get; set; }//主键
                  public string Title { get; set; }//标题
                  public DateTime PubTime { get; set; }//发布日期
                  public double Price { get; set; }//单价
                  public override string ToString()
                  {
                      return "Id:" + this.Id + ",Title:" + this.Title + ",PubTime" + this.PubTime +",Price" + this.Price + ",AuthorName" + AuthorName;
                  }
              }
              

              实体配置类:创建实现了IEntityTypeConfiguration接口的实体配置类,配置实体类和数据库表的对应关系

              class BookEntityConfig : IEntityTypeConfiguration
              {
              	public void Configure(EntityTypeBuilder builder)
              	{
              		builder.ToTable("T_Books");
              	}
              }
              

              创建继承自DbContext的类

              internal class MyDbContext: DbContext
              {
                  public DbSet Books { get; set; }
                  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
                  {
                      string connStr = "Server=192.168.1.193;Database=demo1; User=sa;Password=@q123; Trusted_Connection=False;MultipleActiveResultSets=true;TrustServerCertificate=true";
                      optionsBuilder.UseSqlServer(connStr);
                  }
                  protected override void OnModelCreating(ModelBuilder modelBuilder)
                  {
                      base.OnModelCreating(modelBuilder);
                      modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
                  }
              }
              

              2、在“程序包管理器控制台”中执行命令:Add-Migration InitialCreate,会自动在项目的Migrations文件夹中中生成操作数据库的C#代码。

              3、代码需要执行后才会应用对数据库的操作。“程序包管理器控制台”中执行Update-database

              4、查看一下数据库,表建好了

              反向工程

              1、根据数据库表来反向生成实体类

              2、Scaffold-DbContext 'Server=.;Database=demo1;Trusted_Connection=True;MultipleActiveResultSets=true' Microsoft.EntityFrameworkCore.SqlServer

              Tips:1、生成的实体类可能不能满足项目的要求,可能需要手工修改或者增加配置。

              2、再次运行反向工程工具,对文件所做的任何更改都将丢失。

              3、不建议把反向工具当成了日常开发工具使用,不建议DBFirst。

              Migrations其他命令

              1、Update-Database XXX :把数据库回滚到XXX的状态,迁移脚本不动。

              2、Remove-migration:删除最后一次的迁移脚本

              3、Script-Migration:生成迁移SQL代码。

              可以生成版本D到版本F的SQL脚本:Script-Migration D F

              生成版本D到最新版本的SQL脚本:Script-Migration D

              4、通过给Add-Migration命令添加“-OutputDir”参数的形式来在同一个项目中为不同的数据库生成不同的迁移脚本

              小结:

              1、使用迁移脚本,可以对当前连接的数据库执行编号更高的迁移,这个操作叫做“向上迁移”(Up),也可以执行把数据库回退到旧的迁移,这个操作叫“向下迁移”(Down)。

              2、除非有特殊需要,否则不要删除Migrations文件夹下的代码。

              3、进一步分析Migrations下的代码。分析Up、Down等方法。查看Migration编号。

              4、查看数据库的__EFMigrationsHistory表:记录当前数据库曾经应用过的迁移脚本,按顺序排列。

              修改表结构

              想要限制Title的最大长度为50,Title字段设置为“不可为空”,并且想增加一个不可为空且最大长度为20的AuthorName(作者名字)属性。

              1、首先在Book实体类中增加一个AuthorName属性。

              2、修改BookEntityConfig

              builder.ToTable("T_Books");
              builder.Property(e => e.Title).HasMaxLength(50).IsRequired();
              builder.Property(e => e.AuthorName).HasMaxLength(20).IsRequired();
              

              3、执行Add-Migration AddAuthorName_ModifyTitle。AddAuthorName_ModifyTitle为本次迁移操作的名称

              4、执行:Update-Database

              EF Core操作数据库

              .NET EF Core(Entity Framework Core),在这里插入图片描述,词库加载错误:未能找到文件“C:\Users\Administrator\Desktop\火车头9.8破解版\Configuration\Dict_Stopwords.txt”。,操作,没有,安装,第1张

              插入数据

              只要操作Books属性,就可以向数据库中增加数据,但是通过C#代码修改Books中的数据只是修改了内存中的数据。对Books做修改后,需要调用DbContext的异步方法SaveChangesAsync()把修改保存到数据库。也有同步的保存方法SaveChanges(),但是用EF Core都推荐用异步方法。

               static async Task Main(string[] args)
               {
                   using (MyDbContext dbContext = new MyDbContext())
                   {
                       Book book = new Book() { Title = "西游记", Price = 15.5, AuthorName = "吴承恩" };
                       dbContext.Books.Add(book);
                       await dbContext.SaveChangesAsync();
                   }
               }
              

              查询数据

              DbSet实现了IEnumerable接口,因此可以对DbSet实施Linq操作来进行数据查询。EF Core会把Linq操作转换为SQL语句。面向对象,而不是面向数据库(SQL)。

              IQueryable books = dbContext.Books.Where(b => b.Price > 20);
              foreach (Book book in books)
              {
                  Console.WriteLine(book);
              }
              Book b1 = dbContext.Books.Single(b => b.Title == "西游记");
              Book b2 = dbContext.Books.FirstOrDefault(b => b.Id == 2);
              Console.WriteLine(b1);
              Console.WriteLine(b2);
              IEnumerable books2 = dbContext.Books.OrderByDescending(b => b.Price);
              var groups = dbContext.Books.GroupBy(b => b.AuthorName)
              	.Select(g => new { AuthorName = g.Key, BooksCount = g.Count(), MaxPrice = g.Max(b => b.Price) });
              foreach(var g in groups)
              {
              	Console.WriteLine($"作者名:{g.AuthorName},著作数量:{g.BooksCount},最贵的价格:{g.MaxPrice}");
              }
              

              修改、删除

              1、要对数据进行修改,首先需要把要修改的数据查询出来,然后再对查询出来的对象进行修改,然后再执行SaveChangesAsync()保存修改。

              修改:

               var b = dbContext.Books.Single(b => b.Title == "西游记");
               b.AuthorName = "WuChengEn";
               await dbContext.SaveChangesAsync();
              

              2、删除也是先把要修改的数据查询出来,然后再调用DbSet或者DbContext的Remove方法把对象删除,然后再执行SaveChangesAsync()保存修改。

              var b = dbContext.Books.Single(b => b.Id == 1);
              dbContext.Remove(b);//也可以写成ctx.Books.Remove(b);
              await dbContext.SaveChangesAsync();
              

              开源批量修改删除插件

              其他数据库

              mysql:

              Install-Package Pomelo.EntityFrameworkCore.MySql

              optionsBuilder.UseMySql("server=localhost;user=root;password=root;database=ef",
              	new MySqlServerVersion(new Version(5, 6, 0)));
              

              PostgreSQL:

              Install-Package Npgsql.EntityFrameworkCore.PostgreSQL

              optionsBuilder.UseNpgsql("Host=127.0.0.1;Database=ef;Username=postgres;Password=123456");
              

              EF Core实体的配置

              主要规则:

              1:表名采用DbContext中的对应的DbSet的 属性名。

              2:数据表列的名字采用实体类属性的名字,列的数据类型采用和实体类属性类型最兼容的类型。

              3:数据表列的可空性 取决于对应实体类属性的可空性。

              4:名字为Id的属性为主键,如果主键为short, int 或者 long类型,则默认采用自增字段,如果主键为Guid类型,则默认采用默认的Guid生成机制生成主键值。

              Data Annotation配置方式

              把配置以特性(Annotation)的形式标注在实体类中

               [Table("t_books")]
               public class Book
               {
                   public long Id { get; set; }//主键
                   [Required]
                   [MaxLength(50)]
                   public string Title { get; set; }//标题
                   public DateTime PubTime { get; set; }//发布日期
                   public double Price { get; set; }//单价
                   public string AuthorName { get; set; }
                   public override string ToString()
                   {
                       return "Id:" + this.Id + ",Title:" + this.Title + ",PubTime" + this.PubTime +",Price" + this.Price + ",AuthorName" + AuthorName;
                   }
               }
              

              Fluent API配置方式

              把配置写到单独的配置类中

               internal class BookEntityConfig : IEntityTypeConfiguration
               {
                   public void Configure(EntityTypeBuilder builder)
                   {
                       builder.ToTable("t_books");
                    
                       builder.Property(e => e.Title).HasMaxLength(50).IsRequired();
                       builder.Property(e => e.AuthorName).HasMaxLength(20).IsRequired();
                   }
               }
              

              Fluent API

              1、视图与实体类映射:

              modelBuilder.Entity().ToView("blogsView");
              

              2、排除属性映射:

              modelBuilder.Entity().Ignore(b => b. Name2);
              

              3、配置列名:

              modelBuilder.Entity().Property(b =>b.BlogId).HasColumnName("blog_id");
              

              4、配置列数据类型:

              builder.Property(e => e.Title) .HasColumnType("varchar(200)");
              

              5、配置主键

              默认把名字为Id或者“实体类型+Id“的属性作为主键,可以用HasKey()来配置其他属性作为主键。

              modelBuilder.Entity().HasKey(c => c.Number);
              

              6、可以用HasDefaultValue()为属性设定默认值

              modelBuilder.Entity().Property(b => b.Age).HasDefaultValue(6);
              

              7、索引

              modelBuilder.Entity().HasIndex(b => b.Url);
              

              复合索引

              modelBuilder.Entity().HasIndex(p => new { p.FirstName, p.LastName });
              

              唯一索引:IsUnique();聚集索引:IsClustered()

              8、…

              通过代码查看EF Core的sql语句

              方法1:标准日志

              nuget安装:Install-Package Microsoft.Extensions.Logging.Console

              public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
              optionsBuilder.UseLoggerFactory(MyLoggerFactory);
              

              完整代码:

               internal class MyDbContext: DbContext
               {
                   public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
                   public DbSet Books { get; set; }
                   protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
                   {
                       string connStr = "Server=192.168.1.193;Database=demo1; User=sa;Password=@q123; Trusted_Connection=False;MultipleActiveResultSets=true;TrustServerCertificate=true";
                       optionsBuilder.UseSqlServer(connStr);
                       optionsBuilder.UseLoggerFactory(MyLoggerFactory);
                   }
                   protected override void OnModelCreating(ModelBuilder modelBuilder)
                   {
                       base.OnModelCreating(modelBuilder);
                       modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
                   }
               }
              

              .NET EF Core(Entity Framework Core),在这里插入图片描述,词库加载错误:未能找到文件“C:\Users\Administrator\Desktop\火车头9.8破解版\Configuration\Dict_Stopwords.txt”。,操作,没有,安装,第2张

              方法2:简单日志

              此方式不需要额外的引入Logging框架

              optionsBuilder.LogTo(Console.WriteLine);
              //可以自己写代码过滤一些不需要的消息
              

              方法3:ToQueryString

              EF Core的Where方法返回的是IQueryable类型,DbSet也实现了IQueryable接口。 IQueryable有扩展方法ToQueryString()可以获得SQL

               IQueryable books = dbContext.Books.Where(b => b.Id == 2);
               string sql = books.ToQueryString();
               Console.WriteLine(sql);
              

              悲观并发控制

              悲观并发控制一般采用行锁、表锁等排他锁对资源进行锁定,确保同时只有一个使用者操作被锁定的资源。

              MYSQL方案:select * from T_Houses where Id=1 for update

              如果有其他的查询操作也使用for update来查询Id=1的这条数据的话,那些查询就会被挂起,一直到针对这条数据的更新操作完成从而释放这个行锁,代码才会继续执行。

              锁是和事务相关的,因此通过BeginTransactionAsync()创建一个事务,并且在所有操作完成后调用CommitAsync()提交事务。

              Console.WriteLine("请输入您的姓名");
              string name = Console.ReadLine();
              using MyDbContext ctx = new MyDbContext();
              using var tx = await ctx.Database.BeginTransactionAsync();
              Console.WriteLine("准备Select " + DateTime.Now.TimeOfDay);
              var h1 = await ctx.Houses.FromSqlInterpolated($"select * from T_Houses where Id=1 for update")
              	.SingleAsync();
              Console.WriteLine("完成Select " + DateTime.Now.TimeOfDay);
              if (string.IsNullOrEmpty(h1.Owner))
              {
              	await Task.Delay(5000);
              	h1.Owner = name;
              	await ctx.SaveChangesAsync();
              	Console.WriteLine("抢到手了");
              }
              else
              {
              	if (h1.Owner == name)
              	{
              		Console.WriteLine("这个房子已经是你的了,不用抢");
              	}
              	else
              	{
              		Console.WriteLine($"这个房子已经被{h1.Owner}抢走了");
              	}
              }
              await tx.CommitAsync();
              Console.ReadKey();
              

              Tips:悲观锁是独占、排他的,如果系统并发量很大的话,会严重影响性能,如果使用不当的话,甚至会导致死锁。

              乐观并发控制:并发令牌

              Update T_Houses set Owner=新值
              where Id=1 and Owner=旧值
              

              当Update的时候,如果数据库中的Owner值已经被其他操作者更新为其他值了,那么where语句的值就会为false,因此这个Update语句影响的行数就是0,EF Core就知道“发生并发冲突”了,因此SaveChanges()方法就会抛出DbUpdateConcurrencyException异常。

              1、把被并发修改的属性使用IsConcurrencyToken()设置为并发令牌。

              builder.Property(h => h.Owner).IsConcurrencyToken();
              

              2、示例代码:

              Console.WriteLine("请输入您的姓名");
              string name = Console.ReadLine();
              using MyDbContext ctx = new MyDbContext();
              var h1 = await ctx.Houses.SingleAsync(h => h.Id == 1);
              if (string.IsNullOrEmpty(h1.Owner))
              {
              	await Task.Delay(5000);
              	h1.Owner = name;
              	try
              	{
              		await ctx.SaveChangesAsync();
              		Console.WriteLine("抢到手了");
              	}
              	catch (DbUpdateConcurrencyException ex)
              	{
              		var entry = ex.Entries.First();
              		var dbValues = await entry.GetDatabaseValuesAsync();
              		string newOwner = dbValues.GetValue(nameof(House.Owner));
              		Console.WriteLine($"并发冲突,被{newOwner}提前抢走了");
              	}
              }
              else
              {
              	if (h1.Owner == name)
              	{
              		Console.WriteLine("这个房子已经是你的了,不用抢");
              	}
              	else
              	{
              		Console.WriteLine($"这个房子已经被{h1.Owner}抢走了");
              	}
              }
              Console.ReadLine();
              

              乐观并发控制:RowVersion

              SQLServer数据库可以用一个byte[]类型的属性做并发令牌属性,然后使用IsRowVersion()把这个属性设置为RowVersion类型,这样这个属性对应的数据库列就会被设置为ROWVERSION类型。对于ROWVERSION类型的列,在每次插入或更新行时,数据库会自动为这一行的ROWVERSION类型的列其生成新值。

              class House
              {
              	public long Id { get; set; }
              	public string Name { get; set; }
              	public string Owner { get; set; }
              	public byte[] RowVer { get; set; }
              }
              
              builder.Property(h => h.RowVer).IsRowVersion();
              

              Tips:乐观并发控制能够避免悲观锁带来的性能、死锁等问题,因此推荐使用乐观并发控制而不是悲观锁。

              总结:如果有一个确定的字段要被进行并发控制,那么使用IsConcurrencyToken()把这个字段设置为并发令牌即可;如果无法确定一个唯一的并发令牌列,那么就可以引入一个额外的属性设置为并发令牌,并且在每次更新数据的时候,手动更新这一列的值。


文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复:表情:
评论列表 (暂无评论,0人围观)

还没有评论,来说两句吧...

目录[+]

取消
微信二维码
微信二维码
支付宝二维码