使用了这么久的EntityFrameworkCore框架,今天想来就其中的一个部分来做一个知识的梳理,从而使自己对于整个知识有一个更加深入的理解,如果你对EFCore中的实体关系不熟悉你需要有一个知识的预热,这样你才能够更好的去理解整个知识,在建立好了这些实体之间的关系以后,我们可以通过使用InClude、ThenInclude这些方法来进行快速获得对应关联实体数据,用起来确实十分的方便,这里我们将通过一系列的例子来进行说明。

    1 单独使用Include

  在介绍这个方法之前,我来先贴出实体之间的关联关系,假设这里有三个相互关联的实体VehicleWarranty、WarrantyWarningLevel、VehicleWarrantyRepairHistory这三个实体后面两个都是第一个的子级并且并且VehicleWarranty、WarrantyWarningLevel之间的关系是1对1的关系,VehicleWarranty和VehicleWarrantyRepairHistory之间是1:N的关系,即1对多的关系,我们这里贴出具体的Model,从而方便后面分析具体的代码。

    /// <summary>
/// 车辆三包信息(DCSService)
/// </summary>
public class VehicleWarranty : Entity<Guid> {
public VehicleWarranty() {
Details = new List<VehicleWarrantyRepairHistory>();
} //车辆售后档案
public Guid VehicleSoldId { get; set; } //VIN
[Required]
[MaxLength(EntityDefault.FieldLength_50)]
public string Vin { get; set; } //产品分类
public Guid? ProductCategoryId { get; set; } //产品分类编号
[MaxLength(EntityDefault.FieldLength_50)]
public string ProductCategoryCode { get; set; } //产品分类名称
[MaxLength(EntityDefault.FieldLength_100)]
public string ProductCategoryName { get; set; } //车牌号
[MaxLength(EntityDefault.FieldLength_50)]
public string LicensePlate { get; set; } //发动机号
[MaxLength(EntityDefault.FieldLength_50)]
public string EngineCode { get; set; } //变速箱号
[MaxLength(EntityDefault.FieldLength_50)]
public string TransmissionSn { get; set; } //开发票日期
public DateTime InvoiceDate { get; set; } //行驶里程
public int Mileage { get; set; } //是否三包期内
public bool? IsInWarranty { get; set; } //预警等级
public Guid? WarningLevelId { get; set; } public WarrantyWarningLevel WarningLevel { get; set; } //等级编号
[MaxLength(EntityDefault.FieldLength_50)]
public string LevelCode { get; set; } //等级名称
[MaxLength(EntityDefault.FieldLength_100)]
public string LevelName { get; set; } //预警内容
[MaxLength(EntityDefault.FieldLength_800)]
public string WarningComment { get; set; } //累计维修天数
public int TotoalRepairDays { get; set; } //售出后60天/3000KM内严重故障次数
public int? FNum { get; set; } //严重安全性能故障累计次数
public int? GNum { get; set; } //发动机总成累计更换次数
public int? HNum { get; set; } //变速箱总成累计更换次数
public int? INum { get; set; } //发动机主要零件最大更换次数
public int? JNum { get; set; } //变速箱主要零件最大更换次数
public int? KNum { get; set; } //同一主要零件最大更换次数
public int? LNum { get; set; } //同一产品质量问题最大累计次数(部件+故障+方位)
public int? MNum { get; set; } //同一产品质量问题最大累计次数
public int? NNum { get; set; } public List<VehicleWarrantyRepairHistory> Details { get; set; }
} /// <summary>
/// 三包预警等级(DCS)
/// </summary>
public class WarrantyWarningLevel : Entity<Guid> {
//等级编号
[Required]
[MaxLength(EntityDefault.FieldLength_50)]
public string Code { get; set; } //等级名称
[Required]
[MaxLength(EntityDefault.FieldLength_100)]
public string Name { get; set; } //颜色
[Required]
[MaxLength(EntityDefault.FieldLength_50)]
public string Color { get; set; } //备注
[MaxLength(EntityDefault.FieldLength_200)]
public string Remark { get; set; }
} /// <summary>
/// 车辆三包信息维修履历(DCSService)
/// </summary>
public class VehicleWarrantyRepairHistory : Entity<Guid> {
//车辆三包信息
[Required]
public Guid VehicleWarrantyId { get; set; } public VehicleWarranty VehicleWarranty { get; set; } //VIN
[Required]
[MaxLength(EntityDefault.FieldLength_50)]
public string Vin { get; set; } //维修合同
public Guid RepairContractId { get; set; } //维修合同编号
[Required]
[MaxLength(EntityDefault.FieldLength_50)]
public string RepairContractCode { get; set; } //处理时间
public DateTime? DealTime { get; set; } //经销商
public Guid DealerId { get; set; } //经销商编号
[Required]
[MaxLength(EntityDefault.FieldLength_50)]
public string DealerCode { get; set; } //经销商名称
[Required]
[MaxLength(EntityDefault.FieldLength_100)]
public string DealerName { get; set; } //履历来源
public VehicleWarrantyRepairHistorySource Source { get; set; } //累计维修天数
public int? TotoalRepairDays { get; set; } //售出后60天/3000KM内严重故障次数
public int? FNum { get; set; } //严重安全性能故障累计次数
public int? GNum { get; set; } //发动机总成累计更换次数
public int? HNum { get; set; } //变速箱总成累计更换次数
public int? INum { get; set; } //发动机主要零件最大更换次数
public int? JNum { get; set; } //变速箱主要零件最大更换次数
public int? KNum { get; set; } //同一主要零件最大更换次数
public int? LNum { get; set; } //同一产品质量问题最大累计次数(部件+故障+方位)
public int? MNum { get; set; } //同一产品质量问题最大累计次数
public int? NNum { get; set; }
}

  这里我们贴出第一个简单的查询示例,通过Include方法来一下子查询出关联的三包预警等级这个实体,在我们的例子中我们返回的结果是带分页的,而且会根据前端传递的Dto来进行过滤,这里我们来看这段代码怎么实体。

