【问题标题】:Entity Framework circular dependency for last entity最后一个实体的实体框架循环依赖
【发布时间】:2017-02-25 15:44:28
【问题描述】:

请考虑以下实体

public class What {
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Track> Tracks { get; set; }
    public int? LastTrackId { get; set; }]
    public Track LastTrack { get; set; }
}

public class Track {
    public Track(string what, DateTime dt, TrackThatGeoposition pos) {
        What = new What { Name = what, LastTrack = this };
    }

    public int Id { get; set; }

    public int WhatId { get; set; }
    public What What { get; set; }
}

我使用以下配置实体:

builder.HasKey(x => x.Id);
builder.HasMany(x => x.Tracks).
    WithOne(y => y.What).HasForeignKey(y => y.WhatId);
builder.Property(x => x.Name).HasMaxLength(100);
builder.HasOne(x => x.LastTrack).
    WithMany().HasForeignKey(x => x.LastTrackId);

你有没有看到需要循环引用:

What.LastTrack <-> Track.What

当我尝试将 Track 添加到上下文时(实际上是在 SaveChanges 上):

Track t = new Track("truc", Datetime.Now, pos);
ctx.Tracks.Add(t);
ctx.SaveChanges();

我收到以下错误:

无法保存更改,因为在要保存的数据中检测到循环依赖:''What' {'LastTrackId'} -> 'Track' {'Id'}, 'Track' {'WhatId'} -> '什么' {'Id'}'。

我想说...是的,我知道但是...

EF Core 可以进行这样的配置吗?

【问题讨论】:

  • 请显示导致错误的代码。这可能是一个先有鸡还是先有蛋的问题(两个新对象需要彼此的外键)。
  • @GertArnold 就是这样。现在我通过两次提交来解决它。恐怕我没有保留确切的旧代码。
  • 你确实需要两次提交,别无选择。您可能希望将它们包装在 TransactionScope 中。
  • @GertArnold 允许回滚两个提交?
  • 是的,这就是TransactionScope 的常用用途。

标签: entity-framework entity-framework-core


【解决方案1】:

这就是我喜欢称之为偏爱孩子的问题:父母有多个孩子,但其中一个特别特别。这会导致现实生活中的问题......以及数据处理中的问题。

在您的班级模型中,What(顺便说一句,这是一个合理的名称吗?)将 Tracks 作为子级,但其中之一,LastTrackWhat 保留引用的特殊子级.

WhatTracks 在一个事务中创建时,EF 将尝试使用生成的What.Id 插入新的Tracks 和WhatId。但在它可以保存What 之前,它需要最后一个Track 的生成ID。由于 SQL 数据库不能同时插入记录,所以这种循环引用不能在一个孤立的事务中建立。

您需要一个事务来保存What 及其Tracks,并需要一个后续事务来设置What.LastTrackId

要在一个数据库事务中执行此操作,您可以将代码包装在 TransactionScope 中:

using(var ts = new TransactionScope())
{
    // do the stuff
    ts.Complete();
}

如果发生异常,ts.Complete(); 将不会发生,并且会在 TransactionScope 被释放时发生回滚。

【讨论】:

    【解决方案2】:

    我遇到了同样的问题,但我用不同的方式解决了。

    就我而言,它是关于状态列表和对最后状态的引用。因此,以下情况:

    public class What {
        public int Id { get; set; }
        public string Name { get; set; }
        public ICollection<Status> StatusList { get; set; }
        public int? LastStatusId { get; set; }
        public Status LastStatus { get; set; }
    
        public void AddStatus(Status s)
        {
            StatusList.Add(s);
            LastStatus = s;
        }
    }
    
    public class Status{
        public int Id { get; set; }
    
        public int WhatId { get; set; }
        public What What { get; set; }
    }
    

    在我的程序中,我将代码更改为使用 StatusList 作为不包括 lastStatus 的历史记录,所以:

    public class What {
        public int Id { get; set; }
        public string Name { get; set; }
        public ICollection<Status> StatusHistory { get; set; }
        public int? LastStatusId { get; set; }
        public Status LastStatus { get; set; }
    
        public void AddStatus(Status s)
        {
            if(LastStatus) StatusList.Add(LastStatus);
            LastStatus = s;
        }
    
        public List<Status> GetStatusList(Status s) // If needed, a method, not a property because i got an error with lazyLoading
        {
            return new List<Status>(StatusHistory) { LastStatus}; // List of all status (history + last)
        }
    }
    
    public class Status{
        public int Id { get; set; }
    
        public int? WhatId { get; set; }
        public What What { get; set; }
    }
    

    并且不要忘记将您的上下文 IsRequired(false) 放在 foreignKey 上:

    builder.HasMany(x => x.Status).
        WithOne(y => y.What).HasForeignKey(y => y.WhatId).IsRequired(false);
    

    这样,不要再循环引用了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-23
      • 2017-10-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多