003 Entity Framework Core 2.x P3 P4 插入数据和简单查询
博客园文章Id:12709575
在Asp .Net Core项目中使用Entity Freamwork Core
项目架构
![项目架构 项目架构](./images/1587003222759.png)
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查找的方法:
![相似查询 相似查询](./images/1587044148725.png)
EF中Like查找的新方法:
![相似查询 相似查询](./images/1587044032107.png)
修改数据
从数据库中查询一个数据,并修改其中的一个属性值直接调用saveChanges就可以更新了.
![修改某个字段值 修改某个字段值](./images/1587044368224.png)
因为province对象被查询出来之后,就一直被context进行追踪,所以当只修改某一个字段的时候,EFCore也之后更新这一个字段,而不会更新别的未修改的字段到数据库中.
EFCore也可以通知执行多个sql命令,比如在一个操作中,又进行添加,和修改操作.
![同时执行多个sql命令 同时执行多个sql命令](./images/1587044633849.png)
修改离线对象 (没有被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上下文实例的生命周期之后,我们通过一个例子来描述如何修改离线对象.
![修改离线对象 修改离线对象](./images/1587045341470.png)
上面的例子中,当province对象,被Update之后,就会被_context2所追踪,直到被_context2.SaveChanges(); 之后,才会将追踪状态改为已修改.
但是这种修改离线数据有个弊端,就是这种方式会将除了主键之外,所有的字段都修改一遍,无论值有没有变都会修改一下,离线修改就相当于告诉_dbContext2 此对象有变化,但是具体哪些字段有变化,不清楚,所以你要所有的字段(除了主键)都update一下.
EFCore也提供了批量修改的方法.UpdateRange()方法
.
删除
删除已追踪的数据.
![删除 删除](./images/1587045827002.png)
删除离线数据的情况.
![删除离线对象 删除离线对象](./images/1587045950361.png)
上下文对象会先追踪离线对象,然后再根据此对象进行删除.
EFCore也提供了批量删除的方法RemoveRange()
.
当我们只知道id的情况下,如果我们想要删除数据,只能先通过这个id将指向的数据查询出来,之后在将这个对象Remove才可以.
EFCore 执行存储过程的写法:
_context_DataBase.ExecuteSqlCommand("exec Del...");
EFCore 执行原生sql
执行命令: DbContext.Database.ExcuteSqlCommand(); //EFCore 3.x中已弃用
执行查询: DbSet.FromSql(); //EFCore 3.x中已弃用