【问题标题】:Why I am getting an error when i try to save form into quiz model with entity framework?为什么当我尝试使用实体框架将表单保存到测验模型中时出现错误?
【发布时间】:2021-03-17 10:34:16
【问题描述】:

我要新的实体框架和 dotnet 核心。很快我想解释一下我做了什么以及我遇到了什么样的错误?

我做了什么?

首先我在下面创建了几个模型。

public class Quiz
{
    public int QuizID { get; set; }
    public int UserID { get; set; }
    public string Text { get; set; }
    public User User { get; set; }

    public ICollection<Question> Questions { get; set; }
}

public class Question
{
    public int QuestionID { get; set; }
    public string Text { get; set; }
    public int QuizID { get; set; }

    public Quiz Quiz { get; set; }

    public IList<Option> Options { get; set; }
}

public class User
{
    public int ID { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }

    public ICollection<Quiz> Quizzes { get; set; }
}

public class Option
{
    public int OptionID { get; set; }
    public string Choice { get; set; }
    public int QuestionID { get; set; }

    public bool? IsCorrect { get; set; }

    public Question Question { get; set; }
}

其次,我创建了 IActionResult 方法来保存来自用户的信息。

public async Task<IActionResult> CreateQuiz()
        {
            Quiz quiz = new Quiz();
            quiz.UserID = 0;
            quiz.Text = Request.Form["content"].ToString();
            _context.Add(quiz);
            await _context.SaveChangesAsync();

            for (int i = 1; i <= 4; i++)
            {
                Question question = new Question();
                question.QuizID = quiz.QuizID;
                question.Text = Request.Form["title_" + i].ToString(); 
                _context.Add(question);
                _context.SaveChanges();

                for (int j = 1; j <= 4; j++)
                {
                    Option option = new Option();
                    option.QuestionID = question.QuestionID;
                    option.Choice = Request.Form["option_a" + i].ToString();
                    option.IsCorrect = j == int.Parse(Request.Form["correct_answer_" + i].ToString());
                    _context.Add(option);
                }
            }

            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }

最后我在保存表单时遇到了这个错误。

处理请求时发生未处理的异常。 SqliteException:SQLite 错误 19:“外键约束失败”。 Microsoft.Data.Sqlite.SqliteException.ThrowExceptionForRC(int rc, sqlite3 数据库)

DbUpdateException:更新条目时出错。看 有关详细信息的内部异常。 Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection 连接,CancellationToken cancelToken)

我需要你们的帮助。

*抱歉拼写规则,我也是英语新手。

【问题讨论】:

    标签: entity-framework sqlite .net-core foreign-keys


    【解决方案1】:

    您可以采取一些措施来避免此类问题。从您的描述中确定到底出了什么问题有点困难,但是您可以大大简化您的代码,这应该会解决它。

    首先,遵循键名的约定并使用注释来命名您的键,而不是依赖于约定。您有混合命名,例如针对 Question 的 PK 的“QuestionId”,而针对 User 的 PK 的“Id”。

    接下来,我将删除所有外键字段并为外键字段使用阴影属性。 FK 列的问题在于,当您使用导航属性时,它们是关系的第二个真实来源。 option.QuestionId 是问题的 ID,还是 option.Question.QuestionId?你如何保证这些总是同步的?

    最后对于实体定义,将导航属性声明为virtual。这既可以用于延迟加载(作为故障保护),也可以通过代理进行更改跟踪。

    因此将实体定义更新为:

    public class Quiz
    {
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int QuizID { get; set; }
        public string Text { get; set; }
        public virtual User User { get; set; }
    
        public virtual ICollection<Question> Questions { get; set; } = new List<Question>();
    }
    
    public class Question
    {
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int QuestionID { get; set; }
        public string Text { get; set; }
    
        [ForeignKey("QuizID")] // Creates a shadow property mapped to a QuizID column in table.
        public virtual Quiz Quiz { get; set; }
    
        public virtual ICollection<Option> Options { get; set; } = new List<Option>();
    }
    
    public class User
    {
        [Key]
        public int UserID { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
    
        public virtual ICollection<Quiz> Quizzes { get; set; } = new List<Quiz>();
    }
    
    public class Option
    {
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int OptionID { get; set; }
        public string Choice { get; set; }
        public bool? IsCorrect { get; set; }
    
        [ForeignKey("QuestionID")]
        public virtual Question Question { get; set; }
    }
    

    现在,在创建问题数据时,您可以利用 EF 通过填充一组完整的相关实体并将它们保存在一起来管理 FK。

    public async Task<IActionResult> CreateQuiz()
    {
        var systemUser = _context.Users.Single(x => x.UserId == 0);
        Quiz quiz = new Quiz();
        quiz.User = systemUser;
        quiz.Text = Request.Form["content"].ToString();
    
        for (int i = 1; i <= 4; i++)
        {
            Question question = new Question();
            question.Text = Request.Form["title_" + i].ToString(); 
            quiz.Questions.Add(question);
    
            for (int j = 1; j <= 4; j++)
            {
                Option option = new Option();
                option.Choice = Request.Form["option_a" + i].ToString();
                option.IsCorrect = j == int.Parse(Request.Form["correct_answer_" + i].ToString());
                question.Options.Add(option);
            }
        }
    
        _context.Quizes.Add(quiz);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    

    这可能会进一步简化,但我将其保持在接近原始的位置,因此应该更容易理解。与其多次调用SaveChanges 并尝试设置FK,不如创建对象并将它们相互关联起来。我们创建测验,然后通过并创建问题,通过将它们添加到 quiz.Questions 集合将它们与测验相关联。然后对问题选项进行同样的操作。完成后,我们告诉上下文将测验添加到它的测验DbSet,并在最后调用一次SaveChanges。 EF 将自动保存所有实体及其关系,填充适当的 FK。这里的好处是所有更改都在一个事务中一起提交,而不是单独保存可能会保存测验的地方,但是一个问题或一个选项存在问题,使数据库在故障点处于不完整状态.

    【讨论】:

    • 感谢广泛解释的答案。我应用了你的指示,他们解决了我的问题。我只想问你两个问题 1. _context.Quizzes.Add(quiz) 和 _context.Add(quiz); 有什么区别? 2.“首先,遵循您的密钥名称的约定并使用注释来命名您的密钥,而不是依赖约定”您说。我想知道你为什么建议它?
    猜你喜欢
    • 1970-01-01
    • 2020-06-29
    • 2021-12-13
    • 2014-11-18
    • 2016-07-17
    • 1970-01-01
    • 2019-12-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多