【问题标题】:Adding Unchanged items to an Object Graph full of Added items将未更改的项目添加到充满已添加项目的对象图中
【发布时间】:2015-09-13 07:00:04
【问题描述】:

我有一个名为 Option 的域对象。它是更大的测验模式的一部分。该表中的前两个主键 (Id) 与 Text 列的值“true”和“false”相关。但在那之后,该表中的项目对这个问题的范围没有影响。 “真”和“假”这两行的意义在于它们会被重复使用很多次。

另请注意,选项表是一个标识(自动增量)表。我的 OptionMap 文件中确实有以下内容:

this.HasKey(t => t.Id);
this.Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

显然,我不想每次在测验中添加一个以真假为选项的新问题时,都在该表中添加真假。

在客户端的 JavaScript 中,我对其进行了编码,以便在创建新测验并添加 true 和/或 false 作为问题的选项时,将 Id 1 和 2 添加到发送的对象中到服务器(分别为 true 和 false - 回忆前 2 行,如帖子顶部所述)。当问题没有使用其中任何一个作为选项时,发送到服务器的选项的 Id 是未定义的。

在服务器上,在我的服务中,我编写了以下代码,它能够检测到 true 或 false 已作为选项添加,并尝试使用现有的 true/false 存储值(如适用)来自选项表:

foreach (var question in newQuestions)
{       
    _quizRepository.AddQuestion(question);
    var options = question.QuestionWithOptions.Select(q => q.Option).ToList();

    // This loop is an edge case. true and false will be very common answers. Rather than storing them over and over again,
    // this loop will re-use the already stored answers for true and false, which complies with the quiz schema.
    foreach (var option in options)
    {
        if (option.Text.Equals("true", StringComparison.OrdinalIgnoreCase) ||
            option.Text.Equals("false", StringComparison.OrdinalIgnoreCase))
        {
            var storedOption = _quizRepository.Context.Option.Single(o => o.Id == option.Id);
            foreach (var questionWithOption in question.QuestionWithOptions)
            {
                if (questionWithOption.Option.Id == storedOption.Id)
                {
                    questionWithOption.Option = storedOption;
                    _quizRepository.Context.Entry(questionWithOption.Option).State = EntityState.Unchanged;
                }
            }
        }                    
    }

    _quizRepository.SaveChanges();
}

我尝试将状态设置为未更改和已修改,但每次调用 SaveChanges 时,它都会为真假添加新行。如上所述,这正是我想要避免的。

任何帮助我弄清楚如何使用现有的“真”和“假”选项保存这些新问题将不胜感激。

编辑 - 添加域类

问题:

public partial class Question
{
    public Question()
    {
        QuestionWithOptions = new List<QuestionWithOption>();
        QuizWithQuestions = new List<QuizWithQuestion>();
    }
    public int Id { get; set; }
    public string Text { get; set; }
    public int? idTimeLimit { get; set; }
    public QuestionType idType { get; set; }

    public virtual ICollection<QuestionWithOption> QuestionWithOptions { get; set; }
    public virtual ICollection<QuizWithQuestion> QuizWithQuestions { get; set; }
}

QuestionWithOption:

public partial class QuestionWithOption
{
    public int Id { get; set; }
    public int idQuestion { get; set; }
    public int idOption { get; set; }
    public bool CorrectAnswer { get; set; }
    public string Letter { get; set; }
    public virtual Option Option { get; set; }
    public virtual Question Question { get; set; }
}

选项:

public partial class Option
{
    public Option()
    {
        this.QuestionWithOptions = new List<QuestionWithOption>();
    }

    public int Id { get; set; }
    public string Text { get; set; }
    public virtual ICollection<QuestionWithOption> QuestionWithOptions { get; set; }
}

添加问题方法

这会在以下两行代码之间切换(我正在尝试任何事情)

    public void AddQuestion(Question question)
    {
        //((TTSWebinarsContext) db).Question.Attach(question);
        ((TTSWebinarsContext) db).Question.Add(question);
    }

编辑 - 最终评论

让我补充一下这种情况有多荒谬。在同一个操作中,向选项表添加新选项(而不是使用现有的选项 1 和 2),插入 QuestionWithOption 表的值是正确的值,即 1 和 2。因此,操作本身不是甚至声音。选项表中添加了一些冗余值,这些值甚至不是操作中任何内容的 FK。我开始了解 NHibernate 的粉丝。

