我有一个名为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的粉丝了。
答案 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();
}
}
}
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。
肮脏,凌乱,但它有效。现有选项现在用于“真实”和“假”,而不是一遍又一遍地创建它们。