具有多对多关系的实体框架的复制/克隆

时间:2018-08-18 07:20:13

标签: entity-framework entity-framework-6 dbcontext

我正在尝试在我们的上下文中克隆实体。除了多对多表以外,其他一切都正常。

这是层次结构:

  • 计划
    • 目标
      • 资金
    • 帐户
      • 贡献流
        • 资金

“资金”表将“贡献流”链接到“目标”。一个缺陷是资金行具有FundingId,ContributionStreamId和GoalId。我不知道使用后两个作为主键。

我正在尝试复制计划以及所有较低级别的实体。根据{{​​3}},我分离了计划并更新了ID,但是Fundings表中新的“多对多”条目指向新的ContributionStreams和目标

这是代码

var detachedPlan = db.Plans
  .Include("Goals")
  .Include("Accounts")
  .Include("Accounts.ContributionStreams")
  .Include("Accounts.ContributionStreams.Fundings").AsNoTracking()
  .FirstOrDefault(p => p.PlanId == originalPlan.PlanId);

foreach (var goal in detachedPlan.Goals.ToList())
{
    Guid newGoalId = Guid.NewGuid();
    goal.GoalId = newGoalId;
}

foreach (var account in detachedPlan.Accounts.ToList())
{
    account.AccountId = Guid.NewGuid();
    foreach (var contributionStream in account.ContributionStreams.ToList())
    {
        Guid newContributionStreamId = Guid.NewGuid();
        var fundingList = contributionStream.Fundings.ToList();
        contributionStream.ContributionStreamId = newContributionStreamId;
        foreach (Funding funding in fundingList)
        {
            funding.ContributionStreamId = newContributionStreamId;
            funding.FundingId = Guid.NewGuid();
        }
    }
}
detachedPlan.PlanId = Guid.NewGuid();
detachedPlan.Name += " copy";
db.Plans.Add(detachedPlan);

您可以看到,除了替换主键funding.ContributionStreamId = newContributionStreamId;之外,我还必须创建从Funding到ContributionStream的引用。对目标进行类似的分配会创建不需要的实体副本,它们指向旧目标,而不是新的tributionStream。

Fundings类是从数据库自动生成的:

public partial class Funding
{
    public System.Guid FundingId { get; set; }
    public System.Guid ContributionStreamId { get; set; }
    public System.Guid GoalId { get; set; }
    public double Weight { get; set; }
    public string Notes { get; set; }

    public virtual ContributionStream ContributionStream { get; set; }
    public virtual Goal Goal { get; set; }
}

“资金的SQL”是

CREATE TABLE [dbo].[Fundings] (
  [FundingId]            UNIQUEIDENTIFIER NOT NULL,
  [ContributionStreamId] UNIQUEIDENTIFIER NOT NULL,
  [GoalId]               UNIQUEIDENTIFIER NOT NULL,
  [Weight] FLOAT NOT NULL DEFAULT 1, 
  [Notes] NVARCHAR(4000) NULL, 
  PRIMARY KEY CLUSTERED ([FundingId] ASC),
  CONSTRAINT [FK_Fundings_ContributionStreams] FOREIGN KEY ([ContributionStreamId]) REFERENCES [dbo].[ContributionStreams] ([ContributionStreamId]) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT [FK_Fundings_Goals] FOREIGN KEY ([GoalId]) REFERENCES [dbo].[Goals] ([GoalId])
);

1 个答案:

答案 0 :(得分:0)

好吧,我决定采用下面的“强力”解决方案。我仍然不了解为什么我无法更新创建新的target.GoalId的循环中的funding.GoalId的内部原理。无知不是幸福。

这是解决方案

// Keep track of the mapping of old goals to new goals
Dictionary<Guid, Guid> oldGoalId = new Dictionary<Guid, Guid>();

var detachedPlan = db.Plans
  .Include("Goals")
  .Include("Accounts")
  .Include("Accounts.ContributionStreams")
  .Include("Accounts.ContributionStreams.Fundings").AsNoTracking()
  .FirstOrDefault(p => p.PlanId == originalPlan.PlanId);

foreach (var goal in detachedPlan.Goals.ToList())
{
  Guid newGoalId = Guid.NewGuid();
  oldGoalId.Add(goal.GoalId, newGoalId); // record old and new goalId for the new goal

  // BTW, this seems like the right solution, but it's not.
  // This loop creates unwanted additional copies of the funding in the old Plan
  // foreach (Funding funding in goal.Fundings.ToList())
  //    funding.GoalId = newGoalId;
  goal.GoalId = newGoalId;
}

foreach (var account in detachedPlan.Accounts.ToList())
{
  account.AccountId = Guid.NewGuid();
  foreach (var contributionStream in account.ContributionStreams.ToList())
  {
    Guid newContributionStreamId = Guid.NewGuid();
    var fundingList = contributionStream.Fundings.ToList();
    contributionStream.ContributionStreamId = newContributionStreamId;
    foreach (Funding funding in fundingList)
    {
      funding.ContributionStreamId = newContributionStreamId;
      // update the GoalId here.
      // Not sure why it can't be updated in the commented out loop above
      funding.GoalId = oldGoalId[funding.GoalId];
      funding.FundingId = Guid.NewGuid();
    }
  }
}
detachedPlan.PlanId = Guid.NewGuid();
detachedPlan.Name += " copy";
db.Plans.Add(detachedPlan);