跟着杨中科学习(七)EFCore(五)

EFCore的异步

异步方法大部分是定义在Microsoft.EntityFrameworkCore这个命名空间下EntityFrameworkQueryableExtensions等类中的扩展方法,记得using。

如何异步遍历IQueryable

  • 使用ToListAsync()、ToArrayAsync()。但是注意结果集不要太大。
foreach (var a in await ctx. Articles. ToListAsync())
{
    Console. WriteLine(a. Title);
}
  • 使用await foreach(Book b inctx.Books.AsAsyncEnumerable())
await foreach (var a in ctx. Articles. AsAsyncEnumerable ())
{
	Console. WriteLine(a. Title);
}

EfCore执行非查询原生 SQL语句

使用dbCtx.Database. ExecuteSqllnterpolated ()
dbCtx.Database.ExecuteSqllnterpolatedAsync()方法来执
行原生的非查询SQL语句。

可以用来执行增删改

ctx.Database.ExecuteSqlInterpolatedAsync(@$"in
sert into T_Books(Title,PubTime,Price,AuthorName)
select Title, PubTime, Price,{aName} from T_Books
where Price > {price}");

FormattableString类型可以避免sql注入

还有一个ExecutesqlRow方法,类似于以前的sqlHelp

EFCore执行实体相关的原生查询sql语句

如果要执行的原生SQL是一个查询语句,并且查询的结
果也能对应一个实体,就可以调用对应实体的DbSet的
FromSqllnterpolated()方法来执行一个查询SQL语句,同样
使用字符串内插来传递参数。

只能执行单表查询

IQueryable<Book> books =
ctx.Books.FromSqlInterpolated(@$"select * from T_Books
where DatePart(year,PubTime)>{year}order by newid()");
  • SQL 查询必须返回实体类型对应数据库表的所有列;
  • 结果集中的列名必须与属性映射到的列名称匹配。
  • 只能单表查询,不能使用Join语句进行关联查询。但
    是可以在查询后面使用Include()来进行关联数据的获
    取。

EFCore执行纯原生的查询语句

直接通过EFCore连接ADO.NET,获得数据库连接对象

dbCxt.Database.GetDbConnection()获得ADO.NET Core的数据库连接对象。

DbConnection conn = ctx.Database.GetDbConnection();
if (conn.State != ConnectionState.Open)
{
    conn.Open();
}
using (var cmd = conn.CreateCommand())
{
    cmd.CommandText =@"xxx";
    var pl = cmd.CreateParameter();
    p1.ParameterName ="@year";
    pl.Value = year;
    cmd.Parameters.Add(p1);
    using (var reader = cmd.ExecuteReader());
}
using(MyDbContext ctx=new MyDbContext())
{
    DbConnection conn = ctx.Database.GetDbConnection();//拿到Context对应的底层的Connection
    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 fvar reader = await cmd. ExecuteReaderAsync ())
        {
            while (await reader. ReadAsync ())
            {
                double price =reader. GetDouble(0);
                int count =reader.GetInt32(1);
                Console. WriteLine($"{price}:{count}");
            }
        }
    }
}

推荐使用

Dapper等矿建执行原生复杂的sql语句。

如何知道实体数据变了

只要一个实体对象和Dbcontext发生关联,EFCore就会自动跟踪这个实体的值的变化。

在执行钱,会记录实体的快照,在执行结束后,会对比快照的变化

实体的状态

  • 已添加(Added):DbContext正在跟踪此实体,但数据库中尚不存在
    该实体。
  • 未改变(Unchanged):DbContext正在跟踪此实体,该实体存在于数据
    库中,其属性值和从数据库中读取到的值一致,未发生改变
  • 已修改(Modified):DbContext正在跟踪此实体,并存在于数据库中,
    并且其部分或全部属性值已修改。
  • 已删除(Deleted):DbContext正在跟踪此实体,并存在于数据库中,
    但在下次调用SaveChanges时要从数据库中删除对应数据。
  • 已分离(Detached):DbContext未跟踪该实体。

