簡體   English   中英

如何在asp.net mvc的EF6中僅保存/更新父實體而不保存其子實體?

[英]How to save/update only parent entities without saving its childs entities in EF6 in asp.net mvc?

我正在使用 Asp.Net MVC 開發一個調查應用程序。

我有一個名為 Index.cshtml 的頁面,其中有一個問題表和一個“添加新”按鈕。單擊按鈕后,將使用 jQuery 打開一個彈出窗口。 我正在從控制器調用一個視圖來填充名為 AddOrEdit.cshtml(子頁面)的 jQuery 對話框。 我正在添加新的問題和選項。 問題是一個文本字段,其選項添加到可編輯表格中。 單擊提交按鈕后,將觸發提交表單事件(保存或更新)。 我的問題和它的選項類有一對多的關系。 EF6 嘗試將父實體與其子實體一起保存。 但我想在不同時間插入父母后拯救孩子。 我該如何處理這個問題。

我正在使用 DB First 方法。 最佳做法是什么?

問題.cs

namespace MerinosSurvey.Models
{
using System;
using System.Collections.Generic;

public partial class Questions
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Questions()
    {
        this.Responses = new HashSet<Responses>();
        this.Options = new HashSet<Options>();
    }

    public int QuestionId { get; set; }
    public string QuestionName { get; set; }
    public int QuestionTypeId { get; set; }
    public System.DateTime CreatedDate { get; set; }
    public int CreatedUserId { get; set; }
    public bool IsActive { get; set; }
    public bool Status { get; set; }
    public System.DateTime UpdatedDate { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Responses> Responses { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Options> Options { get; set; }
}
}

選項.cs

namespace MerinosSurvey.Models
{
using System;
using System.Collections.Generic;

public partial class Options
{
    public int OptionId { get; set; }
    public string OptionName { get; set; }
    public int QuestionId { get; set; }
    public System.DateTime CreatedDate { get; set; }
    public System.DateTime UpdatedDate { get; set; }
    public bool IsActive { get; set; }
    public bool Status { get; set; }

    public virtual Questions Questions { get; set; }
}
}

QuestionController.cs - 添加或編輯操作

