• EntityFramework Core基本的增删改查以及常用的方法


    参考资料:
    杨旭视频:https://www.bilibili.com/video/BV1xa4y1v7rR?p=5

    添加

    与数据库进行交互需要用到我们的数据库上下文,我的是DemoDbContext。Context用完后需要对它进行清理资源,也就是调用它的Dispose方法,因为它实现了IDisposable接口。也可以直接使用using关键字,当方法走完时,Context就会被Dispose掉。

    添加一条League:

    class Program
    {
        static void Main(string[] args)
        {
            using var context = new DemoDbContext();
    
            var serieA = new League
            {
                Country = "Italy",
                Name = "Serie A"
            };
    
            context.Leagues.Add(serieA);    // 此时与数据库没有任何交互
            var count = context.SaveChanges();  // 此时发生与数据库交互
            Console.WriteLine(count);
        }
    }
    

    输出结果为1。数据也添加成功了:

    UTOOLS1593085922946.png

    在调用context的SaveChanges方法时,Context会检查所有它的对象的最终的状态,有的对象可能新增了,有的对象可能修改了等。SaveChanges相当于在同一个事务里,针对它的变化,执行相应的SQL语句。如果执行失败,会整体性回滚,执行成功会返回受影响的行数,这里就是1。

    添加多笔数据,有两种方式,一种是把多笔数据对应的多个对象作为AddRange方法的参数;另一种是new一个集合,把多笔数据放在集合里作为AddRange方法的参数:

    context.Leagues.AddRange(serieB, serieC);   // 方法1
    context.Leagues.AddRange(new List<League> { serieB, serieC });  // 方法2
    

    添加多笔不同类型的数据:

    using var context = new DemoDbContext();
    
    var seriaA = context.Leagues.Single(x => x.Name == "Serie A");
    
    var serieB = new League
    {
        Country = "Italy",
        Name = "Serie B"
    };
    
    var serieC = new League
    {
        Country = "Italy",
        Name = "Serie C"
    };
    
    var milan = new Club
    {
        Name = "AC Milan",
        City = "Milan",
        DateOfEstablished = new DateTime(1899, 12, 16),
        League = seriaA
    };
    
    context.AddRange(serieB, serieC, milan);
    
    var count = context.SaveChanges();
    Console.WriteLine(count);
    

    其中SeriaA是从数据库中查询到的Name为"Serie A"的实体。serieB, serieC与milan是不同类型,milan的League属性是查询出来的SeriaA。在添加多笔不同类型的数据到数据库时,直接使用context.AddRange,省去了中间的League等DbSet。直接使用context也可以使用Add方法添加单笔数据。运行了一下是可以直接执行成功的。

    UTOOLS1593095309828.png

    可以看到先进行了一次查询,又进行了三次INSERT,是符合预期的。

    输出执行的SQL语句

    上面我们在控制台中输出了SQL语句,如何做到的呢?我们在SaveChanges时,想要在控制台日志中输出执行的SQL语句。回到数据库Context类中,加一个静态只读的日志工厂:

    public static readonly ILoggerFactory ConsoleLoggerFactory =
        LoggerFactory.Create(builder =>
        {
            builder.AddFilter((category, level) =>
                category == DbLoggerCategory.Database.Command.Name
                && level == LogLevel.Information)
            .AddConsole();
        });
    

    其中AddConsole会有红色下划线,我们需要在数据库上下文所在的项目中安装Microsoft.Extensions.Logging.Console这个Nuget包。

    再看上面这个ILoggerFactory类的属性,.Net Core的日志系统会输出很多日志,我们加了一个过滤器过滤一下。范畴(category)为DbLoggerCategory.Database.Command.Name,等级(level)为Information。

    然后修改一下重写的OnConfiguring,在UseSqlServer前面加上UseLoggerFactory:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseLoggerFactory(ConsoleLoggerFactory)
            .UseSqlServer("Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=YangDemo;Integrated Security=True");
    }
    

    再次运行程序就会在控制台输出日志:

    UTOOLS1593086989644.png

    查询

    查询Leagues表中的所有数据:

    using var context = new DemoDbContext();
    
    var leagues = context.Leagues.Where(l => l.Country == "Italy").AsEnumerable();
    // var leagues = context.Leagues.Where(l => l.Country == "Italy").ToList();  // AsEnumerable与ToList结果相同,但有不同之处
    
    foreach (League league in leagues)
    {
        Console.WriteLine(league.Name);
    }
    

    其中AsEnumerable和ToList的不同之处参见:https://www.cnblogs.com/Kit-L/p/13191498.html#asenumerable方法

    如果不写ToList或者AsEnumerable等枚举查询结果集合的语句的话,只相当于组建了SQL查询语句,并不执行。下列语句是用查询表达式形式来进行查询,但一般没人这样用。

    var leagues2 = (from league in context.Leagues
                    where league.Country == "Italy"
                    select league).ToList();
    

    也可以先不ToList,而是直接用for循环来遍历查询,但这种方式最好是for循环中的操作很简单很快,否则会产生数据冲突等问题。一般也不应该这样做,最好是先ToList再从结果中读取数据。

    可以看到我们查询中的条件"Italy"是写死的,我们可以用一个参数来替换,这样生成的SQL语句中的"Italy"也会变成参数:

    var italy = "Italy";
    var leagues = context.Leagues.Where(l => l.Country == italy).AsEnumerable();
    

    看一下控制台输出的SQL语句:

    UTOOLS1593096675670.png

    可以看到变成了参数的形式,本来是字符串的形式。但可以看到itely的参数值是一个问号,但我们传进去的是"Italy"。这是因为默认情况下,EF Core不会把参数输出到日志中。可以回数据库上下文类中进行修改使其显示:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseLoggerFactory(ConsoleLoggerFactory)
    
            .EnableSensitiveDataLogging()   //
    
            .UseSqlServer("Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=YangDemo;Integrated Security=True");
    }
    

    模糊查询

    如何实现SQL语句的Country LIKE %e%这种模糊查询操作?可以使用字符串的Contains方法:

    var leagues = context.Leagues
        .Where(l => l.Country.Contains("e"))
        .AsEnumerable();
    

    还有另一种写法:

    var leagues2 = context.Leagues
        .Where(x => EF.Functions.Like(x.Country, "%e%"))
        .ToList();
    

    重要:ToList等执行对数据库的操作的方法

    总结一下哪些常用的方法可以执行对数据库的操作:

    ToList():返回集合。

    First()、FirstOrDefault():返回单笔数据,返回满足条件的第一笔数据。
    First()必须有数据,没有就报错,FirstOrDefault()可以有数据也可以没有数据。最终如果要输出的话,可以用?.的方式,如league?.Name。First()等括号中可以直接写条件,常常可以省略Where()这一块。

    Single():符合查询条件的只能是一个数据。
    SingleOrDefault():符合查询条件的只能是一个数据或者没有数据。

    Last()、LastOrDefault():最后一笔数据,其他特征同上。想使用它们,必须进行排序,比如使用OrderBy()或OrderByDescending()。

    Count()、LongCount():查询出来的Count的结果。统计个数。

    Min()、Max():最小最大值。

    Average():平均值。

    Sum():求和。

    Find():它不是LINQ方法,是DbSet的方法,但也会执行查询动作。

    还有这些方法的异步版本,比如ToListAsync()、FirstAsync()等,还有SaveChangesAsync()。

    删除

    EF Core只能删除被Context追踪的数据,而数据只有先查询出来,才能被追踪。也就是说不查询出来,是无法删除的。

    我们知道Clubs表中有一笔数据"AC Milan",我们先查出来再删掉它:

    var milan = context.Clubs.Single(x => x.Name == "AC Milan");    // 追踪数据
    
    // 调用删除方法的多种形式
    context.Clubs.Remove(milan);
    context.Remove(milan);
    context.Clubs.RemoveRange(milan, milan);
    context.RemoveRange(milan, milan);
    
    var count = context.SaveChanges();
    Console.WriteLine(count);
    

    如果想使用SQL语句或存储过程对数据库进行Delete动作的话,EF Core也是支持的。

    修改

    与删除一样,首先数据要被context追踪,才可以修改。:

    using var context = new DemoDbContext();
    
    var league = context.Leagues.First();
    
    league.Name += "~~";    // 此时context就知道这个属性已经被修改了,league对象的状态就是Modified,再SaveChange就可以把所有修改操作到数据库
    
    var count = context.SaveChanges();
    
    Console.WriteLine(count);
    

    修改多条数据:

    例如:

    var leagues = context.Leagues.Skip(1).Take(3).ToList();
    
    foreach (var league in leagues)
    {
        league.Name += "~~";
    }
    
    var count = context.SaveChanges();
    

    而在实际应用场景中,修改的数据往往都是前后端分离的系统架构里,从前端传过来的JSON,反序列化之后变成一个C#类。此时这些数据都不是context查询出来的,自然也就没追踪,也就没法进行修改。

    using var context = new DemoDbContext();
    
    var league = context.Leagues.AsNoTracking().First();    // AsNoTracking()
    
    league.Name += "++";
    
    context.Leagues.Update(league);     // 进行追踪
    
    var count = context.SaveChanges();
    
    Console.WriteLine(count);
    

    使用AsNoTracking()来模拟前端传回的数据,AsNoTracking顾名思义,就是不进行追踪,查完就跟context没有任何关系了。league就相当于前端的JSON传进来的。修改了league.Name之后,如何再次让它被Context追踪?使用Update()方法将数据传进去就可以了。它可以将league的状态设置成modified状态。

    Update()同样也有UpdateRange()方法,context上也有这个UpDate()方法,也就是说可以直接context.Update(league)

    UTOOLS1593099970333.png

    执行完发现我们虽然只修改了Name属性,但Country属性也重设了一遍,这是因为重新进行追踪后,league的所有属性都处于modified状态,相当于都修改过。

    也可以只修改Name这一个属性,开销似乎会小很多,可以查阅官方文档

    不追踪-AsNoTracking()

    不加AsNoTracking(),context查询的数据都将被它追踪,而且是不断变化地追踪。既然有变化追踪,就会不断消耗CPU和内存,开销有点大。

    如果查询的数据量比较大,而且不需要进行变化追踪,可以加上AsNoTracking()。执行完AsNoTracking(),依然处于没有执行对数据库查询地动作的状态,直到遇到ToList()等枚举查询结果的方法才执行。

    AsNoTracking()可以跟我们前面用的时候一样单独设置,也可以进行全局的设置。去数据库上下文中的构造函数中添加:

    public DemoDbContext()
    {
        ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
    }
    

    这样就算是在这个上下文的全局进行设置了,context将不会再追踪,也不用单独再在查询中写AsNoTracking()。

  • 相关阅读:
    python xlrd和xlwtxlutils包的使用
    python中的re模块和正则表达式基础
    linux定时任务crontab
    分析nginx access log日志的命令
    Git SSH Key 生成步骤
    mysql分表的3种方法
    memcached全面剖析–5. memcached的应用和兼容程序
    memcached全面剖析–4. memcached的分布式算法
    memcached全面剖析–3. memcached的删除机制和发展方向
    memcached全面剖析–2. 理解memcached的内存存储
  • 原文地址:https://www.cnblogs.com/Kit-L/p/13193532.html
Copyright © 2020-2023  润新知