【问题讨论】:

  • 可以为 Question、QuestionWithOption 和 Option 添加代码吗?
  • AddQuestion 的代码也可以提供帮助。
  • AddQuestion 中的 db 是从哪里来的?我尝试实现您的代码并且一切正常,但我确保我在同一上下文中进行了所有更改。每个上下文都会跟踪它自己的变化,所以也许这就是问题所在。
  • @MrSangrief AddQuestion 在存储库中,它使用相同的上下文。当你说它有效时,你确定它没有为真假添加新行吗?
  • 我已将我的代码添加为 anwser。您当时没有发布您的源代码,所以我尝试自己重新创建您的设置。

标签: entity-framework


【解决方案1】:

另一个选项将不会弄乱状态(但需要更多资源):迭代选项,替换默认答案,记住这些选项中的索引。然后在迭代结束后,按照您记住的索引删除所有项目。

我这样做是因为不想过多插手EF的事情……

编辑:

甚至更好:添加一个未映射到 Option 类的字段,如果要删除该选项,请设置此选项。替换后执行list.RemoveAll(f =&gt; f.Flag);

另一个编辑:

而且我认为你的循环搞混了。我会 - 如果我正确地看到结构 - 将其重写为:

void storeQustionsOptimized( QUESTION newQuestions CONTEXT _quizRepository )
{
    var defaultOptionTrue = xxx;
    var defaultOptionFalse = xxx;

    foreach (var question in newQuestions)
    {     
        foreach (var questionWithOption in question.QuestionWithOptions)
        {
            if (questionWithOption.Option.Text.Equals("true", StringComparison.OrdinalIgnoreCase) )
            {
                questionWithOption.Option = defaultOptionTrue;
            }
            else if( questionWithOption.Option.Text.Equals("false", StringComparison.OrdinalIgnoreCase))
            {
                 questionWithOption.Option = defaultOptionFalse;
            }
        }
        _quizRepository.AddQuestion(question);
    }

    _quizRepository.SaveChanges();        
}

【讨论】:

  • 你的意思是修改FKs并在SaveChanges之后删除新的true-false Options,然后再次调用SaveChanges?那可能行得通。下次有机会我会试试的。
  • 不,在safechanges之前做,这样它们就不会被创建
  • 再次查看代码:您是否通过 context.Options.Add(..) 明确添加选项,因为否则读取您的代码,不应创建它们,因为唯一的参考将来自问题对象,它们不再属于...
  • 循环的逻辑完全符合我的要求。它只是不保存。还需要更多的时间才能回到这一点。谢谢。
  • 还要检查这段代码: var storedOption = _quizRepository.Context.Option.Single(o => o.Id == option.Id) - 您正在检索手头已有的选项...
【解决方案2】:

我认为这是错误:

questionWithOption.Option = storedOption;
_quizRepository.Context.Entry(questionWithOption.Option).State = EntityState.Unchanged;

应该通过以下方式解决:

questionWithOption.Option = storedOption;

如果不将选项的实体附加到上下文,它应该使用提供的选项。

【讨论】:

  • 那行不通。出现以下错误:附加类型为“CUWebinars.Business.Models.Option”的实体失败,因为同一类型的另一个实体已经具有相同的主键值。如果图中的任何实体具有冲突的键值,则在使用“附加”方法或将实体的状态设置为“未更改”或“已修改”时,可能会发生这种情况。这可能是因为某些实体是新实体,尚未收到数据库生成的键值。在这种情况下,使用 'Add' 方法或 'Added' 实体状态来跟踪图形,然后将非新实体的状态设置为
  • “未更改”或“已修改”(视情况而定)。还将对 AddQuestion 的调用移到循环的末尾,因为当它是 foreach 循环中的第一条语句时,我遇到了引用完整性错误。
  • @onefootswill 更新答案。
  • 嗨。这是我最初的尝试之一。唉,它没有用。
【解决方案3】:

如果你尝试修复数据库中包含真假的代码(可能不是最好的解决方案),第一个问题,在这段代码中

        var storedOption = _quizRepository.Context.Option.Single(o => o.Id == option.Id);
        foreach (var questionWithOption in question.QuestionWithOptions)
        {
            if (questionWithOption.Option.Id == storedOption.Id)
            {
                questionWithOption.Option = storedOption;
                _quizRepository.Context.Entry(questionWithOption.Option).State = EntityState.Unchanged;
            }
        }

您从 DB 中读取了 storedOption 并且您从未在这段代码中更改它。 然后你设置_quizRepository.Context.Entry(questionWithOption.Option).State = EntityState.Unchanged; 这不是必需的,因为 questionWithOption.Option 是storedOption 并且没有人改变storedOption。

映射中某处的第二个问题(可能在question.QuestionWithOptions)或_quizRepository.AddQuestion(question);

编辑
阅读其他 cmets,如果您从 DB 中检索它,请务必始终使用相同的上下文。但在这种情况下,错误不应该是违反 PK,而应该是实体之间的上下文不同。