    [HttpPost]
    public ActionResult AddOrEdit(Questions question)
    {
        if (question != null)
        {
            using (MerinosSurveyEntities db = new MerinosSurveyEntities())
            {
                Questions questionComing = db.Questions.FirstOrDefault(x => x.QuestionId == question.QuestionId);
                if (questionComing != null)
                {
                    //Update
                    questionComing.QuestionName = question.QuestionName;
                    questionComing.Status = true;
                    questionComing.IsActive = true;
                    questionComing.UpdatedDate = DateTime.Now;
                    db.Questions.Attach(questionComing);
                    db.Entry(questionComing).State = EntityState.Modified;
                    question.QuestionId = questionComing.QuestionId;
                    db.SaveChanges();
                }
                else
                {
                    //New Question
                    question.Status = true;
                    question.IsActive = true;
                    question.UpdatedDate = DateTime.Now;
                    question.CreatedDate = DateTime.Now;
                    db.Questions.Attach(question);
                    db.Entry(question).State = EntityState.Added;
                    db.SaveChanges();
                    question.QuestionId = question.QuestionId;
                }

                List<Options> options = question.Options.ToList();
                List<Options> existingOptions = new List<Options>(db.Options.Where(x =>
                    x.Status && x.IsActive && x.QuestionId == question.QuestionId));

                foreach (Options existingOption in existingOptions)
                {
                    Options optionUpdated = options.FirstOrDefault(x => x.OptionId == existingOption.OptionId);
                    if (optionUpdated != null)
                    {
                        //Update
                        existingOption.UpdatedDate = DateTime.Now;
                        existingOption.OptionName = optionUpdated.OptionName;
                        existingOption.IsActive = true;
                        existingOption.Status = true;
                        db.Options.Attach(existingOption);
                        db.Entry(existingOption).State = EntityState.Modified;
                        db.SaveChanges();
                        options.RemoveAll(x => x.OptionId == existingOption.OptionId);
                    }
                    else
                    {
                        //Delete
                        existingOption.Status = false;
                        existingOption.UpdatedDate = DateTime.Now;
                        db.Options.Attach(existingOption);
                        db.Entry(existingOption).State = EntityState.Modified;
                        db.SaveChanges();
                    }
                }

                foreach (Options optionNew in options)
                {
                    optionNew.IsActive = true;
                    optionNew.Status = true;
                    optionNew.CreatedDate = DateTime.Now;
                    optionNew.UpdatedDate = DateTime.Now;
                    optionNew.QuestionId = question.QuestionId;
                    db.Options.Add(optionNew);
                    db.SaveChanges();
                }

                return Json(new { success = true, message = "Soru başarılı bir şekilde güncellendi." 
  },
                    JsonRequestBehavior.AllowGet);
            }
        }

        return Json(new { success = true, message = "Bir problem oluştu." },
            JsonRequestBehavior.AllowGet);
    }

你的方法非常刻意,但容易出現問題。 使用 EF,DbContext 的行為很像一個工作單元,並且 SaveChanges 應該只被調用一次。 使用類似相關層次結構的問題,您可以更新並保存問題,但是如果保存其中一個選項出現問題,會發生什么? 您將部分提交更改並使數據處於不完整、不准確的狀態。

它還有很多樣板代碼,其中一些(例如將跟蹤實體的狀態顯式設置為 Modified)是完全沒有必要的。 該操作可以修改和簡化為:

[HttpPost]
public ActionResult AddOrEdit(Questions question)
{
    if (question == null) // Assert and fail. Avoids nesting.
        return Json(new { success = true, message = "Bir problem oluştu." },
            JsonRequestBehavior.AllowGet);

    using (MerinosSurveyEntities db = new MerinosSurveyEntities())
    {
        Questions questionComing = db.Questions.Include(x => x.Options).SingleOrDefault(x => x.QuestionId == question.QuestionId); // Prefetch our options...
        if (questionComing != null)
        {   //Update
            questionComing.QuestionName = question.QuestionName;
            questionComing.Status = true;
            questionComing.IsActive = true;
            questionComing.UpdatedDate = DateTime.Now;
            // db.Questions.Attach(questionComing); -- not needed, already tracked
            // db.Entry(questionComing).State = EntityState.Modified; - Not needed
            // question.QuestionId = questionComing.QuestionId; -- Redundant. The ID matches, we loaded based on it.
            // db.SaveChanges(); -- No save yet.

            // Handle options here... There are probably optimizations that can be added.
            var activeOptionIds = question.Options.Where(x => x.Status && s.IsActive).Select(x => x.OptionId).ToList();
            foreach(var option in question.Options.Where(x => activeOptionIds.Contains(x.OptionId))
            {
                var existingOption = questionComing.Options.SingleOrDefault(x => x.OptionId == option.OptionId);
                if(existingOption != null)
                { // Update
                    existingOption.UpdatedDate = DateTime.Now;
                    existingOption.OptionName = optionUpdated.OptionName;
                    existingOption.IsActive = true;
                    existingOption.Status = true;
                }
                else
                { // New
                    questionComing.Options.Add(option); // Provided we trust the data coming in. Otherwise new up an option and copy over values.
                }
            }

            var removedOptions = questionComing.Options.Where(x => !activeOptionIds.Contains(x.OptionId).ToList();
            foreach(var option in removedOptions)
            {
                option.Status = option.IsActive = false;
                option.UpdatedDate = DateTime.Now;
            } 
        }
        else
        {   //New Question
            // Dangerous to trust the Question coming in. Better to validate and copy values to a new Question to add...
            question.Status = true;
            question.IsActive = true;
            question.UpdatedDate = question.CreatedDate = DateTime.Now;

            // db.Questions.Attach(question); -- Add it...
            // db.Entry(question).State = EntityState.Added; 
            // question.QuestionId = question.QuestionId; -- Does nothing...
            db.Questions.Add(question); // This will append all Options as well.
        }

        // Now, after all changes are in, Save.
        db.SaveChanges();
        return Json(new { success = true, message = "Soru başarılı bir şekilde güncellendi." },JsonRequestBehavior.AllowGet);
    } // end using.

}

我將進一步分解為處理添加與更新的私有方法。 雖然這並沒有回答如何在沒有子項的情況下更新父項,但它應該說明為什么您應該利用 EF 的功能來確保子項正確地與其父項一起更新。 SaveChanges應該只在 DbContext 的生命周期范圍內調用一次,以便確保在發生故障時提交或回滾所有相關更改。 EF 管理它被告知要跟蹤的實體之間的關系,因此您可以添加具有子項的實體。 您需要小心的是引用,例如,如果您有一個與新問題相關聯的現有 QuestionType 實體。 在這些場景中,您總是希望在 DbContext 范圍內加載實體並使用該引用,而不是傳入的分離引用,因為 EF 會將其視為導致重復數據或重復鍵約束被命中的新實體。 通常建議不要在客戶端和服務器之間傳遞實體以避免此類問題。 如果未正確驗證,附加或添加來自客戶端的實體可能會使系統暴露在數據篡改中,並且在引用現有數據時可能會導致問題。

例如,如果您傳入一個新問題,該問題的 QuestionType 引用為“MultipleChoice”(問題類型的查找表),其中 QuestionType ID #1。 如果您執行以下操作:

db.Questions.Add(question);

“問題”未被跟蹤,所有引用的實體都未被跟蹤。 如果您添加它或將其附加為新實體,則這些引用的實體將被視為新實體。 這將有效地想要插入一個新的 QuestionType ID #1,導致關鍵違規(行已存在),或者將插入一個新的 QuestionType ID #12,例如,如果 QuestionType 配置為遞增 ID。 為了解決這個問題:

var existingQuestionType = db.QuestionTypes.Single(x => x.QuestionTypeId == question.QuestionType.QuestionTypeId);
question.QuestionType = existingQuestionType; // Points our question type reference at the existing, tracked reference.
db.Questions.Add(question);

在此示例中, question.QuestionType 和 existingQuestionType 的 ID 均為 1。 不同之處在於 existingQuestionType 由 Context 跟蹤/已知,其中 question.QuestionType 是未跟蹤的引用。 如果我們在上下文不知道引用的情況下添加問題,它會將其視為問題的子記錄並希望插入它。 這可能是使人們因 EF 引用而絆倒並導致問題和努力與相關實體更加慎重的最大事情之一,但會剝奪 EF 可以提供的許多優勢。 我們將新的問題引用指向被跟蹤的實體,因此當 EF 插入問題時,它已經知道 QuestionType 引用是現有行,並且一切都按預期進行。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM