EFCore学习1

《1》数据更新方法

                //方法1批量更新数据库数据,直接使用SQL语句
                ctx.Database.ExecuteSql($"UPDATE [T_Books] SET [Price] = [Price] + 2");

                //方法2 EF Core仍会为每个本书发送 UPDATE 语句,并且数据库必须单独执行每个语句
                //并且要先查询,后修改
                var bk = ctx.Books.Where(b => b.Price > 10);   
                foreach (var bi in bk)
                {
                    bi.Price = bi.Price + 1;
                }

                //方法3 从EF Core7.0开始可以使用ExecuteUpdate 和 ExecuteDelete 方法更高效地执行相同的操作
                ctx.Books.ExecuteUpdate(s => s.SetProperty(e => e.Price, e => e.Price + 1000));
                await ctx.SaveChangesAsync();
            
          //下面是批量删除           ctx.Articles.Where(b
=>b.Id>4).ExecuteDelete();

 

 《2》两种配置方式

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

[Table("T_Books")]

Public class Book

{
}

优点:简单;缺点:耦合度高,不灵活

2、Fluent API

builder.ToTable("T_Books");

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

缺点:复杂;优点:解耦

《3》EFCore中查看SQL语句方法

方法1:标准日志

private static ILoggerFactory loggerFactory = LoggerFactory.Create(b => b.AddConsole());

然后在OnConfig方法最后一行添加下面语句,这样就输出到控制台里面了。

optionsBuilder.UseLoggerFactory(loggerFactory);

方法2:简单日志

然后在OnConfig方法最后一行添加下面语句,这样就输出到控制台里面了。

只是用起来简单,输出的数据比较多需要自己过滤。

optionsBuilder.LogTo(msg=> { Console.WriteLine(msg); });

方法3:ToQuerystring

《4》EFCore关系数据库中,一个表中的多个外键数据,指向另外一张表出现异常如下面所示

Leave类中有两个User实体一个是Requester,另外是Approver

public User Requester { get; set; }
public User Approver { get; set; }

将这两个实体设定成User外键时会提示下面错误,代码如下

        public void Configure(EntityTypeBuilder<Leave> builder)
        {
            builder.ToTable("T_Leaves");
            builder.HasOne<User>(a => a.Requester).WithMany().IsRequired();
            builder.HasOne<User>(a => a.Approver).WithMany();
        }

下面是提示错误

 将 FOREIGN KEY 约束 'FK_T_Leaves_T_User_RequesterId' 引入表 'T_Leaves' 可能会导致循环或多重级联路径。请指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。 无法创建约束或索引。

后面在第二个实体后面添加.OnDelete(DeleteBehavior.Restrict);后正常下面是更改后的代码

        public void Configure(EntityTypeBuilder<Leave> builder)
        {
            builder.ToTable("T_Leaves");
            builder.HasOne<User>(a => a.Requester).WithMany().IsRequired();
            builder.HasOne<User>(a => a.Approver).WithMany().OnDelete(DeleteBehavior.Restrict);
        }

 

《5》自引用的组织结构树报错

.NET 8.0框架

在生成“T_OrtUnits”表时报错误,代码如下图所示

        public void Configure(EntityTypeBuilder<OrgUnit> builder)
        {
            builder.ToTable("T_OrgUnits");
            builder.Property(o => o.Name).IsUnicode().IsRequired().HasMaxLength(50);
            builder.HasOne<OrgUnit>(o => o.Parent).WithMany(o => o.Childrens); //根节点没有Parent,因此这个关系不能修饰为“不可为空”
        }

 错误提示如下: 

将 FOREIGN KEY 约束 'FK_T_OrgUnits_T_OrgUnits_ParentId' 引入表 'T_OrgUnits' 可能会导致循环或多重级联路径。请指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。
无法创建约束或索引。请参阅前面的错误。

下面更改如下正常了,或者要添加.OnDelete(DeleteBehavior.Restrict);

        public void Configure(EntityTypeBuilder<OrgUnit> builder)
        {
            builder.ToTable("T_OrgUnits");
            builder.Property(o => o.Name).IsUnicode().IsRequired().HasMaxLength(50);
            builder.HasOne<OrgUnit>(o => o.Parent).WithMany(o => o.Childrens).IsRequired(false); //根节点没有父节点,所以此关系不能修饰为”不可为空“
        }

《6》IQueryable和IEnumerable区别

                //服务器端评估,所有数据查询处理都在服务端执行
                IQueryable<Article> arts = ctx.Articles.Where(a => a.Title.Contains("微软"));
                foreach (Article article in arts)
                {
                    Console.WriteLine(article.Title);
                }

                //客户端评估,只执行最简单查询,其它数据处理操作在本机内存中执行
                IEnumerable<Article> articles1 = ctx.Articles;
                IEnumerable<Article> articles  = articles1.Where(a => a.Title.Contains("微软"));
                foreach (var article in articles)
                {
                    Console.WriteLine(article.Title);
                }

 

《6》IQueryable复用

IQueryable要用终结语句才会执行数据库查询和处理操作,利用这个特性可以进行IQueryable查询条件复用

                //IQueryable的复用,第一条是复用语句,第二条和第三条执行数据库查询是都会加上第一条语句条件
                IQueryable<Article> articles = ctx.Articles.Where(b => b.Title.Contains("微软"));
                Console.WriteLine(articles.Count());
                Console.WriteLine(articles.Max(b => b.Id));

上面这段代码执行了两次数据库查询操作,第一条是复用语句,生成的数据库语句如下

//第二行代码生成数据库语句
SELECT MAX(t.Id)
FROM T_Articles AS t
WHERE t.Title LIKE N'%微软%'),
//第三行代码生成的数据库语句
SELECT MAX([t].[Id])
FROM [T_Articles] AS [t]
WHERE [t].[Title] LIKE N'%微软%'

从上面生成的数据库语句中可以看出,两段SQL语句都执行第一条的复用语句

《7》IQueryable读取数据库的方式

1:DataReader:分批从数据库服务器读取数据。内存占用小,DB连接占用时间长;

2:DataTable:把所有数据都一次性从数据库服务器中加载到客户端内存中。内存占用大,节省DB连接。

IQueryable内部就是在调用DataReader。缺点:如果处理的慢,会长时间占用连接。

要想一次性加载数据到内存:用IQueryable的ToArray(),ToArrayAsync(),ToList(),ToListAsync()等方法。

下面代码是一次性加载数据到内存中,去掉ToArray()就是分批读从数据库读取数据

                foreach(var a in ctx.Articles.ToArray())
                {
                    Console.WriteLine(a.Title);
                    Thread.Sleep(10);
                }

何时需要一次加载

1、场景1:遍历IQueryable并且进行数据处理的过程很耗时。

2、场景2:如果方法需要返回查询结果,并且在方法里销毁DbContext的话,是不能返回lQueryable的。必须一次性加载返回。
3、场景3:多个IQueryable的遍历嵌套。很多数据库的ADO.NET Core Provider是不支持多个DataReader同时执行的。把连接字符串中的MultipleActiveResultSets=true删掉,其他数据库不支持这个。

如果是其它数据库需要多个DataReader同时执行,也是要把要读取数据全部加载到内存中,不用分批读取就可以了,也是后面加载ToArray()方法

《8》EF Core执行原生SQL语句

1:执行非查询原生SQL语句

