Entity Framework 5 การอัพเดตเรคคอร์ด


870

ฉันได้ทำการสำรวจวิธีการต่าง ๆ ในการแก้ไข / ปรับปรุงระเบียนภายใน Entity Framework 5 ในสภาพแวดล้อม ASP.NET MVC3 แต่จนถึงขณะนี้ยังไม่มีใครทำเครื่องหมายในช่องทั้งหมดที่ฉันต้องการ ฉันจะอธิบายว่าทำไม

ฉันได้พบสามวิธีที่ฉันจะพูดถึงข้อดีและข้อเสีย:

วิธีที่ 1 - โหลดระเบียนต้นฉบับอัปเดตแต่ละคุณสมบัติ

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    original.BusinessEntityId = updatedUser.BusinessEntityId;
    original.Email = updatedUser.Email;
    original.EmployeeId = updatedUser.EmployeeId;
    original.Forename = updatedUser.Forename;
    original.Surname = updatedUser.Surname;
    original.Telephone = updatedUser.Telephone;
    original.Title = updatedUser.Title;
    original.Fax = updatedUser.Fax;
    original.ASPNetUserId = updatedUser.ASPNetUserId;
    db.SaveChanges();
}    

ข้อดี

  • สามารถระบุคุณสมบัติที่เปลี่ยนแปลงได้
  • มุมมองไม่จำเป็นต้องมีคุณสมบัติทั้งหมด

จุดด้อย

  • 2 x เคียวรีบนฐานข้อมูลเพื่อโหลดต้นฉบับจากนั้นอัพเดต

วิธีที่ 2 - โหลดบันทึกต้นฉบับตั้งค่าที่เปลี่ยนแปลง

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    db.Entry(original).CurrentValues.SetValues(updatedUser);
    db.SaveChanges();
}

ข้อดี

  • คุณสมบัติที่ถูกแก้ไขเท่านั้นที่ถูกส่งไปยังฐานข้อมูล

จุดด้อย

  • มุมมองต้องมีคุณสมบัติทั้งหมด
  • 2 x เคียวรีบนฐานข้อมูลเพื่อโหลดต้นฉบับจากนั้นอัพเดต

วิธีที่ 3 - แนบระเบียนที่อัปเดตและตั้งค่าสถานะเป็น EntityState.Modified

db.Users.Attach(updatedUser);
db.Entry(updatedUser).State = EntityState.Modified;
db.SaveChanges();

ข้อดี

  • 1 x แบบสอบถามบนฐานข้อมูลที่จะอัปเดต

จุดด้อย

  • ไม่สามารถระบุว่าคุณสมบัติใดจะเปลี่ยนแปลง
  • มุมมองต้องมีคุณสมบัติทั้งหมด

คำถาม

คำถามของฉันกับพวกคุณ มีวิธีที่สะอาดที่ฉันสามารถบรรลุเป้าหมายชุดนี้หรือไม่?

  • สามารถระบุคุณสมบัติที่เปลี่ยนแปลงได้
  • มุมมองไม่จำเป็นต้องมีคุณสมบัติทั้งหมด (เช่นรหัสผ่าน!)
  • 1 x แบบสอบถามบนฐานข้อมูลที่จะอัปเดต

ฉันเข้าใจว่านี่เป็นสิ่งเล็ก ๆ น้อย ๆ ที่ชี้ให้เห็น แต่ฉันอาจพลาดวิธีง่ายๆในการนี้ หากไม่ใช่วิธีที่หนึ่งจะเหนือกว่า ;-)


13
ใช้ ViewModels และเครื่องมือสร้างแผนที่ที่ดีหรือไม่? คุณได้รับ "คุณสมบัติเพื่ออัปเดต" เท่านั้นเพื่อเติมมุมมองของคุณ (และจากนั้นอัปเดต) ยังมีอีก 2 คำถามสำหรับการอัปเดต (รับ + ​​อัปเดตต้นฉบับ) แต่ฉันจะไม่เรียกสิ่งนี้ว่า "Con" หากนั่นเป็นปัญหาด้านประสิทธิภาพเพียงอย่างเดียวของคุณคุณเป็นคนที่มีความสุข;)
Raphaël Althaus

