EntityFramework Core基本的增删改查

boredbird 2020-06-26

参考资料:
杨旭视频: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。数据也添加成功了:

EntityFramework Core基本的增删改查

在调用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方法添加单笔数据。运行了一下是可以直接执行成功的。

EntityFramework Core基本的增删改查

可以看到先进行了一次查询,又进行了三次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");
}

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

EntityFramework Core基本的增删改查

查询

查询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语句:

EntityFramework Core基本的增删改查

可以看到变成了参数的形式,本来是字符串的形式。但可以看到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()等。

删除

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

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

EntityFramework Core基本的增删改查

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

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

AsNoTracking()

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

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

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

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

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

相关推荐