string name = ";delete from T_Articles;";
await ctx.Database.ExecuteSqlInterpolatedAsync($@"insert into T_Articles (Title,Message) select Title,{name}
                       from T_Articles where Id >= 3");

2:执行实体相关查询语句

 string titlePattern = "%微软%";
 var queryable =  ctx.Articles.FromSqlInterpolated($"select * from T_Articles where Title like {titlePattern} order by newid()");//查询返回的是IQueryable类型,下面要遍历才会执行SQL语句
 foreach ( var article in queryable )
 {
     Console.WriteLine( article.Id + article.Title );
 }
//EF Core 7.0 中引入了 FromSql。 使用更旧的版本时,请改用 FromSqlInterpolated。 下面是用FromSql查询
var blogs = ctx.Articles
.FromSql($"select * from T_Articles where Price='23'")
.ToList();

 

3:执行原生SQL语句查询

                DbConnection conn = ctx.Database.GetDbConnection();
                if (conn.State != System.Data.ConnectionState.Open)
                {
                    await conn.OpenAsync();
                }

                using (var cmd = conn.CreateCommand())
                {
                    cmd.CommandText = "select Price,Count(*) from T_Articles group by Price";
                    using (var reader = await cmd.ExecuteReaderAsync())
                    {
                        while (await reader.ReadAsync())
                        {
                            string price = reader.GetString(0);
                            int count = reader.GetInt32(1);
                            Console.WriteLine($"{price}:{count}");
                        }
                    }
                }

 下面是引用Dapper框架执行原生SQL语句查询,

GroupArticleByPrice是要手动添加的类
    class GroupArticleByPrice
    {
        public string Price { get; set; }
        public int Pcount {  get; set; }
    }

 

                var items=ctx.Database.GetDbConnection().Query<GroupArticleByPrice>("select Price,Count(*) Pcount from T_Articles group by Price");
                foreach(var item in items)
                {
                    Console.WriteLine(item.Price.ToString()+ ":"+ item.Pcount);
                }

 

《9》EF Core如何检测实体类数据发生改变,使用DbContext的Entry()方法来获得实体在EF Core中的跟踪信息对象EntityEntry 

(1)实体的状态

1:已添加(Added),2:未改变(Unchanged),3:已修改(Modified),4:已删除(Deleted),5:已分离(Detached)。

首次跟踪一下实体的时候,EF Core会创建这个实体的快照。执行SaveChanges()等方法时,EF Core将会把存储的快照中的值与实体的当前值进行比较。

EntityEntry类的State属性代表实体的状态,通过DebugView.LongView的属性可以看到实体的变化信息。代码如下

                var items  =ctx.Articles.Take(3).ToArray();
                var a1 = items[0];
                var a2 = items[1];
                var a3 = items[2];

                var a4 = new Article { Title = "ddd", Message = "这是个测试" };
                var a5 = new Article { Title = "new", Message = "流星蝴蝶剑" };

                a1.Price += 1;
                ctx.Remove(a2);
                ctx.Articles.Add(a4);

                EntityEntry e1 = ctx.Entry(a1);
                EntityEntry e2 = ctx.Entry(a2);
                EntityEntry e3 = ctx.Entry(a3);
                EntityEntry e4 = ctx.Entry(a4);
                EntityEntry e5 = ctx.Entry(a5);
                Console.WriteLine(e1.State);
                Console.WriteLine(e2.State);
                Console.WriteLine(e3.State);
                Console.WriteLine(e4.State);
                Console.WriteLine(e5.State);

下面是输出的信息

(2) 如果查询出来的对象不会被修改,删除等,那么查询时可以AsNoTracking(),就能降低内存占用。

《10》全局查询筛选器

在Config文件中配置后,用查询时会自动加上查询语句

            builder.HasQueryFilter(a=>a.IsDeleted==false);

如果需要屏蔽全局查询筛选器

var item =  ctx.Articles.IgnoreQueryFilters().Take(3).ToArray();

 

《11》EF Core悲欢锁,基于MySQL数据库

           Console.WriteLine("请输入名字");
           string name = Console.ReadLine();
           using (MyDbConfig ctx = new MyDbConfig())
           using (var tx = ctx.Database.BeginTransaction())
           {
               Console.WriteLine(DateTime.Now.ToString() + "准备select");
               var h = ctx.Houses.FromSqlInterpolated($"select * from T_House where Id=1 for update").Single();
               Console.WriteLine(DateTime.Now.ToString() + "已经完成select");
               if (!string.IsNullOrEmpty(h.Owner))
               {
                   if (h.Owner == name)
                   {
                       Console.WriteLine("房子已经被你抢到了");
                   }
                   else
                   {
                       Console.WriteLine("房子已经【" + h.Owner + "】占了");
                   }
                   Console.ReadKey();
                   return;
               }
               h.Owner = name;
               Thread.Sleep(5000);
               Console.WriteLine("恭喜你,抢到了");
               ctx.SaveChanges();
               Console.WriteLine(DateTime.Now.ToString() + "保存完成");
               tx.Commit();    //解锁
               Console.ReadKey();

下面是运行结果

 

《12》MySQL乐观并发

namespace 乐观并发
{
    class HouseConfig : IEntityTypeConfiguration<House>
    {
        public void Configure(EntityTypeBuilder<House> builder)
        {
            builder.ToTable("T_House");
            builder.Property(b=>b.Name).IsRequired();
            builder.Property(b=>b.Owner).IsConcurrencyToken();
        }
    }
}

 

        static void Main(string[] args)
        {
            Console.WriteLine("请输入名字");
            string name = Console.ReadLine();
            using (MyDbConfig ctx = new MyDbConfig())
            {
                var h = ctx.Houses.Single(h => h.Id == 1);
                if (!string.IsNullOrEmpty(h.Owner))
                {
                    if (h.Owner == name)
                    {
                        Console.WriteLine("房子已经被你抢到了");
                    }
                    else
                    {
                        Console.WriteLine("房子已经【" + h.Owner + "】占了");
                    }
                    Console.ReadKey();
                    return;
                }
                h.Owner = name;
                Thread.Sleep(5000);

                try
                {
                    ctx.SaveChanges();
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    Console.WriteLine("并发访问冲突");
                    var entry1 =ex.Entries.First();
                    string newValue = entry1.GetDatabaseValues().GetValue<string>("Owner");
                    Console.WriteLine("被++++++" + newValue + "抢走了");
                }
                Console.ReadKey();

            }

《12》SQLServer乐观并发

namespace 乐观并发
{
    class House
    {
        public string Name {  get; set; }

        public long Id { get; set; }

        public string Owner {  get; set; }

        public byte[] RowVersion { get; set; }
    }
}

在把RowVersion配置成.IsRowVersion属性,基它和上面MySQL乐观并发一直

namespace 乐观并发
{
    class HouseConfig : IEntityTypeConfiguration<House>
    {
        public void Configure(EntityTypeBuilder<House> builder)
        {
            builder.ToTable("T_House");
            builder.Property(b=>b.Name).IsRequired();
            builder.Property(b => b.RowVersion).IsRowVersion();
        }
    }
}

 

posted @ 2024-03-29 10:59  炽热的舞者  阅读(62)  评论(2编辑  收藏  举报