EFCore优化值AsNoTracking

不让EFCore进行自动跟踪

var items = ctx. Articles. AsNoTracking (). Take (3)T;
foreach (var e in items)
{
    Console. WriteLine(e. Message);
}

实体状态跟踪的妙用

不推荐使用,不利于维护

Book bl=new Book{Id=10};//跟踪通过Id定位
b1.Title = "yzk";
var entry1 = ctx.Entry(b1);
entryl.Property("Title").IsModified = true;
Console.WriteLine(entry1.DebugView.LongView);
ctx.SaveChanges();

数据的批量删除、更新、插入

addRange()后加Range表示循环批量执行sql语句。

全局查询筛选器

public void Configure (EntityTypeBuilder<Article> builder)
{
    builder. ToTable("T_Articles");
    builder. Property(a => a.Title). HasMaxLength(100). IsUnicode (). IsRequired() ;
    builder. Property(a => a. Message). IsUnicode (). IsRequired();
    builder. HasMany (a => a. Comments). WithOne(c => c. TheArticle). HasForeignKey(c =>c.TheArticleId).IsRequired();
    builder. HasQueryFilter(a=>a. IsDeleted=false;
}

忽略查询过滤器

ctx.Books.lgnoreQueryFilters().Where(b=>b.Title.Contains("o")).ToArray()

有性能陷阱

EFCore悲观并发控制

并发控制:避免多个用户同时操作资源造成并发冲突问题。举例:网上购买车票。

最好的解决方案:非数据库解决方案,也就是在程序逻辑方面解决

数据库层面的两种策略:悲观和乐观。

悲观:给车票上锁,只能我买完其他人才能买

悲观控制一般使用行锁或者表锁

EF Core没有封装悲观并发控制的使用,需要开发
人员编写原生SQL语句来使用悲观并发控制。不同数
据库的语法不一样。

Mysql

select * from T_Home where Id=1 for update
select ... for update 为锁,执行后会释放
using(MyDbContext ctx=new MyDbContext())
using(var tx=ctx.Database.BeginTransaction())
{
	var h = ctx. Houses.FromSqlInterpolated($"select * from T_Houses where
	Id=1 for update"). Single();
	ctx.SaveChange();//运行完释放
	tx.Commit();
}


乐观并发控制

乐观:并发令牌

Update T_Home set Owner is 新值 where Id=1 and Owner is 旧值

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

builder.Property(h => h.Owner).IsConcurrencyToken();
using(MyDbContext ctx =new MyDbContext())
{
    var h=ctx.House.Single(h=>h.id==1);
    h.Owner=name;
    try
    {
        cty.SaveChanges();
    }
    catch(DbUpdateConcurrencyException ex)
    {
        //主线程捕获不到异步的异常,所以需要处理子线程的异常
        var entry = ex.Entries.First();
        var dbValues = await entry.GetDatabaseValuesAsync();
        string newOwner = dbValues.GetValue<string>(nameof(Hose.Owner));
        dbValues.GetValue<string>(nameof(House.Owner));
        Console.WriteLine($"并发冲突,被{newOwner}提前抢走了”);
    }
}

RowVersion

使用情景:一个实体内,有很多个字段都不可以并发修改。

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

在实体类上增加RowVersion

class House
{
	public log Id {get;set;}
    public string Name {get;set;}
    public string Owner {get;set;}
    public byte[] RowVersion {get;set;}
}

配置实体类的配置项,设置并发属性

class HouseConfig : IEntityTypeConfiguration<House>
{
    public void Configure (EntityTypeBuilder<House> builder)
    {
        builder. ToTable("T_Houses");
        builder. Property (b => b. Name). IsRequired();
        builder. Property(b => b. RowVersion). IsRowVersion();
    }
   
}

更改代码和上面就一样了

EFCore表达式树

先跳过了

posted @   想要来杯咖啡吗  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示