/// <summary>
/// 查询车辆三包信息
/// </summary>
/// <param name="input">查询输入</param>
/// <param name="pageRequest">分页请求</param>
/// <returns>带分页的三包预警车辆信息</returns>
public async Task<Page<GetVehicleWarrantiesOutput>> GetVehicleWarrantiesAsync(GetVehicleWarrantiesInput input, PageRequest pageRequest) {
var queryResults = _vehicleWarrantyRepository.GetAll()
.Include(v => v.WarningLevel)
.Where(v => _vehicleSoldRepository.GetAll().Any(vs => vs.Status == VehicleStatus.实销完成 && v.Vin == vs.Vin));
var totalCount = await queryResults.CountAsync();
var pagedResults = await queryResults.ProjectTo<GetVehicleWarrantiesOutput>(_autoMapper.ConfigurationProvider).PageAndOrderBy(pageRequest).ToListAsync();
return new Page<GetVehicleWarrantiesOutput>(pageRequest, totalCount, pagedResults);
}

  在这里我们看到了通过一个Include就能够查询出关联的实体,为什么能够实现,那是因为在VehicleWarranty实体中存在WarrantyWarningLevel实体的外键,并且这里还增加了外键关联的实体,这样才能够正确使用InClude方法,并且这个InClude方法只能够以实体作为参数,不能以外键作为参数,到了这里我想提出一个问题,这里最终生成的SQL(SqlServer数据库)是left join 还是inner join呢?在看完后面的分析之前需要思考一下。