ขอบคุณ @ RaphaëlAlthausจุดที่ถูกต้องมาก ฉันสามารถทำสิ่งนี้ได้ แต่ฉันต้องสร้างการทำงาน CRUD สำหรับหลาย ๆ ตารางดังนั้นฉันกำลังมองหาวิธีที่สามารถทำงานกับโมเดลโดยตรงเพื่อช่วยฉันในการสร้าง n-1 ViewModel สำหรับแต่ละโมเดล
Stokedout

3
ในโครงการปัจจุบันของฉัน (หลายหน่วยงานด้วย) เราเริ่มทำงานกับนางแบบคิดว่าเราจะเสียเวลาทำงานกับ ViewModels ตอนนี้เรากำลังจะไปที่ ViewModels และด้วยโครงสร้างพื้นฐาน (ไม่สำคัญ) ที่เริ่มต้นมันไกลมากไกลชัดเจนและง่ายต่อการบำรุงรักษามากขึ้นในตอนนี้ และปลอดภัยยิ่งขึ้น (ไม่ต้องกลัวเกี่ยวกับ "ช่องซ่อนเร้น" หรือสิ่งอื่น ๆ ที่เป็นอันตราย)
Raphaël Althaus

1
และไม่มีอีกมาก (แย่) ViewBags เพื่อเติม DropDownLists ของคุณ (เรามี DropDownList อย่างน้อยหนึ่งรายการในเกือบทุกมุมมอง CRU (D) ของเรา ... )
Raphaël Althaus

ฉันคิดว่าคุณพูดถูกแล้วความเลวของฉันในการพยายามมองข้าม ViewModels ใช่ ViewBag ดูเหมือนจะสกปรกตลอดเวลา ฉันมักจะไปอีกขั้นหนึ่งตามบล็อกของ Dino Espositoและสร้าง InputModels ด้วยเช่นกันเข็มขัดและวงเล็บปีกกา แต่ก็ใช้งานได้ดี เพียงหมายถึง 2 รุ่นพิเศษต่อรุ่น - doh ;-)
Stokedout

คำตอบ:


681

คุณกำลังมองหา:

db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();

59
สวัสดี @ Ladislav Mrnka หากฉันต้องการอัปเดตคุณสมบัติทั้งหมดในครั้งเดียวฉันสามารถใช้รหัสด้านล่างได้หรือไม่ db.Departments.Attach (เขตปกครอง); db.Entry (แผนก) .State = EntityState.Modified; db.SaveChanges ();
Foyzul Karim

23
@Foysal: ใช่คุณทำได้
Ladislav Mrnka

5
ปัญหาอย่างหนึ่งของวิธีนี้คือคุณไม่สามารถจำลอง db.Entry () ซึ่งเป็น PITA ที่ร้ายแรง EF มีเรื่องล้อเลียนที่ดีพอสมควรที่อื่น - มันค่อนข้างน่ารำคาญที่ (เท่าที่ฉันจะบอกได้) พวกเขาไม่มีที่นี่
Ken Smith

23
@Foysal การทำ context.Entry (เอนทิตี) .State = EntityState.Modified เพียงอย่างเดียวไม่เพียงพอที่จะทำการแนบไฟล์ มันจะถูกแนบโดยอัตโนมัติเป็นแก้ไข ...
HelloWorld

4
@ Sandman4 นั่นหมายถึงทุก ๆ ทรัพย์สินจะต้องอยู่ที่นั่นและตั้งค่าเป็นค่าปัจจุบัน ในการออกแบบแอปพลิเคชั่นบางตัวอาจไม่สามารถทำได้
Dan Esparza

176

