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

EF Core如何始终加载所有导航属性?解决循环引用问题

解决EF Core全局AutoInclude循环引用问题及完整对象加载方案

问题根源

你遇到的循环引用错误是因为实体间存在闭环依赖:Order.ArticlePositions → Article.DeliveryNote → DeliveryNote.Order,全局AutoInclude会尝试递归加载所有导航,EF Core检测到这种无限循环后抛出错误。

可行解决方案

方案1:手动排除循环中的导航,保留全局AutoInclude

在OnModelCreating中,对循环链中的部分导航关闭AutoInclude,打破闭环。例如排除DeliveryNote.Order、Article.Order和Article.DeliveryNote:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    var entities = modelBuilder.Model.GetEntityTypes();
    foreach (var entity in entities)
    {
        var navigations = entity.GetNavigations();
        foreach (var navigation in navigations)
        {
            // 排除循环中的导航,避免无限递归
            var skipAutoInclude = 
                (entity.ClrType == typeof(DeliveryNote) && navigation.Name == nameof(DeliveryNote.Order)) ||
                (entity.ClrType == typeof(Article) && navigation.Name == nameof(Article.Order)) ||
                (entity.ClrType == typeof(Article) && navigation.Name == nameof(Article.DeliveryNote));

            modelBuilder.Entity(entity.ClrType)
                .Navigation(navigation.Name)
                .AutoInclude(!skipAutoInclude);
        }
    }
}

方案2:封装显式Include扩展方法(推荐)

全局AutoInclude在复杂项目中灵活性不足,显式Include可以精确控制加载的导航,同时避免循环。针对每个实体封装通用的Include方法:

public static class EntityQueryExtensions
{
    public static IQueryable<Order> IncludeAllRelated(this IQueryable<Order> query)
    {
        return query
            // 加载Customer及关联的Contact
            .Include(o => o.Customer)
                .ThenInclude(c => c.Contact)
            // 加载订单下的商品
            .Include(o => o.ArticlePositions)
            // 加载订单下的送货单及关联商品
            .Include(o => o.DeliveryNotes)
                .ThenInclude(dn => dn.ArticlePositions);
    }
}

查询时调用该方法:

var berndsFirstOrder = RemoteDbContext.Orders
    .IncludeAllRelated()
    .Where(order => order.Customer.Contact.Firstname == "Bernd")
    .FirstOrDefault();

方案3:启用延迟加载(谨慎使用)

EF Core的延迟加载会在访问导航属性时自动加载关联数据,但容易引发N+1查询问题,在Blazor渲染场景中可能影响性能。配置步骤:

  1. 安装包:Microsoft.EntityFrameworkCore.Proxies
  2. 在DbContext中启用代理:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseLazyLoadingProxies();
}
  1. 将所有导航属性改为virtual:
public virtual Customer Customer { get; set; }
public virtual List<Article> ArticlePositions { get; set; }
// 其他导航属性同理

方案4:使用DTO投影(性能最优)

如果仅用于数据展示,推荐创建DTO类,通过投影获取所需数据,避免循环和不必要的字段加载:

// 定义DTO类
public class OrderDto
{
    public int Id { get; set; }
    public decimal TotalPrice { get; set; }
    public CustomerDto Customer { get; set; }
    public List<ArticleDto> ArticlePositions { get; set; }
    public List<DeliveryNoteDto> DeliveryNotes { get; set; }
}

public class CustomerDto
{
    public int Id { get; set; }
    public ContactDto Contact { get; set; }
}

// 省略ArticleDto、DeliveryNoteDto等定义

// 查询投影
var berndsFirstOrder = RemoteDbContext.Orders
    .Where(o => o.Customer.Contact.Firstname == "Bernd")
    .Select(o => new OrderDto
    {
        Id = o.Id,
        TotalPrice = o.TotalPrice,
        Customer = new CustomerDto
        {
            Id = o.Customer.Id,
            Contact = new ContactDto { Firstname = o.Customer.Contact.Firstname }
        },
        ArticlePositions = o.ArticlePositions.Select(a => new ArticleDto
        {
            Id = a.Id,
            Name = a.Name,
            Price = a.Price
        }).ToList(),
        DeliveryNotes = o.DeliveryNotes.Select(dn => new DeliveryNoteDto
        {
            Id = dn.Id,
            ArticlePositions = dn.ArticlePositions.Select(a => new ArticleDto
            {
                Id = a.Id,
                Name = a.Name,
                Price = a.Price
            }).ToList()
        }).ToList()
    })
    .FirstOrDefault();

总结

  • 小型无循环项目:可以用全局AutoInclude配合循环导航排除
  • 需要完整实体:优先封装显式Include扩展方法
  • Blazor数据展示:推荐使用DTO投影,性能最优且避免循环
  • 延迟加载仅适合简单场景,需注意N+1问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 06:27:08