select top (20)
[v].[EngineCode],
[v].[GNum],
[v].[Id],
[v.WarningLevel].[Color] as [LevelColor],
[v].[LevelName],
[v].[LicensePlate],
[v].[ProductCategoryName],
[v].[TotoalRepairDays],
[v].[Vin]
from [VehicleWarranty] as [v]
left join [WarrantyWarningLevel] as [v.WarningLevel] on [v].[WarningLevelId] = [v.WarningLevel].[Id]
where EXISTS(
select 1
from [VehicleSold] as [vs]
where ([vs].[Status] = 7) and ([v].[Vin] = [vs].[Vin]))
order by [v].[Vin]

  这里我们看到生成的SQL语句是left join ,那么这里为什么不是inner join呢?这里先给你看具体的答案吧?这里你看懂了吗?问题就处在我这里建立的外键是可为空的 public Guid? WarningLevelId { get; set; }、如果是不可为空的外键那么生成的SQL就是inner join这个你可以亲自尝试。另外有一个需要提醒的就是,如果你像上面的实体中建立了VehicleWarranty、WarrantyWarningLeve之间的关系的话,迁移到数据库会默认生成外键约束,这个在使用的时候需要特别注意,但是如果你只是添加了外键而没有添加对应的外键同名的实体是不会生成外键约束关系的,这个暂时不理解里面的实现机制。

  2 主清单使用Include

  刚才介绍的是1对1的关联关系,那么像VehicleWarranty、VehicleWarrantyRepairHistory之间有明显的主清单关系,即一个VehicleWarranty对应多个VehicleWarrantyRepairHistory的时候使用InClude方法会生成什么样的SQL语句呢?这里我也贴出代码,然后再来分析生成的SQL语句。  

/// <summary>
/// 查询特定的三包预警车辆信息
/// </summary>
/// <param name="id">特定Id</param>
/// <returns>特定的三包预警车辆信息</returns>
public async Task<GetVehicleWarrantyWithDetailsOutput> GetVehicleWarrantyWithDetailsAsync(Guid id) {
var query = await _vehicleWarrantyRepository.GetAll()
.Include(v => v.WarningLevel)
.Include(v => v.Details)
.SingleOrDefaultAsync(v => v.Id == id);
if (null == query) {
throw new ValidationException("找不到当前特定的三包预警车辆信息");
} var retResult = ObjectMapper.Map<GetVehicleWarrantyWithDetailsOutput>(query);
return retResult;
}

  这里使用了两个InClude方法,那么EFCore会怎么生成这个SQL呢?通过查询最终的SQL我们发现EFCore在处理这类问题的时候是分开进行查询,然后再合并到查询的实体中去的,所以在这个查询的过程中生成的SQL如下:

select top (2)
[v].[Id],
[v].[EngineCode],
[v].[FNum],
[v].[GNum],
[v].[HNum],
[v].[INum],
[v].[InvoiceDate],
[v].[IsInWarranty],
[v].[JNum],
[v].[KNum],
[v].[LNum],
[v].[LevelCode],
[v].[LevelName],
[v].[LicensePlate],
[v].[MNum],
[v].[Mileage],
[v].[NNum],
[v].[ProductCategoryCode],
[v].[ProductCategoryId],
[v].[ProductCategoryName],
[v].[TotoalRepairDays],
[v].[TransmissionSn],
[v].[VehicleSoldId],
[v].[Vin],
[v].[WarningComment],
[v].[WarningLevelId],
[v.WarningLevel].[Id],
[v.WarningLevel].[Code],
[v.WarningLevel].[Color],
[v.WarningLevel].[Name],
[v.WarningLevel].[Remark]
from [VehicleWarranty] as [v]
left join [WarrantyWarningLevel] as [v.WarningLevel] on [v].[WarningLevelId] = [v.WarningLevel].[Id]
where [v].[Id] = @__id_0
order by [v].[Id] select
[v.Details].[Id],
[v.Details].[DealTime],
[v.Details].[DealerCode],
[v.Details].[DealerId],
[v.Details].[DealerName],
[v.Details].[FNum],
[v.Details].[GNum],
[v.Details].[HNum],
[v.Details].[INum],
[v.Details].[JNum],
[v.Details].[KNum],
[v.Details].[LNum],
[v.Details].[MNum],
[v.Details].[NNum],
[v.Details].[RepairContractCode],
[v.Details].[RepairContractId],
[v.Details].[Source],
[v.Details].[TotoalRepairDays],
[v.Details].[VehicleWarrantyId],
[v.Details].[Vin]
from [VehicleWarrantyRepairHistory] as [v.Details]
inner join (
select distinct [t].*
from (
select top (1) [v0].[Id]
from [VehicleWarranty] as [v0]
left join [WarrantyWarningLevel] as [v.WarningLevel0]
on [v0].[WarningLevelId] = [v.WarningLevel0].[Id]
where [v0].[Id] = @__id_0
order by [v0].[Id]
) as [t]
) as [t0] on [v.Details].[VehicleWarrantyId] = [t0].[Id]
order by [t0].[Id]

  这个在查询的过程中会分作几个SQL查询并且会将前面查询的结果作为后面查询的部分条件来进行了,待整个查询完毕后再在内存中将这些结果组合到一个query对象中。

  3 ThenInclude用法

  上面的介绍完了之后,你应该能够明白这个Include的具体含义和用法了,接着上面的例子,如果WarrantyWarningLevel里面还有通过外键Id去关联别的实体,这个时候ThenInclude就派上了用场了,理论上只要彼此之间建立了这种外键关系就可以一直ThenInClude下去,但是一般情况下不会用到这么复杂的情况,当然这里面每一个Include也都是作为一个单独的查询来进行的,这个也可以找具体的例子进行试验,这里也贴出一个具体的例子吧。

public async Task<GetRepairContractDetailForSettlementOutput> GetById(Guid id) {
var repairContract = await _repairContractRepository.GetAll()
.Include(d => d.RepairContractWorkItems)
.ThenInclude(w => w.Materials)
.FirstOrDefaultAsync(r => r.Id == id); if (repairContract == null)
throw new ValidationException(_localizer["当前维修合同不存在"]);
var vehicleSold = _vehicleSoldRepository.Get(repairContract.VehicleId);
var isTrafficSubsidy = _repairContractManager.IsTrafficSubsidy(repairContract.Id);
var (nextMaintenanceMileage, nextMaintenanceTime) = _repairContractManager.GetNextMaintainInfo(repairContract, vehicleSold);
var result = new GetRepairContractDetailForSettlementOutput() {
Id = repairContract.Id,
Code = repairContract.Code,
CustomerName = repairContract.CustomerName,
CellPhoneNumber = repairContract.CellPhoneNumber,
Vin = repairContract.Vin,
LicensePlate = repairContract.LicensePlate,
NextMaintenanceTime = nextMaintenanceTime,
NextMaintenanceMileage = nextMaintenanceMileage,
LaborFee = repairContract.LaborFee,
LaborFeeAfter = repairContract.LaborFeeAfter,
MaterialFee = repairContract.MaterialFee,
MaterialFeeAfter = repairContract.MaterialFeeAfter,
OutFee = repairContract.OutFee,
OtherFee = repairContract.OtherFee,
TotalFeeAfter = repairContract.TotalFeeAfter,
ShowIsTrafficSubsidy = isTrafficSubsidy,
LastMaintenanceTime = vehicleSold.LastMaintenanceTime,
LastMaintenanceMileage = vehicleSold.LastMaintenanceMileage,
WorkItems = _mapper.Map<IList<GetRepairContractWorkItemForSettlementOutput>>(repairContract.RepairContractWorkItems)
};
return result;
}

  最后还要介绍一种极特殊的情况,由于ThenInclude方法只能一层层向下进行,如果我想对同一个实体里面的两个关联实体做ThenInclude操作这个怎么处理,这里就直接给出代码吧。

/// <summary>
///维修合同完成的事件
/// </summary>
/// <param name="repairContractId"></param>
public void Finished(Guid repairContractId) {
var repairContract = _repairContractRepository.GetAll()
.Include(c => c.RepairContractWorkItems).ThenInclude(wi => wi.Materials)
.Include(c => c.RepairContractWorkItems).ThenInclude(wi => wi.Fault)
.SingleOrDefault(c => c.Id == repairContractId);
var repairContractAdjusts = _repairContractAdjustRepository.GetAll()
.Include(a => a.WorkItems).ThenInclude(w => w.Materials)
.Where(a => a.RepairContractId == repairContractId).ToList(); var @event = new AddRepairContractEvent {
Key = repairContract?.Code,
RepairContract = repairContract,
RepairContractAdjusts = repairContractAdjusts
};
_producer.Produce(@event);
}

  这里需要Include同一个实体两次,然后分别调用ThenInclude方法,这个属于比较特殊的情况,在使用的时候需要注意。

  温馨提示:

  这里读者在看代码的时候可能不太理解类似这种 _repairContractRepository的具体由来,这里贴出一份完整的代码。

internal class AddRepairContractEventManager : DomainService, IAddRepairContractEventManager {
private readonly KafkaProducer _producer;
private readonly IRepository<RepairContract, Guid> _repairContractRepository;
private readonly IRepository<RepairContractAdjust, Guid> _repairContractAdjustRepository; public AddRepairContractEventManager(KafkaProducer producer,
IRepository<RepairContract, Guid> repairContractRepository,
IRepository<RepairContractAdjust, Guid> repairContractAdjustRepository) {
_producer = producer;
_repairContractRepository = repairContractRepository;
_repairContractAdjustRepository = repairContractAdjustRepository;
} /// <summary>
///维修合同完成的事件
/// </summary>
/// <param name="repairContractId"></param>
public void Finished(Guid repairContractId) {
var repairContract = _repairContractRepository.GetAll()
.Include(c => c.RepairContractWorkItems).ThenInclude(wi => wi.Materials)
.Include(c => c.RepairContractWorkItems).ThenInclude(wi => wi.Fault)
.SingleOrDefault(c => c.Id == repairContractId);
var repairContractAdjusts = _repairContractAdjustRepository.GetAll()
.Include(a => a.WorkItems).ThenInclude(w => w.Materials)
.Where(a => a.RepairContractId == repairContractId).ToList(); var @event = new AddRepairContractEvent {
Key = repairContract?.Code,
RepairContract = repairContract,
RepairContractAdjusts = repairContractAdjusts
};
_producer.Produce(@event);
}
}

  

EFCore中的导航属性的更多相关文章

  1. 在实体对象中访问导航属性里的属性值出现异常“There is already an open DataReader associated with this Command which must be closed first”

    在实体对象中访问导航属性里的属性值出现异常“There is already an open DataReader associated with this Command which must be ...

  2. EntityFramework 6.x和EntityFramework Core关系映射中导航属性必须是public?

    前言 不知我们是否思考过一个问题,在关系映射中对于导航属性的访问修饰符是否一定必须为public呢?如果从未想过这个问题,那么我们接下来来探讨这个问题. EF 6.x和EF Core 何种情况下必须配 ...

  3. 你所不知道的库存超限做法 服务器一般达到多少qps比较好[转] JAVA格物致知基础篇:你所不知道的返回码 深入了解EntityFramework Core 2.1延迟加载(Lazy Loading) EntityFramework 6.x和EntityFramework Core关系映射中导航属性必须是public? 藏在正则表达式里的陷阱 两道面试题,带你解析Java类加载机制

    你所不知道的库存超限做法 在互联网企业中,限购的做法,多种多样,有的别出心裁,有的因循守旧,但是种种做法皆想达到的目的,无外乎几种,商品卖的完,系统抗的住,库存不超限.虽然短短数语,却有着说不完,道不 ...

  4. .NET ORM 导航属性【到底】可以解决什么问题?

    写在开头 从最早期入门时的单表操作, 到后来接触了 left join.right join.inner join 查询, 因为经费有限,需要不断在多表查询中折腾解决实际需求,不知道是否有过这样的经历 ...

  5. 《Entity Framework 6 Recipes》中文翻译系列 (22) -----第五章 加载实体和导航属性之延迟加载

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第五章 加载实体和导航属性 实体框架提供了非常棒的建模环境,它允许开发人员可视化地使 ...

  6. 《Entity Framework 6 Recipes》中文翻译系列 (23) -----第五章 加载实体和导航属性之预先加载与Find()方法

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-2  预先加载关联实体 问题 你想在一次数据交互中加载一个实体和与它相关联实体. ...

  7. 《Entity Framework 6 Recipes》中文翻译系列 (26) ------ 第五章 加载实体和导航属性之延缓加载关联实体和在别的LINQ查询操作中使用Include()方法

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-7  在别的LINQ查询操作中使用Include()方法 问题 你有一个LINQ ...

  8. EFCode First 导航属性

    首先谈谈自己对EF的接触的过程吧,最先接触EF只是因为EF支持从数据库把关系扒下来,可以省掉自己写Select.Update.Insert这些SQL语句,而且修改非常方便,后来在使用的过程中发现导航属 ...

  9. EF Code First 导航属性 与外键

    一对多关系 项目中最常用到的就是一对多关系了.Code First对一对多关系也有着很好的支持.很多情况下我们都不需要特意的去配置,Code First就能通过一些引用属性.导航属性等检测到模型之间的 ...