ฉันชอบคำตอบที่ได้รับการยอมรับจริงๆ ฉันเชื่อว่ายังมีวิธีอื่นในการเข้าถึงสิ่งนี้เช่นกัน สมมติว่าคุณมีรายการคุณสมบัติสั้น ๆ ที่คุณไม่ต้องการรวมไว้ในมุมมองดังนั้นเมื่ออัปเดตเอนทิตีเอนทิตีเหล่านั้นจะถูกตัดออก สมมติว่าทั้งสองฟิลด์นี้เป็นรหัสผ่านและ SSN

db.Users.Attach(updatedUser);

var entry = db.Entry(updatedUser);
entry.State = EntityState.Modified;

entry.Property(e => e.Password).IsModified = false;
entry.Property(e => e.SSN).IsModified = false;   

db.SaveChanges();   

ตัวอย่างนี้ช่วยให้คุณสามารถละทิ้งตรรกะทางธุรกิจของคุณเพียงอย่างเดียวหลังจากเพิ่มเขตข้อมูลใหม่ลงในตารางผู้ใช้ของคุณและในมุมมองของคุณ


ฉันจะยังคงได้รับข้อผิดพลาดหากฉันไม่ได้ระบุค่าสำหรับคุณสมบัติ SSN แม้ว่าฉันจะตั้งค่า IsModified ให้เป็นเท็จ แต่ก็ยังตรวจสอบความถูกต้องของคุณสมบัติกับกฎของแบบจำลอง ดังนั้นถ้าคุณสมบัติถูกทำเครื่องหมายเป็น NOT NULL มันจะล้มเหลวถ้าฉันไม่ได้ตั้งค่าใด ๆ ที่แตกต่างจาก null
RolandoCC

คุณจะไม่ได้รับข้อผิดพลาดเนื่องจากฟิลด์เหล่านั้นจะไม่อยู่ในแบบฟอร์มของคุณ คุณปล่อยให้ฟิลด์ที่คุณจะไม่อัพเดทแน่นอนคว้ารายการจากฐานข้อมูลโดยใช้แบบฟอร์มส่งกลับโดยแนบและบอกรายการว่าฟิลด์เหล่านั้นไม่ได้ถูกแก้ไข การตรวจสอบรูปแบบถูกควบคุมใน ModelState ไม่ใช่ในบริบท ตัวอย่างนี้เป็นการอ้างอิงผู้ใช้ที่มีอยู่ดังนั้น "updatedUser" หาก SSN ของคุณเป็นฟิลด์บังคับมันจะเกิดขึ้นเมื่อสร้างครั้งแรก
smd

4
ถ้าฉันเข้าใจถูกต้อง "updatedUser" เป็นอินสแตนซ์ของวัตถุที่บรรจุด้วย FirstOrDefault () หรือที่คล้ายกันดังนั้นฉันจึงอัปเดตเฉพาะคุณสมบัติที่ฉันเปลี่ยนและตั้งค่าอื่นเป็น ISModified = false ใช้งานได้ดี แต่สิ่งที่ฉันพยายามทำคือการอัปเดตวัตถุโดยไม่ต้องใส่ข้อมูลก่อนโดยไม่ทำการสร้าง FirstOrDefault () ใด ๆ ก่อนการปรับปรุง นี่คือเมื่อฉันได้รับข้อผิดพลาดถ้าฉันไม่ได้ระบุค่าสำหรับเขตข้อมูลที่ร้องขอทั้งหมดแม้ว่าคุณจะตั้งค่า ISModified = false ในคุณสมบัติเหล่านั้น รายการคุณสมบัติ (e => e.columnA) .IsModified = false; หากไม่มีบรรทัดนี้ ColumnA จะล้มเหลว
RolandoCC

สิ่งที่คุณกำลังอธิบายคือการสร้างเอนทิตีใหม่ สิ่งนี้ใช้กับการอัพเดตเท่านั้น
smd

1
RolandoCC ใส่ db.Configuration.ValidateOnSaveEnabled = false ก่อน db.SaveChanges ();
กี้

