เมื่อเรียนรู้ LINQ ฉันเกือบจะพบกับความยากลำบาก ซึ่งเป็นการดำเนินการอัปเดตฐานข้อมูลที่คุณเห็นในชื่อ ตอนนี้ฉันจะพาคุณก้าวเข้าสู่หล่มนี้ โปรดเตรียมอิฐและน้ำลายของคุณตามฉันมา
เริ่มจากกรณีที่ง่ายที่สุดกันดีกว่า ลองใช้ฐานข้อมูล Northwind เป็นตัวอย่าง เมื่อคุณต้องการแก้ไข ProductName ของผลิตภัณฑ์ คุณสามารถเขียนโค้ดต่อไปนี้บนไคลเอนต์ได้โดยตรง:
// รายการ 0NorthwindDataContext db = ใหม่ NorthwindDataContext();
สินค้าผลิตภัณฑ์ = db.Products.Single(p => p.ProductID == 1);
product.ProductName = "ชัยเปลี่ยน";
db.ส่งการเปลี่ยนแปลง();
ทดสอบแล้วอัพเดตได้สำเร็จ อย่างไรก็ตาม ฉันเชื่อว่าโค้ดดังกล่าวจะไม่ปรากฏในโปรเจ็กต์ของคุณ เนื่องจากเป็นไปไม่ได้เลยที่จะนำมาใช้ซ้ำ เอาล่ะ เรามาปรับโครงสร้างใหม่และแยกมันออกเป็นเมธอดกันดีกว่า พารามิเตอร์ควรเป็นอย่างไร? คือชื่อผลิตภัณฑ์ใหม่และรหัสผลิตภัณฑ์ที่จะอัปเดต ดูเหมือนว่าจะเป็นเช่นนั้น
โมฆะสาธารณะ UpdateProduct (int id, สตริง productName)
-
NorthwindDataContext db = NorthwindDataContext ใหม่ ();
สินค้าผลิตภัณฑ์ = db.Products.Single(p => p.ProductID == id);
product.ProductName = ชื่อผลิตภัณฑ์;
db.ส่งการเปลี่ยนแปลง();
}ในโครงการจริง เราไม่สามารถแก้ไขชื่อผลิตภัณฑ์เพียงอย่างเดียวได้ สาขาอื่นๆ ของผลิตภัณฑ์อาจมีการปรับเปลี่ยนเช่นกัน จากนั้นลายเซ็นของเมธอด UpdateProduct จะกลายเป็นดังนี้:
โมฆะสาธารณะ UpdateProduct (int id,
ชื่อผลิตภัณฑ์สตริง,
รหัสซัพพลายเออร์ int,
รหัสหมวดหมู่ int,
ปริมาณสตริงต่อหน่วย,
ราคาหน่วยทศนิยม,
หน่วยสั้นในสต็อก,
หน่วยสั้นOnOrder,
short reorderLevel) แน่นอนว่านี่เป็นเพียงฐานข้อมูลง่ายๆ ในโครงการจริง ไม่ใช่เรื่องแปลกที่จะมีฟิลด์ยี่สิบ สามสิบ หรือหลายร้อยช่อง ใครสามารถทนต่อวิธีการดังกล่าวได้? ถ้าคุณเขียนแบบนี้ Product object ทำหน้าที่อะไร?
ถูกต้อง ใช้ Product เป็นพารามิเตอร์ของวิธีการ และโยนการดำเนินการกำหนดที่น่ารำคาญไปยังโค้ดไคลเอ็นต์ ในเวลาเดียวกัน เราได้แยกโค้ดสำหรับการรับอินสแตนซ์ Product เพื่อสร้างวิธีการ GetProduct และใส่วิธีการที่เกี่ยวข้องกับการดำเนินการฐานข้อมูลลงในคลาส ProductRepository ที่มีหน้าที่รับผิดชอบในการจัดการกับฐานข้อมูลโดยเฉพาะ โอ้ใช่แล้ว สอาร์พี!
// รายการที่ 1
// ProductRepository
สินค้าสาธารณะ GetProduct(int id)
-
NorthwindDataContext db = NorthwindDataContext ใหม่ ();
กลับ db.Products.SingleOrDefault(p => p.id == id);
-
โมฆะสาธารณะ UpdateProduct(ผลิตภัณฑ์ผลิตภัณฑ์)
-
NorthwindDataContext db = NorthwindDataContext ใหม่ ();
db.Products.Attach(สินค้า);
db.ส่งการเปลี่ยนแปลง();
-
//รหัสลูกค้า
พื้นที่เก็บข้อมูล ProductRepository = ใหม่ ProductRepository();
ผลิตภัณฑ์ผลิตภัณฑ์ = repository.GetProduct(1);
product.ProductName = "ชัยเปลี่ยน";
repository.UpdateProduct (ผลิตภัณฑ์);
ที่นี่ฉันใช้วิธีแนบเพื่อแนบอินสแตนซ์ของผลิตภัณฑ์กับ DataContext อื่น สำหรับฐานข้อมูล Northwind เริ่มต้น ผลลัพธ์นี้เป็นข้อยกเว้นต่อไปนี้:
// ข้อยกเว้น 1 NotSupportException:
พยายามแนบหรือเพิ่มเอนทิตี เอนทิตีไม่ใช่เอนทิตีใหม่และอาจถูกโหลดจาก DataContext อื่น ไม่สนับสนุนการดำเนินการนี้
มีความพยายามในการแนบหรือเพิ่มเอนทิตีที่ไม่ใช่รายการใหม่
บางทีอาจโหลดจาก DataContext อื่น สิ่งนี้ไม่ได้รับการสนับสนุน เมื่อพิจารณาจาก MSDN เรารู้ว่าเมื่อทำการซีเรียลไลซ์เอนทิตีไปยังไคลเอนต์ เอนทิตีเหล่านี้จะถูกแยกออกจาก DataContext ดั้งเดิม DataContext จะไม่ติดตามการเปลี่ยนแปลงในเอนทิตีเหล่านี้หรือการเชื่อมโยงกับออบเจ็กต์อื่นอีกต่อไป หากคุณต้องการอัปเดตหรือลบข้อมูลในเวลานี้ คุณต้องใช้วิธีแนบเพื่อแนบเอนทิตีกับ DataContext ใหม่ก่อนที่จะเรียก SendChanges มิฉะนั้นข้อยกเว้นข้างต้นจะถูกส่งออกไป
ในฐานข้อมูล Northwind คลาสผลิตภัณฑ์ประกอบด้วยคลาสที่เกี่ยวข้องสามคลาส (เช่น การเชื่อมโยงคีย์ต่างประเทศ): Order_Detail หมวดหมู่ และซัพพลายเออร์ ในตัวอย่างข้างต้น แม้ว่าเราจะแนบผลิตภัณฑ์ แต่ไม่มีคลาสแนบที่เชื่อมโยงอยู่ ดังนั้นจึงส่ง NotSupportException ออกไป
แล้วจะเชื่อมโยงคลาสที่เกี่ยวข้องกับผลิตภัณฑ์ได้อย่างไร สิ่งนี้อาจดูซับซ้อน แม้แต่กับฐานข้อมูลธรรมดาอย่าง Northwind ก็ตาม ดูเหมือนว่าก่อนอื่นเราจะต้องได้รับคลาสดั้งเดิมของ Order_Detail, Category และ Supplier ที่เกี่ยวข้องกับผลิตภัณฑ์ดั้งเดิม จากนั้นจึงแนบคลาสเหล่านั้นเข้ากับ DataContext ปัจจุบันตามลำดับ แต่ในความเป็นจริง แม้ว่าเราจะทำเช่นนี้ NotSupportException ก็จะถูกโยนทิ้งไป
ดังนั้นจะดำเนินการอัพเดตอย่างไร? เพื่อความง่าย เราจะลบคลาสเอนทิตีอื่นๆ ใน Northwind.dbml และเก็บเฉพาะผลิตภัณฑ์เท่านั้น ด้วยวิธีนี้ เราสามารถเริ่มการวิเคราะห์จากกรณีที่ง่ายที่สุดได้
หลังจากลบคลาสอื่นเนื่องจากปัญหา เราได้ดำเนินการโค้ดในรายการ 1 อีกครั้ง แต่ฐานข้อมูลไม่ได้เปลี่ยนชื่อของผลิตภัณฑ์ เมื่อพิจารณาวิธีการแนบเวอร์ชันที่โอเวอร์โหลดแล้ว เราสามารถค้นหาปัญหาได้อย่างง่ายดาย
วิธีการแนบ (เอนทิตี) เรียกการโอเวอร์โหลดการแนบ (เอนทิตี, เท็จ) ตามค่าเริ่มต้น ซึ่งจะแนบเอนทิตีที่เกี่ยวข้องในสถานะที่ไม่มีการแก้ไข หากวัตถุ Product ไม่ได้รับการแก้ไข เราควรเรียกเวอร์ชันที่โอเวอร์โหลดนี้เพื่อแนบวัตถุ Product เข้ากับ DataContext ในสถานะที่ยังไม่ได้แก้ไขสำหรับการดำเนินการในภายหลัง ในขณะนี้ สถานะของออบเจ็กต์ Product คือ "แก้ไข" และเราสามารถเรียกได้เฉพาะเมธอด Attach(entity, true) เท่านั้น
ดังนั้นเราจึงเปลี่ยนรหัสที่เกี่ยวข้องในรายการ 1 เป็น Attach(product, true) และดูว่าเกิดอะไรขึ้น
// ข้อยกเว้น 2 InvalidOperationException:
หากเอนทิตีประกาศสมาชิกเวอร์ชันหรือไม่มีนโยบายการตรวจสอบการอัปเดต จะสามารถแนบเป็นเอนทิตีที่แก้ไขเท่านั้นโดยไม่มีสถานะดั้งเดิม
สามารถแนบเอนทิตีได้เฉพาะเมื่อมีการแก้ไขโดยไม่มีสถานะดั้งเดิม
หากประกาศเป็นสมาชิกเวอร์ชันหรือไม่มีนโยบายการตรวจสอบการอัปเดต
LINQ กับ SQL ใช้คอลัมน์ RowVersion เพื่อใช้การตรวจสอบการทำงานพร้อมกันในแง่ดีเริ่มต้น มิฉะนั้นข้อผิดพลาดข้างต้นจะเกิดขึ้นเมื่อมีการแนบเอนทิตีกับ DataContext ในสถานะที่แก้ไข มีสองวิธีในการใช้คอลัมน์ RowVersion วิธีหนึ่งคือการกำหนดคอลัมน์ประเภทการประทับเวลาสำหรับตารางฐานข้อมูล และอีกวิธีคือการกำหนดแอตทริบิวต์ IsVersion=true บนแอตทริบิวต์เอนทิตีที่สอดคล้องกับคีย์หลักของตาราง โปรดทราบว่าคุณไม่สามารถมีคอลัมน์ TimeStamp และแอตทริบิวต์ IsVersion=true ในเวลาเดียวกันได้ มิฉะนั้น InvalidOprationException จะถูกส่งออกไป: สมาชิก "System.Data.Linq.Binary TimeStamp" และ "Int32 ProductID" จะถูกทำเครื่องหมายเป็นเวอร์ชันแถวทั้งคู่ ในบทความนี้ เราใช้คอลัมน์การประทับเวลาเป็นตัวอย่าง
หลังจากสร้างคอลัมน์ชื่อ TimeStamp แล้วพิมพ์การประทับเวลาสำหรับตารางผลิตภัณฑ์ ให้ลากกลับเข้าไปในตัวออกแบบ จากนั้นรันโค้ดในรายการ 1 ขอบคุณพระเจ้า ในที่สุดมันก็ได้ผล
ตอนนี้ เราลากตารางหมวดหมู่ไปในตัวออกแบบ ครั้งนี้ฉันได้เรียนรู้บทเรียนและเพิ่มคอลัมน์การประทับเวลาลงในตารางหมวดหมู่ก่อน หลังจากทดสอบแล้ว กลับกลายเป็นข้อผิดพลาดในข้อยกเว้น 1 อีกครั้ง! หลังจากลบคอลัมน์การประทับเวลาของหมวดหมู่แล้ว ปัญหายังคงอยู่ โอ้พระเจ้า เกิดอะไรขึ้นในวิธีการแนบที่แย่มาก?
โอ้ ยังไงก็ตาม มีวิธีการแนบเวอร์ชันที่โอเวอร์โหลดมากเกินไป มาลองดูกัน
โมฆะสาธารณะ UpdateProduct(ผลิตภัณฑ์ผลิตภัณฑ์)
-
NorthwindDataContext db = NorthwindDataContext ใหม่ ();
สินค้า oldProduct = db.Products.SingleOrDefault(p => p.ProductID == product.ProductID);
db.Products.Attach(ผลิตภัณฑ์, oldProduct);
db.ส่งการเปลี่ยนแปลง();
} หรือข้อผิดพลาดข้อยกเว้น 1!
ฉันจะล้ม! แนบ แนบ เกิดอะไรขึ้นกับคุณ?
ในการสำรวจซอร์สโค้ด LINQ เป็น SQL เราใช้ปลั๊กอิน FileDisassembler ของ Reflector เพื่อถอดรหัส System.Data.Linq.dll ให้เป็นโค้ด cs และสร้างไฟล์โปรเจ็กต์ ซึ่งช่วยเราค้นหาและระบุตำแหน่งใน Visual Studio
ข้อยกเว้น 1 จะถูกโยนทิ้งเมื่อใด
อันดับแรก เราจะค้นหาข้อมูลที่อธิบายไว้ในข้อยกเว้น 1 จาก System.Data.Linq.resx และรับคีย์ "CannotAttachAddNonNewEntities" จากนั้นค้นหาเมธอด System.Data.Linq.Error.CannotAttachAddNonNewEntities() ค้นหาข้อมูลอ้างอิงทั้งหมดสำหรับเมธอดนี้ และค้นหา ว่ามีอยู่ด้วยกัน 2 วิธี วิธีนี้ใช้ใน 3 ตำแหน่ง คือ วิธี StandardChangeTracker.Track และวิธี InitializeDeferredLoader
เราเปิดโค้ดของ Table.Attach(entity, bool) อีกครั้ง และตามที่คาดไว้ เราพบว่าโค้ดเรียกเมธอด StandardChangeTracker.Track (เช่นเดียวกับเมธอด Attach(entity, entity)):
trackedObject = this.context.Services.ChangeTracker.Track (เอนทิตี จริง) ในวิธีการติดตาม รหัสต่อไปนี้แสดงข้อยกเว้น 1:
ถ้า (trackedObject.HasDeferredLoaders)
-
โยน System.Data.Linq.Error.CannotAttachAddNonNewEntities();
}ดังนั้นเราจึงหันมาสนใจคุณสมบัติ StandardTrackedObject.HasDeferredLoaders:
บูลแทนที่ภายใน HasDeferredLoaders
-
รับ
-
foreach (การเชื่อมโยง MetaAssociation ใน this.Type.Associations)
-
ถ้า (this.HasDeferredLoader (association.ThisMember))
-
กลับเป็นจริง;
-
-
foreach (สมาชิก MetaDataMember จาก p ใน this.Type.PersistentDataMembers
โดยที่ p.IsDeferred && !p.IsAssociation
เลือก พี)
-
ถ้า (this.HasDeferredLoader (สมาชิก))
-
กลับเป็นจริง;
-
-
กลับเท็จ;
-
} จากนี้เราสามารถอนุมานคร่าวๆ ได้ว่าตราบใดที่มีรายการที่โหลดแบบ Lazy ในเอนทิตี การดำเนินการแนบจะส่งข้อยกเว้น 1 ซึ่งสอดคล้องกับสถานการณ์ที่เกิดข้อยกเว้น 1 ทุกประการ - คลาสผลิตภัณฑ์ประกอบด้วยรายการที่โหลดแบบขี้เกียจ
จากนั้นจึงมีวิธีหลีกเลี่ยงข้อยกเว้นนี้ - ลบรายการที่ต้องล่าช้าในการโหลดในผลิตภัณฑ์ จะกำจัดมันได้อย่างไร? คุณสามารถใช้ DataLoadOptions เพื่อโหลดได้ทันที หรือตั้งค่ารายการที่ต้องโหลดแบบ Lazy Loading เป็นค่าว่าง แต่วิธีแรกไม่ได้ผล ฉันเลยต้องใช้วิธีที่สอง
// รายการ 2
คลาส ProductRepository
-
สินค้าสาธารณะ GetProduct(int id)
-
NorthwindDataContext db = NorthwindDataContext ใหม่ ();
กลับ db.Products.SingleOrDefault(p => p.ProductID == id);
-
ผลิตภัณฑ์สาธารณะ GetProductNoDeffered (int id)
-
NorthwindDataContext db = ใหม่ NorthwindDataContext();
//ตัวเลือก DataLoadOptions = DataLoadOptions ใหม่ ();
//options.LoadWith<สินค้า>(p => p.Category);
//db.LoadOptions = ตัวเลือก;
ผลิตภัณฑ์ var = db.Products.SingleOrDefault(p => p.ProductID == id);
product.Category = null;
คืนสินค้า;
-
โมฆะสาธารณะ UpdateProduct(ผลิตภัณฑ์ผลิตภัณฑ์)
-
NorthwindDataContext db = ใหม่ NorthwindDataContext();
db.Products.Attach(สินค้า, จริง);
db.ส่งการเปลี่ยนแปลง();
-
-
//รหัสลูกค้า
พื้นที่เก็บข้อมูล ProductRepository = ใหม่ ProductRepository();
ผลิตภัณฑ์ผลิตภัณฑ์ = repository.GetProductNoDeffered(1);
product.ProductName = "ชัยเปลี่ยน";
repository.UpdateProduct (ผลิตภัณฑ์);
ข้อยกเว้น 2 จะถูกโยนทิ้งเมื่อใด
ตามวิธีการในส่วนที่แล้ว เราพบโค้ดที่มีข้อยกเว้น 2 อย่างรวดเร็ว โชคดีที่มีเพียงโค้ดนี้เท่านั้นในโปรเจ็กต์ทั้งหมด:
ถ้า (asModified && ((inheritanceType.VersionMember == null) && inheritanceType.HasUpdateCheck))
-
โยน System.Data.Linq.Error.CannotAttachAsModifiedWithoutOriginalState();
-
อย่างที่คุณเห็น เมื่อพารามิเตอร์ตัวที่สอง asModified ของ Attach เป็นจริง ไม่มีคอลัมน์ RowVersion (VersionMember=null) และมีคอลัมน์ตรวจสอบการอัปเดต (HasUpdateCheck) ข้อยกเว้น 2 จะถูกส่งออกไป รหัสของ HasUpdateCheck เป็นดังนี้:
บูลแทนที่สาธารณะ HasUpdateCheck
-
รับ
-
foreach (สมาชิก MetaDataMember ใน this.PersistentDataMembers)
-
ถ้า (member.UpdateCheck != UpdateCheck.Never)
-
กลับเป็นจริง;
-
-
กลับเท็จ;
-
}ซึ่งสอดคล้องกับสถานการณ์ของเราเช่นกัน - ตารางผลิตภัณฑ์ไม่มีคอลัมน์ RowVersion และในโค้ดที่สร้างขึ้นโดยอัตโนมัติโดยผู้ออกแบบ คุณสมบัติ UpdateCheck ของฟิลด์ทั้งหมดจะเป็นค่าเริ่มต้นเสมอ นั่นคือคุณสมบัติ HasUpdateCheck เป็นจริง
วิธีหลีกเลี่ยงข้อยกเว้น 2 นั้นง่ายกว่านั้นอีก โดยเพิ่มคอลัมน์ TimeStamp ลงในตารางทั้งหมด หรือตั้งค่าฟิลด์ IsVersion=true บนฟิลด์คีย์หลักของตารางทั้งหมด เนื่องจากวิธีหลังแก้ไขคลาสที่สร้างขึ้นโดยอัตโนมัติและสามารถเขียนทับด้วยการออกแบบใหม่ได้ตลอดเวลา ฉันขอแนะนำให้ใช้วิธีเดิม
วิธีการใช้วิธีแนบ?
หลังจากการวิเคราะห์ข้างต้น เราจะพบเงื่อนไขสองประการที่เกี่ยวข้องกับวิธีการแนบ: มีคอลัมน์ RowVersion หรือไม่ และมีการเชื่อมโยงคีย์นอกหรือไม่ (นั่นคือ รายการที่จำเป็นต้องโหลดแบบ Lazy Load) ฉันได้สรุปเงื่อนไขทั้งสองนี้และการใช้งานการแนบไฟล์ที่โอเวอร์โหลดหลายครั้งเมื่อดูตารางด้านล่าง คุณจะต้องเตรียมจิตใจให้พร้อม
หมายเลขซีเรียล
วิธีการแนบ
คอลัมน์ RowVersion มีคำอธิบายที่เกี่ยวข้องหรือไม่
1 เอกสารแนบ ไม่มี ไม่มี ไม่มีการแก้ไข
2 แนบ (เอนทิตี) ไม่ ใช่ NotSupportException: มีการพยายามแนบหรือเพิ่มเอนทิตี เอนทิตีไม่ใช่เอนทิตีใหม่และอาจถูกโหลดจาก DataContexts อื่น ไม่สนับสนุนการดำเนินการนี้
3 แนบ (เอนทิตี) ไม่ว่าจะไม่มีการแก้ไขหรือไม่
4 แนบ (เอนทิตี) ไม่ได้รับการแก้ไข เช่นเดียวกับ 2 หากเซ็ตย่อยไม่มีคอลัมน์ RowVersion
5 แนบ (เอนทิตี จริง) ไม่มี ไม่มี InvalidOperationException: หากเอนทิตีประกาศสมาชิกเวอร์ชันหรือไม่มีนโยบายการตรวจสอบการอัปเดต จะสามารถแนบเป็นเอนทิตีที่แก้ไขโดยไม่มีสถานะดั้งเดิมเท่านั้น
6 แนบ (เอนทิตี จริง) ไม่ ใช่ NotSupportException: มีการพยายามแนบหรือเพิ่มเอนทิตี เอนทิตีไม่ใช่เอนทิตีใหม่และอาจถูกโหลดจาก DataContexts อื่น ๆ ไม่สนับสนุนการดำเนินการนี้
7 แนบ (เอนทิตี, จริง) ไม่ว่าการแก้ไขจะเป็นเรื่องปกติหรือไม่ (การบังคับให้แก้ไขคอลัมน์ RowVersion จะรายงานข้อผิดพลาด)
8 แนบ (เอนทิตี จริง) ใช่ NotSupportException: มีการพยายามแนบหรือเพิ่มเอนทิตี เอนทิตีไม่ใช่เอนทิตีใหม่และอาจถูกโหลดจาก DataContexts อื่น ๆ ไม่สนับสนุนการดำเนินการนี้
9 แนบ (เอนทิตี เอนทิตี) ไม่มี ไม่มี DuplicateKeyException: ไม่สามารถเพิ่มเอนทิตีที่มีคีย์ที่ใช้งานอยู่แล้ว
10 แนบ (เอนทิตี เอนทิตี) ไม่ ใช่ NotSupportException: มีการพยายามแนบหรือเพิ่มเอนทิตี เอนทิตีไม่ใช่เอนทิตีใหม่และอาจถูกโหลดจาก DataContexts อื่น ๆ ไม่สนับสนุนการดำเนินการนี้
11 แนบ (เอนทิตี, เอนทิตี) DuplicateKeyException: ไม่สามารถเพิ่มเอนทิตีที่มีคีย์ที่ใช้งานอยู่แล้ว
12 แนบ (เอนทิตี เอนทิตี) ใช่ NotSupportException: มีการพยายามแนบหรือเพิ่มเอนทิตี เอนทิตีไม่ใช่เอนทิตีใหม่และอาจถูกโหลดจาก DataContexts อื่น ๆ ไม่สนับสนุนการดำเนินการนี้
ไฟล์แนบสามารถอัปเดตได้ตามปกติในสถานการณ์ที่ 7 เท่านั้น (รวมถึงคอลัมน์ RowVersion และไม่มีการเชื่อมโยงคีย์ต่างประเทศ)! สถานการณ์นี้แทบจะเป็นไปไม่ได้เลยสำหรับระบบที่ใช้ฐานข้อมูล! นี่คือ API ประเภทใด
สรุป มาสงบสติอารมณ์และเริ่มสรุปกันดีกว่า
หากคุณเขียน LINQ ลงในโค้ด SQL โดยตรงใน UI เช่น List 0 จะไม่มีอะไรโชคร้ายเกิดขึ้น แต่ถ้าคุณพยายามที่จะแยกชั้นการเข้าถึงข้อมูลที่แยกต่างหากออกไป ภัยพิบัติจะเกิดขึ้น นี่หมายความว่า LINQ ถึง SQL ไม่เหมาะสำหรับการพัฒนาสถาปัตยกรรมหลายชั้นใช่หรือไม่ หลายๆ คนกล่าวว่า LINQ เป็น SQL เหมาะสำหรับการพัฒนาระบบขนาดเล็ก แต่ขนาดที่เล็กไม่ได้หมายความว่าจะไม่มีการแบ่งชั้น มีวิธีใดบ้างที่จะหลีกเลี่ยงข้อยกเว้นมากมายเช่นนี้?
บทความนี้ได้ให้เบาะแสบางอย่างจริงๆ ในบทความถัดไปในชุดนี้ ฉันจะพยายามเสนอวิธีแก้ปัญหาต่างๆ ให้ทุกคนเลือก