【讨论】:

    【解决方案4】:

    这是我在您提供源代码之前编写的用于搜索解决方案的代码,并且它有效。只有我的随机选项被多次添加。

    主要

    static void Main(string[] args)
    {
        var optionTrue = new Option()
        {
            Id = 1,
            Text = "true"
        };
        var optionFalse = new Option()
        {
            Id = 2,
            Text = "false"
        };
        var optionRandom = new Option()
        {
            Text = "edaada"
        };
        var question1 = new Question();
        question1.QuestionWithOptions.Add(new QuestionWithOptions()
        {
            Id = 1,
            Option = optionTrue
        });
        question1.QuestionWithOptions.Add(new QuestionWithOptions()
        {
            Id= 2,
            Option = new Option()
            {
                Id = 10,
                Text = "blala"
            }
        });
    
        var newQuestions = new List<Question> {question1};
    
        using (var quizRepository = new QuizRepository())
        {
            foreach (var question in newQuestions)
            {
                var options = question.QuestionWithOptions.Select(q =>q.Option).ToList();
    
                foreach (var option in options)
                {
                    if (option.Text.Equals("true", StringComparison.OrdinalIgnoreCase) ||
                        option.Text.Equals("false", StringComparison.OrdinalIgnoreCase))
                    {
                        var storedOption = quizRepository.Options.Single(o => o.Id == option.Id);
                        foreach (var questionWithOption in question.QuestionWithOptions)
                        {
                            if (questionWithOption.Option.Id == storedOption.Id)
                            {
                                questionWithOption.Option = storedOption;
                                quizRepository.Entry(questionWithOption.Option).State = EntityState.Unchanged;
                            }
                        }
                    }
                }
    
                quizRepository.Questions.Add(question);
    
                quizRepository.SaveChanges();
            }
        }
    }
    

    数据库上下文

    public class QuizRepository : DbContext
        {
            public virtual DbSet<Question> Questions { get; set; }
            public virtual DbSet<Option> Options { get; set; }
            public virtual DbSet<QuestionWithOptions> QuestionWithOptions { get; set; }
    
            public QuizRepository() : base("name=QuizDBConnectionString")
            {
            }
        }
        public class Question
        {
            public int Id { get; set; }
            public Question()
            {
                QuestionWithOptions = new HashSet<QuestionWithOptions>();
            }
            public ICollection<QuestionWithOptions> QuestionWithOptions { get; set; }
        }
    
        public class QuestionWithOptions
        {
            public int Id { get; set; }
            public Option Option { get; set; }
    
            public int QuestionId { get; set; }
            public Question Question { get; set; }
        }
    
        public class Option
        {
            public Option()
            {
                QuestionWithOptions = new HashSet<QuestionWithOptions>();
            }
            [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
            public int Id { get; set; }
            public string Text { get; set; }
    
            public ICollection<QuestionWithOptions> QuestionWithOptions { get; set;}
         }
    }
    

    【讨论】:

      【解决方案5】:

      终于找出了缺少的一件事。工作代码如下:

      foreach (var question in newQuestions)
      {
          _quizRepository.AddQuestion(question);
      
          var options = question.QuestionWithOptions.Select(q => q.Option).ToList();
      
          // This loop is an edge case. true and false will be very common answers. Rather than storing them over and over again,
          // this loop will re-use the already stored answers for true and false, which complies with the quiz schema.
      
          var optionsToDelete = new List<Option>();
      
          foreach (var option in options)
          {
              if (option.Text.Equals("true", StringComparison.OrdinalIgnoreCase) ||
                  option.Text.Equals("false", StringComparison.OrdinalIgnoreCase))
              {
                  var storedOption = _quizRepository.Context.Option.Single(o => o.Id == option.Id);
      
                  foreach (var questionWithOption in question.QuestionWithOptions)
                  {
                      if (questionWithOption.Option.Id == storedOption.Id)
                      {
                          optionsToDelete.Add(option);
                          questionWithOption.idOption = storedOption.Id;
                      }
                  }
              }
          }
      
          _quizRepository.Context.Option.RemoveRange(optionsToDelete);
          _quizRepository.SaveChanges();
      }
      

      虽然我按预期分配了 StoredOption,但最初添加到对象图中的 Option 是悬空的。所以我必须在调用 SaveChanges 之前从上下文中删除它。我创建了一个列表来在 foreach 循环期间收集这些选项,然后在调用 SaveChanges 上方的行上调用 RemoveRange。

      肮脏,凌乱,但它有效。现有选项现在用于“真”和“假”,而不是一遍又一遍地创建它们。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-17
        • 1970-01-01
        • 2022-10-25
        • 1970-01-01
        • 1970-01-01
        • 2021-03-19
        相关资源
        最近更新 更多