28
foreach(PropertyInfo propertyInfo in original.GetType().GetProperties()) {
    if (propertyInfo.GetValue(updatedUser, null) == null)
        propertyInfo.SetValue(updatedUser, propertyInfo.GetValue(original, null), null);
}
db.Entry(original).CurrentValues.SetValues(updatedUser);
db.SaveChanges();

ดูเหมือนว่าจะเป็นทางออกที่ดีจริงๆ - ไม่มีความยุ่งยากหรือความยุ่งยาก คุณไม่จำเป็นต้องระบุคุณสมบัติด้วยตนเองและคำนึงถึงสัญลักษณ์แสดงหัวข้อย่อยทั้งหมด - มีเหตุผลใด ๆ ที่ไม่มีคะแนนเสียงเพิ่มเติมหรือไม่
nocarrier

ไม่ว่า มันมีหนึ่งใน "ข้อเสีย" ที่ใหญ่ที่สุดมากกว่าหนึ่งตีไปยังฐานข้อมูล คุณยังคงต้องโหลดต้นฉบับด้วยคำตอบนี้
smd

1
@smd ทำไมคุณถึงบอกว่ามันกระทบฐานข้อมูลมากกว่าหนึ่งครั้ง ฉันไม่เห็นว่าเกิดขึ้นเว้นแต่ใช้ SetValues ​​() มีผลกระทบนั้น แต่ดูเหมือนจะไม่เป็นจริง
รัฐสภา

@ รัฐสภาฉันคิดว่าฉันคงหลับไปแล้วเมื่อฉันเขียนสิ่งนั้น ขอโทษ. ปัญหาที่แท้จริงคือการแทนที่ค่า Null ที่ตั้งใจไว้ หากผู้ใช้ที่อัปเดตแล้วไม่มีการอ้างอิงถึงบางสิ่งอีกต่อไปมันจะไม่ถูกต้องที่จะแทนที่ด้วยค่าดั้งเดิมหากคุณต้องการล้างข้อมูล
smd

22

ฉันได้เพิ่มวิธีการอัปเดตเพิ่มเติมลงในคลาสฐานที่เก็บของฉันซึ่งคล้ายกับวิธีการอัปเดตที่สร้างโดย Scaffolding แทนที่จะตั้งค่าวัตถุทั้งหมดเป็น "แก้ไข" มันจะตั้งค่าชุดของคุณสมบัติแต่ละรายการ (T เป็นพารามิเตอร์ทั่วไปของคลาส)

public void Update(T obj, params Expression<Func<T, object>>[] propertiesToUpdate)
{
    Context.Set<T>().Attach(obj);

    foreach (var p in propertiesToUpdate)
    {
        Context.Entry(obj).Property(p).IsModified = true;
    }
}

และจากนั้นจะโทรเช่น:

public void UpdatePasswordAndEmail(long userId, string password, string email)
{
    var user = new User {UserId = userId, Password = password, Email = email};

    Update(user, u => u.Password, u => u.Email);

    Save();
}

ฉันชอบหนึ่งการเดินทางไปยังฐานข้อมูล มันอาจจะดีกว่าถ้าทำด้วยโมเดลมุมมองเพื่อหลีกเลี่ยงการทำซ้ำชุดคุณสมบัติ ฉันยังไม่ได้ทำเพราะฉันยังไม่รู้วิธีหลีกเลี่ยงการนำข้อความการตรวจสอบความถูกต้องไปใช้ในตัวตรวจสอบตัวแบบโมเดลมุมมองของฉันในโครงการโดเมนของฉัน


อ้าว ... โครงการแยกต่างหากสำหรับมุมมองแบบจำลองและโครงการแยกต่างหากสำหรับที่เก็บข้อมูลที่ทำงานกับมุมมองแบบจำลอง
Ian Warburton

11
public interface IRepository
{
    void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class;
}

