.NET Core EF实体导航属性引发循环引用问题求助
问题描述
我正在学习开发Web API,遇到了循环引用的问题:
用Scaffold-DbContext命令生成了TpVoStock和TpVoVehiclesAdvertisement两个实体类,想建立TpVoStock可选关联一个TpVoVehiclesAdvertisement的关系。添加导航属性后,执行包含Advertisement的查询时,返回结果出现循环引用——TpVoStock包含Advertisement,该Advertisement又包含原TpVoStock,循环往复。
我是.NET Core和EF的新手,试过调整导航属性写法、使用Fluent API等方法都无法解决,恳请帮忙!
相关代码
TpVoVehiclesAdvertisement实体
public partial class TpVoVehiclesAdvertisement { [Key] public int Id { get; set; } [Column("stockId")] public int? StockId { get; set; } [StringLength(50)] [Unicode(false)] public string? Vin { get; set; } [ForeignKey("StockId")] public virtual TpVoStock Stock { get; set; } }
TpVoStock实体
public partial class TpVoStock { [Key] public int Id { get; set; } public int? StatusId { get; set; } public int DestCountryId { get; set; } public virtual TpVoVehiclesAdvertisement Advertisement { get; set; } }
StockDbContext上下文
public partial class StockDbContext : DbContext { public StockDbContext() { } public StockDbContext(DbContextOptions<StockDbContext> options) : base(options) { } public virtual DbSet<TpVoStock> TpVoStocks { get; set; } public virtual DbSet<TpVoVehiclesAdvertisement> TpVoVehiclesAdvertisements { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) //#warning To protect potentially sensitive information in your connection string, you should move it out of source code. //You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. //For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263. => optionsBuilder.UseSqlServer("SomeConnectionString"); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.UseCollation("Czech_CI_AS"); modelBuilder.Entity<TpVoStock>(entity => { entity.ToTable("TP_VO_STOCK", tb => { tb.HasComment("VO Cars Stock"); tb.HasTrigger("TriggerLogChanges_TP_VO_STOCK"); }); entity.Property(e => e.StatusId).HasDefaultValueSql("((0))"); }); modelBuilder.Entity<TpVoVehiclesAdvertisement>(entity => { entity.HasKey(e => e.Id).HasName("PK__TP_VO_VE__3214EC0752946F6B"); }); //modelBuilder.Entity<TpVoStock>() // .HasOne<TpVoVehiclesAdvertisement>(s => s.Advertisement); have tried to do this one OnModelCreatingPartial(modelBuilder); } partial void OnModelCreatingPartial(ModelBuilder modelBuilder); }
返回结果示例
//Stock [{"id":10944,"statusId":2,"destCountryId":1, //Advertisement "advertisement":{"id":1,"stockId":10944,"vin":"VF3LCBHZHFS232605", //Again same TpVoStock "stock": {"id":10944,"statusId":2,"destCountryId":1, //And Again same TpVoAdvertisement "advertisement":{"id":1,"stockId":10944,"vin":"VF3LCBHZHFS232605"}]
解决方案
循环引用的本质是实体间互相包含导航属性,JSON序列化时会触发无限递归,以下是几种实用解决方法:
1. 使用DTO(数据传输对象)【推荐】
不要直接返回EF实体,定义只包含业务所需字段的DTO类,从根源避免循环引用:
public class StockDto { public int Id { get; set; } public int? StatusId { get; set; } public int DestCountryId { get; set; } public AdvertisementDto? Advertisement { get; set; } } public class AdvertisementDto { public int Id { get; set; } public int? StockId { get; set; } public string? Vin { get; set; } // 此处不包含Stock导航属性,仅保留必要字段 }
查询时将实体映射为DTO返回:
var stocks = await _context.TpVoStocks .Include(s => s.Advertisement) .Select(s => new StockDto { Id = s.Id, StatusId = s.StatusId, DestCountryId = s.DestCountryId, Advertisement = s.Advertisement != null ? new AdvertisementDto { Id = s.Advertisement.Id, StockId = s.Advertisement.StockId, Vin = s.Advertisement.Vin } : null }) .ToListAsync(); return Ok(stocks);
2. 配置JSON序列化忽略循环引用
在Program.cs中添加JSON序列化配置,让系统自动忽略循环引用:
builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; });
3. 调整EF导航属性或关闭延迟加载
- 关闭延迟加载:在DbContext配置中禁用延迟加载代理:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("SomeConnectionString") .UseLazyLoadingProxies(false); }
或者将实体中导航属性的virtual修饰符移除(EF默认对虚拟属性启用延迟加载):
// 调整TpVoVehiclesAdvertisement public TpVoStock? Stock { get; set; } // 调整TpVoStock public TpVoVehiclesAdvertisement? Advertisement { get; set; }
- 用Fluent API明确配置关系:在
OnModelCreating中补充关系配置,确保EF正确识别关联:
modelBuilder.Entity<TpVoStock>() .HasOne(s => s.Advertisement) .WithOne(a => a.Stock) .HasForeignKey<TpVoVehiclesAdvertisement>(a => a.StockId) .IsRequired(false); // 对应StockId可空的可选关联
内容的提问来源于stack exchange,提问作者Kharitone
相关产品推荐
相关产品推荐