随机推荐

  1. CF1188B/E Count Pairs(数学)

    数同余的个数显然是要把\(i,j\)分别放到\(\equiv\)的两边 $ (a_i + a_j)(a_i^2 + a_j^2) \equiv k \bmod p $ 左右两边乘上\((a_i-a_j ...

  2. Redis恢复数据

    对于单点或者集群,都可以用 cat data.txt | redis-cli --pipe方式进行冷恢复. 对于大数据量会很慢,但不会出错.

  3. Shell编程—企业生产案例

    Linux系统Shell编程—企业生产案例(一) 企业数据库可以说是重点保护对象啊,没有之一,数据在当今企业里就是生命线,因此今天就来说一说,如何通过shell脚本来检查或监控MYSQL数据库服务是否 ...

  4. 2、vueJs基础知识02

    vue生命周期: 钩子函数: created -> 实例已经创建 √ beforeCompile -> 编译之前 compiled -> 编译之后 ready -> 插入到文档 ...

  5. 单一职责原则(SRP)

    内聚性:一个模块的组成元素之间的功能相关性.就一个类而言,应该仅有一个引起它变化的原因.当需求变化时,该变化会反映为类的职责的变化,如果一个类承担了多于一个的职责,那么引起它变化的原因就会有多个.如果 ...

  6. ubuntu之路——day6(今天对数据集的建立有了更深的体会)

    两个重点: 一.举个例子,如果建立一个图像识别的数据集,你的训练集和你的训练验证集是从网上爬下来的(也就是说这些图片的大小.像素.后期制作都可能很精美),你真正的测试集是用户的手机上传(不同的手机.环 ...

  7. 简单实用的PCM音频播放器--沉寂几年之后回归的第一份笔记

    ---恢复内容开始--- PCM音频网络流播放,至于用处,就不多解释了. 一个简单的类,基于NAudio,一个简单的拼装类,实例化时三个参数,依次是采样率,系统播放设备Index,播放声道,调用Pla ...

  8. qt creator中常用快捷键

    激活欢迎模式 Ctrl + 1 激活编辑模式 Ctrl + 2 激活调试模式 Ctrl + 3 激活项目模式 Ctrl + 4 激活帮助模式 Ctrl + 5 激活输出模式 Ctrl + 6 查找当前 ...

  9. 005 文档API

    1.索引API 下面的请求把JSON对象添加到school索引,_doc映射下. 关于POST请求,如果存在索引,则更新:如果不存在,则添加. POST school/_doc/1 { "n ...

  10. Hystrix原理与实战(转)

    背景 分布式系统环境下,服务间类似依赖非常常见,一个业务调用通常依赖多个基础服务.如下图,对于同步调用,当库存服务不可用时,商品服务请求线程被阻塞,当有大批量请求调用库存服务时,最终可能导致整个商品服 ...