将未更改的项添加到已添加项

时间:2015-06-26 10:46:38

标签: entity-framework

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

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

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

显然,每次在测验中添加带有true和false选项的新问题时,我都不想继续向该表添加true和false。

在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();
}

我试图将状态设置为Unchanged和Modified,但每次调用SaveChanges时,都会为true和false添加新行。如上所述,这正是我想要避免的。

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

编辑 - 添加了域类

问题:

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);
    }

修改 - 最终评论

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

5 个答案:

答案 0 :(得分:1)

我认为这是错误:

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

应该通过以下方式解决:

questionWithOption.Option = storedOption;

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

答案 1 :(得分:1)

另一种选择可能是没有搞乱状态(但需要更多的资源密集):迭代选项,替换默认答案,记住选项中的索引。然后,一旦离开迭代,在记住它们时按索引删除所有项目。

我是这样做的,因为我不想过多地干涉EF的事务......

编辑:

甚至更好:添加未映射到Option类的字段,如果要删除该选项,请设置此字段。替换后执行list.RemoveAll(f => 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();        
}

答案 2 :(得分:1)

如果你试图在数据库中修复代码mantaining true和false(可能不是最好的解决方案),那么第一个问题,在这段代码中

        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);)中的第二个问题

EDIT
阅读其他注释,如果从DB中检索它,也一定要使用相同的上下文。但在这种情况下,错误不应该是PK违规,而应该是实体之间的上下文不同。

答案 3 :(得分:1)

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

主要

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();
        }
    }
}

的DbContext

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;}
     }
}

答案 4 :(得分:0)

最后弄明白了一件事。工作代码如下:

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。

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