EF Core分离实体延迟加载PerformedServices报错及总价计算需求
遇到InvalidOperationException错误:尝试在OrderProxy类型的分离实体上延迟加载导航属性PerformedServices,EF Core不支持对分离实体或AsNoTracking加载的实体进行延迟加载。未使用AsNoTracking方法,仅在Order类的构造函数中绑定PerformedServices的CollectionChanged事件时触发该错误。
需求:实现订单总价计算,且当任意服务的总价变化时能通知更新。
错误详情
InvalidOperationException: An error was generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning': An attempt was made to lazy-load navigation 'PerformedServices' on a detached entity of type 'OrderProxy'. Lazy loading is not supported for detached entities or entities that are loaded with 'AsNoTracking'. This exception can be suppressed or logged by passing event ID 'CoreEventId.DetachedLazyLoadingWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
相关代码
// ObservableObject implements INotifyPropertyChanged public class Order : ObservableObject { public Order() { // I'm getting error here PerformedServices.CollectionChanged += PerformedServices_CollectionChanged; } private void PerformedServices_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) foreach (INotifyPropertyChanged item in e.NewItems) item.PropertyChanged += PerformedServicesItem_PropertyChanged; if (e.OldItems != null) foreach (INotifyPropertyChanged item in e.OldItems) item.PropertyChanged -= PerformedServicesItem_PropertyChanged; } private void PerformedServicesItem_PropertyChanged(object? sender, PropertyChangedEventArgs e) { var ps = (PerformedServices?)sender; if (e.PropertyName == nameof(ps.SummaryPrice)) OnPropertyChanged(nameof(OrderTotalPrice)); } public int Id { get; set; } public int ClientId { get; set; } public virtual Client? Client { get; set; } public DateTime CompleteDate { get; set; } public virtual ObservableCollection<Service> Services { get; set; } = new(); public virtual ObservableCollection<PerformedServices> PerformedServices { get; set; } = new(); public int OrderTotalPrice => PerformedServices.Sum(ps => ps.SummaryPrice); }
方案1:延迟绑定集合事件
EF Core的延迟加载代理在实体分离时,构造函数中直接访问virtual导航属性会触发延迟加载失败。改成在实体加载/附加到上下文后再绑定事件:
private bool _eventsAttached = false; // 在实体加载完成后调用此方法绑定事件 public void AttachCollectionEvents() { if (_eventsAttached) return; PerformedServices.CollectionChanged += PerformedServices_CollectionChanged; // 给已存在的服务项绑定属性变更事件 foreach (var item in PerformedServices) { if (item is INotifyPropertyChanged notifyItem) notifyItem.PropertyChanged += PerformedServicesItem_PropertyChanged; } _eventsAttached = true; }
使用示例:从数据库加载Order后,调用order.AttachCollectionEvents();。
方案2:禁用延迟加载,改用预先加载
去掉导航属性的virtual修饰符避免生成代理,查询时预先加载PerformedServices:
1. 修改Order类
移除Services和PerformedServices的virtual关键字:
public ObservableCollection<Service> Services { get; set; } = new(); public ObservableCollection<PerformedServices> PerformedServices { get; set; } = new();
2. 查询时预先加载集合
var order = dbContext.Orders .Include(o => o.PerformedServices) .FirstOrDefault(o => o.Id == orderId);
实体不会生成延迟加载代理,构造函数中访问集合不会触发错误,事件绑定正常生效。
方案3:封装集合字段避免触发延迟加载
用私有字段封装导航集合,确保初始化时不触发延迟加载:
public Order() { _performedServices = new ObservableCollection<PerformedServices>(); _performedServices.CollectionChanged += PerformedServices_CollectionChanged; } private ObservableCollection<PerformedServices> _performedServices; public ObservableCollection<PerformedServices> PerformedServices { get => _performedServices; set { if (_performedServices != value) { _performedServices.CollectionChanged -= PerformedServices_CollectionChanged; _performedServices = value; _performedServices.CollectionChanged += PerformedServices_CollectionChanged; // 给新集合中的项绑定属性变更事件 foreach (var item in _performedServices) { if (item is INotifyPropertyChanged notifyItem) notifyItem.PropertyChanged += PerformedServicesItem_PropertyChanged; } } } }
方案4:临时抑制警告(不推荐)
仅作为临时应急方案,在DbContext中屏蔽该警告,但无法解决根本问题:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder .UseSqlServer("你的连接字符串") .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.DetachedLazyLoadingWarning)); }
内容的提问来源于stack exchange,提问作者Mideks

