003 Entity Framework Core 2.x P3 P4 插入数据和简单查询
博客园文章Id:12709575
在Asp .Net Core项目中使用Entity Freamwork Core
项目架构
AspEFCore.Data
依赖项
Microsort.EntityFrameworkCore.Tools
迁移命令
Microsoft.EntityFrameworkCore.Design
迁移引擎
Microsoft.Extensions.Logging.Debug
与Debug相关的日期记录
Microsoft.Extensions.Logging.Console
将日志输出到控制台的扩展
引用项目
AspEFCore.Domain.Models
AspEFCore.Domain.Models
依赖项
Microsoft.EntityFrameworkCore
与Debug相关的日期记录
Microsoft.EntityFrameworkCore.SqlServer
将日志输出到控制台的扩展
AspEFCore.Web
依赖项
Microsort.EntityFrameworkCore.Tools
迁移命令
创建以下实体类模型
using System.Collections.Generic;
namespace AspEFCore.DoMain.Models
{
public class City
{
/// <summary>
/// 主键Id
/// </summary>
public int CityId { get; set; }
/// <summary>
/// 外键Id
/// </summary>
public int ProvinceId { get; set; }
/// <summary>
/// 导航属性
/// </summary>
public Province Province { get; set; }
public IEnumerable<CityCompany> CityCompanies { get; set; }
public Mayor Mayor { get; set; }
}
}
namespace AspEFCore.DoMain.Models
{
public class CityCompany
{
public int CityCompanyId { get; set; }
public int CityId { get; set; }
public City City { get; set; }
public int CompanyId { get; set; }
public Company Company { get; set; }
}
}
using System;
using System.Collections.Generic;
namespace AspEFCore.DoMain.Models
{
public class Company
{
public Company()
{
CityCompanies = new List<CityCompany>();
}
public int CompanyId { get; set; }
public string Name { get; set; }
public DateTime EstablishDate { get; set; }
public string LegalPerson { get; set; }
public IEnumerable<CityCompany> CityCompanies { get; set; }
}
}
namespace AspEFCore.DoMain.Models
{
public class Mayor
{
public int MayOrId { get; set; }
public int CityId { get; set; }
public City City { get; set; }
}
}
using System.Collections.Generic;
namespace AspEFCore.DoMain.Models
{
public class Province
{
public Province()
{
Cities = new List<City>();
}
public int ProvinceId { get; set; }
public string Name { get; set; }
public int Population { get; set; }
public IEnumerable<City> Cities { get; set; }
}
}
注意:
- 在EFCore规则下,在实体类中凡是以实体类名称+Id的形式或直接叫Id形式的简单类型(
int
,或者是string
)的属性,EFCore默认它就是主键,并会自动自增. - EFCore上下文实例是需要
Disposable
的,所以我们在使用上下文的时候如果是采用new的方式创建了上下文实例,那么是需要使用using来进行包裹,如果使用的是容器注入的方式我们是不需要使用using的形式创建,也不需要手动的Disposable
,容器机制会自动帮助我们进行释放.
在Asp .Net Core中使用EFCore上下文的方式有两种:
- 直接在using中new 出EFCore上下文实例.
- 使用asp.net 自带的容器,进行构造函数注入的方式来使用,那么我们就不在需要使用using来包裹,EFCore上下文实例了,因为asp.net core容器会自动释放该上下文实例资源.
在 EFCore 2.x中 在asp.net core 中如果使用了AddContext方式来注入EFCore 那么会自动添加日志记录的功能,无需在另外引用依赖项,但是在EFCore3.x中,如果我们想要在控制台中显示一些,我们需要手动在DbContext
继承类中加入一些代码,详情查询EFCore对日志使用描述的 官方文档,代码如下:
我们需要在类体中加入以下静态变量.
public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
我们需要在重写的OnConfiguring
方法中配置日志工厂
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLoggerFactory(MyLoggerFactory);
}
迁移
如果我们修改过模型类的结构或者添加或修改过一些种子数据,那么我们需要先进行Add-Migration
操作,值得注意的是Add-Migration
后面起的名称不能和之前已经存在的Add-Migration
名称重复.
Add-Migration
命令执行成功之后,我们需要执行 Update-Database
命令进行迁移,到此Model
与Db
之前就已经是同步状态,知道下一次对Model进行添加,或修改时,我们需要再进行此类操作,进行模型和数据的同步操作,保证是同步状态.
EFCore Insert 操作
- _context.Provinces.Add(Province);
- MyContext开始追踪Province对象
- _context.SaceChanges();
- 检查所有MyContext正在追踪的对象
- 读取每一个对象的状态
- 生成SQL语句
- 执行所有生成的SQL语句
- 如果有返回数据的话,就获取这些返回数据
Insert 操作
var province = new Province
{
Name = "北京",
Population = 2_000_000
};
this._context.Province.Add(province);
this._context.Province.AddRange(province);
批量 Insert 操作
数据源
var province = new Province
{
Name = "北京",
Population = 2_000_000
};
var province2 = new Province
{
Name = "上海",
Population = 1_000_000
};
var province3 = new Province
{
Name = "关东",
Population = 3_000_000
};
添加追踪的方式1
this._context.Province.Add(province);
this._context.Province.Add(province2);
this._context.Province.Add(province3);
添加追踪的方式2
this._context.Province.AddRange(province,province2,province3);
添加追踪的方式3
this._context.Province.AddRange(new List<Province>
{
province,province2,province
});
提交添加功能
this._context.SaveChanges();
同时 Insert 两个不同的对象
数据源:
var province = new Province
{
Name = "天津",
Population = 8_000_000
};
var company = new Company()
{
Name = "TaiDa",
EstablishDate = new DateTime(1990,1,1),
LegalPerson = "Secret Man"
};
添加
this._context.AddRange(province, company);
提交
this._context.SaveChanges();
批量操作的大小限制
- 默认大小显示是由数据库Provider定的.
- 例如Sql Server 是1000个命令
- 如果超出该大小,那么超出的部分将作为另外的批次来执行.
如果想修改此限制也是可以的,我们可以在AddDbContext方法中,修改限制,代码如下:
options.UseSqlServer(
_configuration.GetConnectionString("DefaultConnectionString"),
opts=>opts.MaxBatchSize(1000000)); //修改一次提交的命令的数量限制
});
查询
//查询所有provinces表的数据
var provinces = _context.Province.ToList();
//带过滤条件的查询
var provinces2 = _context.Province
.Where(x => x.Name == "北京")
.ToList();
//使用Linq的方式查询
var provinces3 = (from p in _context.Province
where p.Name == "上海"
select p).ToList();
值得注意的是,在进行.ToList()
方法之前,无论是查询方法的方式(表达式树),还是Linq表达式的方式,都只是构建了一个查询表达式,并没有实际去查询数据库,我们可以理解成是在拼接sql的过程,只有执行到类似如.ToList()
或者.ToDictionary()
或者.ToArray()
等方法的时候才会正在的发送生成好的sql脚本到数据库中去进行查询.
查询注意事项:
案例代码如下:
var provinces4 = _context.Province
.Where(x => x.Name == "北京");
foreach (var province in provinces4)
{
Console.WriteLine(province.Name);
/*执行了一些耗时方法*/
}
上述代码的问题在于,变量provinces4
此时只是相当于一个表达式树,在使用foreach
对这个表达式树进行遍历时,此表达式树会生成相应的sql去请求数据库查询数据,但是如果这个foreach
循环执行的时间比较长的话,会导致数据库连接长期处于被打开状态(引发数据库性能问题),此时如果有别的操作来,操作此表的话,就会出现一些意想不到的事情,所以在遍历之前,我们最好使用ToList()
方法之类的方式,先将此表达式应对的数据查询到内存中(此处数据库连接就会被关闭掉),再对查询到数据进行处理.
EFCore 查询参数细节问题.
- 我们最好不要在查询方法,或者Linq表达式中直接使用查询条件,而是将查询条件赋值给一个变量,来传入,这样生成的参数就会是参数化的参数,下列是比较.
-
.Where(x=>x.Name == "Beijing")
- Select * from xxx as x where x.Name = 'Beijing';
-
var param = 'Beijing';
-
xxx.Where(x=>x.Name == param)
- paramters=@_param_0='Beijing'
- ... Where[x].[Name]=@_param_0
下面列举会将延迟加载立即执行的Linq方法:
- ToList(); //返回一个List集合
- First(); //查询符合条件中的第一笔,如果没有符合条件的就会报错.
- FirstOrDefault();//查询符合条件中的第一笔,如果没有符合条件的就会为null.
- Single(); //查询返回的结果只会为一个,如果一个都没有或者多余一个就会报错.
- SingleOrDefault();查询返回的结果只会为一个,如果一个都没有返回null多余一个就会报错.
- Last(); //必须先排序才能使用此方法,不排序的话会造成性能问题
- LastOrDefault(); //必须先排序才能使用此方法 不排序的话会造成性能问题
- Count();
- LongCount();
- Max();
- Min();
- Average();
下面列举会立即执行查询的非Linq方法
- Find(主键);
find方法的优点,如果我们要查找的数据已经在内存中了,并且别Context追踪的情况下,find不会再去查询数据库,而是在内存中查询数据.
EF中之前的Like查找的方法:
EF中Like查找的新方法:
修改数据
从数据库中查询一个数据,并修改其中的一个属性值直接调用saveChanges就可以更新了.
因为province对象被查询出来之后,就一直被context进行追踪,所以当只修改某一个字段的时候,EFCore也之后更新这一个字段,而不会更新别的未修改的字段到数据库中.
EFCore也可以通知执行多个sql命令,比如在一个操作中,又进行添加,和修改操作.
修改离线对象 (没有被EFCore追踪的对象)
修改EFCore上下文注入实例的方式
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<MyContext>(options => //默认为是Scope方式的生命周期,即一次Http请求只生成一个上下文实例.
{
options.EnableSensitiveDataLogging(true); //展示敏感数据的记录
//option.UseSqlServer("Server =(localdb)\MSSQLLocalDB;Database=AspEFCoreDemo; Trusted_Connection=True");
options.UseSqlServer(
_configuration.GetConnectionString("DefaultConnectionString"),
opts=>opts.MaxBatchSize(1000000)); //修改一次提交的命令的数量限制
}, ServiceLifetime.Scoped); //我们可以修改此注入的生命周期
}
在修改EFCore上下文实例的生命周期之后,我们通过一个例子来描述如何修改离线对象.
上面的例子中,当province对象,被Update之后,就会被_context2所追踪,直到被_context2.SaveChanges(); 之后,才会将追踪状态改为已修改.
但是这种修改离线数据有个弊端,就是这种方式会将除了主键之外,所有的字段都修改一遍,无论值有没有变都会修改一下,离线修改就相当于告诉_dbContext2 此对象有变化,但是具体哪些字段有变化,不清楚,所以你要所有的字段(除了主键)都update一下.
EFCore也提供了批量修改的方法.UpdateRange()方法
.
删除
删除已追踪的数据.
删除离线数据的情况.
上下文对象会先追踪离线对象,然后再根据此对象进行删除.
EFCore也提供了批量删除的方法RemoveRange()
.
当我们只知道id的情况下,如果我们想要删除数据,只能先通过这个id将指向的数据查询出来,之后在将这个对象Remove才可以.
EFCore 执行存储过程的写法:
_context_DataBase.ExecuteSqlCommand("exec Del...");
EFCore 执行原生sql
执行命令: DbContext.Database.ExcuteSqlCommand(); //EFCore 3.x中已弃用
执行查询: DbSet.FromSql(); //EFCore 3.x中已弃用