public class Repository : DbContext, IRepository
{
    public void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class
    {
        Set<T>().Attach(obj);
        propertiesToUpdate.ToList().ForEach(p => Entry(obj).Property(p).IsModified = true);
        SaveChanges();
    }
}

ทำไมไม่เพียงแค่ DbContext.Attach (obj); DbContext.Entry (obj) .State = EntityState.Modified;
nelsontruran

นี่เป็นsetส่วนหนึ่งของคำสั่งการอัพเดท
Tanveer Badar

4

เพียงเพื่อเพิ่มไปยังรายการตัวเลือก คุณยังสามารถคว้าวัตถุจากฐานข้อมูลและใช้เครื่องมือการแมปอัตโนมัติเช่นAuto Mapper เพื่ออัปเดตส่วนต่าง ๆ ของระเบียนที่คุณต้องการเปลี่ยน ..


3

ขึ้นอยู่กับสถานการณ์การใช้งานของคุณ นี่คือวิธีที่ฉันทำตามปกติ:

สำหรับโค้ดด้านเซิร์ฟเวอร์ (เช่นกระบวนการแบทช์) ฉันมักจะโหลดเอนทิตีและทำงานกับพร็อกซีแบบไดนามิก โดยปกติแล้วในกระบวนการแบบเป็นชุดคุณจะต้องโหลดข้อมูลต่อไปในเวลาที่บริการทำงาน ฉันพยายามแบตช์ข้อมูลแทนการใช้วิธีการค้นหาเพื่อประหยัดเวลา ขึ้นอยู่กับกระบวนการที่ฉันใช้การควบคุมภาวะพร้อมกันในแง่ดีหรือมองโลกในแง่ร้าย (ฉันมักจะใช้แง่ดียกเว้นสำหรับสถานการณ์การดำเนินการแบบขนานที่ฉันต้องล็อคบางระเบียนด้วยคำสั่ง SQL ธรรมดานี่เป็นของหายาก) ขึ้นอยู่กับรหัสและสถานการณ์ผลกระทบที่สามารถลดลงจนเกือบเป็นศูนย์

สำหรับสถานการณ์ฝั่งไคลเอ็นต์คุณมีตัวเลือกน้อย

  1. ใช้มุมมองแบบจำลอง แบบจำลองควรมี UpdateStatus ของคุณสมบัติ (unmodified-แทรก - อัพเดต - ลบ) มันเป็นความรับผิดชอบของลูกค้าในการตั้งค่าที่ถูกต้องในคอลัมน์นี้ขึ้นอยู่กับการกระทำของผู้ใช้ (insert-update-delete) เซิร์ฟเวอร์สามารถสืบค้น db สำหรับค่าดั้งเดิมหรือไคลเอนต์ควรส่งค่าดั้งเดิมไปยังเซิร์ฟเวอร์พร้อมกับแถวที่เปลี่ยนแปลง เซิร์ฟเวอร์ควรแนบค่าดั้งเดิมและใช้คอลัมน์ UpdateStatus สำหรับแต่ละแถวเพื่อตัดสินใจว่าจะจัดการค่าใหม่อย่างไร ในสถานการณ์นี้ฉันมักจะใช้การเห็นพ้องในแง่ดีเสมอ สิ่งนี้จะทำการแทรก - อัปเดต - ลบคำสั่งและไม่เลือกใด ๆ แต่อาจต้องใช้รหัสที่ชาญฉลาดในการเดินกราฟและอัปเดตเอนทิตี (ขึ้นอยู่กับสถานการณ์ของคุณ - แอปพลิเคชัน) ผู้ทำแผนที่สามารถช่วยได้ แต่ไม่ได้จัดการกับตรรกะ CRUD

  2. ใช้ไลบรารีเช่น breeze.js ซึ่งซ่อนความซับซ้อนส่วนใหญ่ไว้ (ดังอธิบายใน 1) และลองปรับให้เข้ากับกรณีการใช้งานของคุณ

หวังว่ามันจะช่วย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.