You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core 3.1.9关联查询CariHareket重复行及分组支持缺失问题

解决EF Core 3.1中Include多集合导致CariHareket数据重复的问题

这是EF Core在关联多个导航集合时的典型笛卡尔积问题,我来给你拆解原因和可行的解决方案:

问题根源

当你同时IncludeKontrat下的多个子集合(比如KontratKalemler、KontratTarihBaglantilar这些),EF Core会生成一张多表Join的SQL,子集合的行数相乘就会把主表CariHareket的记录重复返回。而且EF Core 3.1确实不支持直接对带导航属性的查询用GroupBy去重,得换思路处理。

先回顾下你的查询代码和实体定义:

当前查询代码

hareketService.GetAll(Predicate())//表名:CariHareket
    .Include(s => s.Cari)
    .Include(s => s.HedefHareketCariVirman)
    .Include(s => s.HareketCari)
    .Include(s => s.Kontrat).ThenInclude(s => s.KontratKalemler)
    .Include(s => s.Kontrat).ThenInclude(s => s.KontratTarihBaglantilar)
    .Include(s => s.Kontrat).ThenInclude(s => s.FaturaTalimativeTurkceFatura)
    .Include(s => s.Kontrat).ThenInclude(s => s.AraciCari)

关键实体定义

public class CariHareket {
    [Key]
    public int ID { get; set; }
    [ForeignKey(nameof(CariID))]
    public virtual Cariler Cari { get; set; }
    public int? CariID { get; set; }
    [ForeignKey(nameof(KontratID))]
    public virtual KontratUst Kontrat { get; set; }
    public int? KontratID { get; set; }
    [ForeignKey(nameof(HareketCariID))]
    public virtual Cariler HareketCari { get; set; }
    public int? HareketCariID { get; set; }
    [ForeignKey(nameof(HedefHareketCariVirmanID))]
    public virtual Cariler HedefHareketCariVirman { get; set; }
    public int? HedefHareketCariVirmanID { get; set; }
    // 其他属性...
}

public class Kontrat {
    // 其他属性...
    [InverseProperty("Kontrat")]
    public virtual BindingList<KontratMasraflarNotlar> KontratMasraflarNotlar { get; set; }
    [InverseProperty("Kontrat")]
    public virtual BindingList<KontratKalem> KontratKalemler { get; set; }
    [InverseProperty("Kontrat")]
    public virtual BindingList<KontratTarihBaglantilar> KontratTarihBaglantilar { get; set; }
    [InverseProperty("Kontrat")]
    public virtual BindingList<KontratTurkceFaturaKalem> FaturaTalimativeTurkceFatura { get; set; }
    // 其他子集合...
}

可行解决方案

方案1:用拆分查询(Split Queries)最简单

EF Core 3.1从3.0开始支持拆分查询,它会为每个导航集合单独生成SQL,彻底避免笛卡尔积。只需要在查询末尾加AsSplitQuery():

hareketService.GetAll(Predicate())
    .Include(s => s.Cari)
    .Include(s => s.HedefHareketCariVirman)
    .Include(s => s.HareketCari)
    .Include(s => s.Kontrat).ThenInclude(s => s.KontratKalemler)
    .Include(s => s.Kontrat).ThenInclude(s => s.KontratTarihBaglantilar)
    .Include(s => s.Kontrat).ThenInclude(s => s.FaturaTalimativeTurkceFatura)
    .Include(s => s.Kontrat).ThenInclude(s => s.AraciCari)
    .AsSplitQuery(); // 加这一句就行

EF Core会自动拆分多个查询,然后在内存里把数据组合起来,不会返回重复的CariHareket记录,这个方案最省心。

方案2:手动分步加载(性能最优)

如果拆分查询不符合你的场景(比如担心多查询的开销),可以分三步加载数据:

// 1. 先拉主表数据,只加载Kontrat主实体,不加载子集合
var cariHareketler = hareketService.GetAll(Predicate())
    .Include(s => s.Cari)
    .Include(s => s.HedefHareketCariVirman)
    .Include(s => s.HareketCari)
    .Include(s => s.Kontrat) // 只加载Kontrat本身,不包含子集合
    .ToList();

// 2. 提取所有需要的KontratID,批量加载所有子集合
var kontratIds = cariHareketler
    .Where(ch => ch.KontratID != null)
    .Select(ch => ch.KontratID.Value)
    .Distinct()
    .ToList();

var kontratsWithDetails = _context.Kontrats
    .Include(k => k.KontratKalemler)
    .Include(k => k.KontratTarihBaglantilar)
    .Include(k => k.FaturaTalimativeTurkceFatura)
    .Include(k => k.AraciCari)
    .Where(k => kontratIds.Contains(k.ID))
    .ToList();

// 3. 把加载好的Kontrat关联回主集合
foreach (var ch in cariHareketler)
{
    if (ch.KontratID != null)
    {
        ch.Kontrat = kontratsWithDetails.FirstOrDefault(k => k.ID == ch.KontratID);
    }
}

这种方式完全由你控制数据加载,没有笛卡尔积,数据量大时性能比拆分查询更好。

方案3:内存中去重(适合小数据集)

如果你的数据量不大,可以先把查询结果拉到内存,再用DistinctBy去重。EF Core 3.1本身没有DistinctBy,可以自己实现或者引入System.Linq.Dynamic.Core包:

var cariHareketler = hareketService.GetAll(Predicate())
    .Include(s => s.Cari)
    .Include(s => s.HedefHareketCariVirman)
    .Include(s => s.HareketCari)
    .Include(s => s.Kontrat).ThenInclude(s => s.KontratKalemler)
    .Include(s => s.Kontrat).ThenInclude(s => s.KontratTarihBaglantilar)
    .Include(s => s.Kontrat).ThenInclude(s => s.FaturaTalimativeTurkceFatura)
    .Include(s => s.Kontrat).ThenInclude(s => s.AraciCari)
    .AsEnumerable() // 转为内存集合
    .DistinctBy(ch => ch.ID) // 按主键去重
    .ToList();

注意:这个方案会先把所有重复数据加载到内存,大数据量时会吃内存,谨慎使用。

为什么GroupBy不行?

EF Core 3.1对GroupBy的支持很有限,当查询包含导航属性时,GroupBy会被转为客户端评估,而且无法正确保留导航集合的完整数据,所以确实不能用它来解决这个重复问题。上面的三个方案都是更可靠的替代方案。

内容的提问来源于stack exchange,提问作者lkmcelik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 17:17:30