กลยุทธ์ในการหลีกเลี่ยง SQL ในตัวควบคุมของคุณ ... หรือฉันควรมีกี่วิธีในแบบจำลองของฉัน


17

ดังนั้นสถานการณ์ที่ฉันพบเจอบ่อยพอสมควรคือสถานการณ์ที่แบบจำลองของฉันเริ่มต้นด้วย:

  • เติบโตเป็นสัตว์ประหลาดด้วยวิธีตันและตัน

หรือ

  • อนุญาตให้คุณส่งชิ้นส่วนของ SQL ไปยังพวกเขาเพื่อให้พวกเขามีความยืดหยุ่นเพียงพอที่จะไม่ต้องใช้วิธีการที่แตกต่างกันนับล้าน

ตัวอย่างเช่นสมมติว่าเรามีโมเดล "วิดเจ็ต" เราเริ่มต้นด้วยวิธีการพื้นฐานบางอย่าง:

  • ได้รับ ($ ID)
  • แทรก ($ บันทึก)
  • อัปเดต ($ id, $ บันทึก)
  • ลบ ($ ID)
  • getList () // รับรายการวิดเจ็ต

ทั้งหมดนี้เป็นเรื่องที่ดีและสวยงาม แต่จากนั้นเราต้องการรายงาน

  • รายการสร้างระหว่าง ($ start_date, $ end_date)
  • รายการซื้อระหว่าง ($ start_date, $ end_date)
  • listOfPending ()

จากนั้นการรายงานเริ่มซับซ้อนขึ้น:

  • listPendingCreatedBetween ($ start_date, $ end_date)
  • listForCustomer ($ CUSTOMER_ID)
  • listPendingCreatedBetweenForCustomer ($ customer_id, $ start_date, $ end_date)

คุณสามารถดูได้ว่าสิ่งนี้กำลังเติบโตที่ใด ... ในที่สุดเราก็มีข้อกำหนดการสืบค้นเฉพาะมากมายที่ฉันต้องใช้วิธีการตันและตันหรือวัตถุ "แบบสอบถาม" บางประเภทที่ฉันสามารถส่งไปยังแบบสอบถามเดียว -> แบบสอบถาม (แบบสอบถาม $ แบบสอบถาม) วิธีการ ...

... หรือเพียงแค่กัดกระสุนและเริ่มทำอะไรเช่นนี้:

  • list = MyModel-> query ("start_date> X AND end_date <Y AND ที่รอดำเนินการ = 1 AND customer_id = Z")

มีบางอย่างที่น่าสนใจเพียงแค่มีวิธีการหนึ่งเช่นนั้นแทนที่จะเป็นวิธีการเฉพาะเจาะจงมากกว่า 50 ล้านวิธี ... แต่บางครั้งก็รู้สึก "ผิด" ในการคัดสิ่งที่ SQL เข้าไปในคอนโทรลเลอร์

มีวิธีที่ "ถูกต้อง" ในการรับมือกับสถานการณ์เช่นนี้หรือไม่? ดูเหมือนว่าเป็นที่ยอมรับหรือไม่ที่จะบรรจุคำค้นหาเช่นนั้นไว้ในวิธีการทั่วไป -> query ()?

มีกลยุทธ์ที่ดีกว่านี้ไหม?


ฉันกำลังประสบปัญหาเดียวกันนี้ในโครงการที่ไม่ใช่ MVC คำถามที่เกิดขึ้นคือเลเยอร์การเข้าถึงข้อมูลควรสรุปออกมาทุกขั้นตอนการจัดเก็บและปล่อยให้ฐานข้อมูลเลเยอร์ตรรกะทางธุรกิจไม่เชื่อเรื่องพระเจ้าหรือควรจะเป็นเลเยอร์การเข้าถึงข้อมูลที่เป็นเรื่องธรรมดา บางทีโซลูชันระดับกลางคือการมีสิ่งต่าง ๆ เช่น ExecuteSP (สตริง spName พารามิเตอร์พารามิเตอร์วัตถุ []) จากนั้นรวมชื่อ SP ทั้งหมดในไฟล์ปรับแต่งเพื่อให้ชั้นธุรกิจอ่าน แม้ว่าฉันจะไม่ได้คำตอบที่ดีจริงๆ
Greg Jackson

คำตอบ:


10

รูปแบบของสถาปัตยกรรมแอปพลิเคชัน Enterprise Martin Fowler อธิบายจำนวนของที่เกี่ยวข้องกับออมทรัพย์ ORM รวมถึงการใช้ Query Object ซึ่งเป็นสิ่งที่ฉันแนะนำ

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

คุณจะมีพวกเขามากมายไหม? อย่างแน่นอน สามารถจัดกลุ่มเป็นแบบสอบถามทั่วไปได้ไหม ใช่อีกครั้ง

คุณสามารถใช้การฉีดพึ่งพาเพื่อสร้างวัตถุจากข้อมูลเมตาได้หรือไม่? นั่นคือสิ่งที่เครื่องมือออมส่วนใหญ่ทำ


4

ไม่มีวิธีที่ถูกต้องในการทำเช่นนี้ หลายคนใช้ ORM เพื่อแยกความซับซ้อนออกไป ORM ขั้นสูงเพิ่มเติมบางส่วนแปลนิพจน์รหัสเป็นคำสั่ง SQL ที่ซับซ้อน ORMs มีข้อเสียของพวกเขาเช่นกัน แต่สำหรับการใช้งานจำนวนมากผลประโยชน์เกินค่าใช้จ่าย

หากคุณไม่ได้ทำงานกับชุดข้อมูลขนาดใหญ่สิ่งที่ง่ายที่สุดที่ต้องทำคือเลือกตารางทั้งหมดลงในหน่วยความจำและตัวกรองในรหัส

//pseudocode
List<Person> people = Sql.GetList<Person>("select * from people");
List<Person> over21 = people.Where(x => x.Age >= 21);

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


1
+ 1 สำหรับ "ไม่มีทางที่ถูกต้องในการทำเช่นนี้เป็น"
Ozz

1
น่าเสียดายที่การกรองนอกชุดข้อมูลนั้นไม่ใช่ตัวเลือกจริงๆแม้แต่ชุดข้อมูลขนาดเล็กที่สุดที่เราทำงานด้วย - มันช้าเกินไป :-( ดีใจที่ได้ยินว่าคนอื่นเจอปัญหาเดียวกันกับฉัน :-)
Keith Palmer Jr.

@ KeithPalmer จากความอยากรู้โต๊ะของคุณใหญ่แค่ไหน?
แดน

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

-1 สำหรับ "ไม่มีวิธีที่ถูกต้องในการทำเช่นนี้" มีหลายวิธีที่ถูกต้อง การเพิ่มจำนวนวิธีการเป็นสองเท่าเมื่อคุณเพิ่มสถานที่ในขณะที่ OP กำลังทำอยู่นั้นเป็นวิธีที่ไม่สามารถลดขนาดได้และทางเลือกที่แนะนำที่นี่ไม่สามารถลดขนาดได้เท่ากันเพียงแค่คำนึงถึงขนาดของฐานข้อมูล วิธีการปรับขนาดมีอยู่ดูคำตอบอื่น ๆ
Theodore Murdock

4

ORM บางตัวอนุญาตให้คุณสร้างแบบสอบถามที่ซับซ้อนโดยเริ่มจากวิธีการพื้นฐาน ตัวอย่างเช่น

old_purchases = (Purchase.objects
    .filter(date__lt=date.today(),type=Purchase.PRESENT).
    .excude(status=Purchase.REJECTED)
    .order_by('customer'))

เป็นแบบสอบถามที่ถูกต้องสมบูรณ์ในDjango ออม

แนวคิดคือคุณมีตัวสร้างคิวรี (ในกรณีนี้Purchase.objects) ซึ่งมีสถานะภายในแสดงข้อมูลเกี่ยวกับคิวรี วิธีการเช่นget, filter, exclude, order_byถูกต้องและส่งกลับสร้างแบบสอบถามใหม่ที่มีการอัปเดตสถานะ วัตถุเหล่านี้ใช้อินเทอร์เฟซแบบ iterable ดังนั้นเมื่อคุณวนซ้ำกว่านั้นแบบสอบถามจะถูกดำเนินการและคุณจะได้รับผลลัพธ์ของแบบสอบถามที่สร้างขึ้นแล้ว แม้ว่าตัวอย่างนี้นำมาจาก Django คุณจะเห็นโครงสร้างเดียวกันใน ORM อื่น ๆ อีกมากมาย


ฉันไม่เห็นว่าข้อได้เปรียบนี้มีอะไรเหนือกว่าเช่น old_purchases = Purchases.query ("date> date.today () และ type = Purchase.PRESENT และสถานะ! = Purchase.REJECTED"); คุณไม่ได้ลดความซับซ้อนหรือทำให้เป็นนามธรรมโดยเพียงแค่ทำให้ SQL ANDs และ ORs เป็นวิธี AND และและ - คุณแค่เปลี่ยนการเป็นตัวแทนของ ANDs และ OR ใช่ไหม?
Keith Palmer Jr.

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

2
... กรณีที่เฉพาะเจาะจงมากที่สุด ตัวอย่างทั่วไปคือการกำหนดการสั่งซื้อเริ่มต้นสำหรับรุ่นใน Django ผลการสืบค้นทั้งหมดจะเป็นไปตามลำดับที่ว่าคุณจะไม่ระบุ อันดับที่สี่หากคุณต้องการทำให้ข้อมูลของคุณมีความผิดปกติด้วยเหตุผลด้านประสิทธิภาพคุณจะต้องปรับแต่ง ORM แทนที่จะเขียนคำค้นหาทั้งหมดของคุณใหม่
Andrea

+1 สำหรับภาษาคิวรีแบบไดนามิกเช่นภาษาที่กล่าวถึงและ LINQ
Evan Plaice

2

มีวิธีที่สามคือ

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

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

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

if (dataObject.getStartDate() != null) {
    query += " AND (date BETWEEN ? AND ?) "
}

... และที่ที่พารามิเตอร์ถูกเพิ่มเข้ากับแบบสอบถาม:

if (dataObject.getStartDate() != null) {
    preparedStatement.setTime(dataObject.getStartDate());
    preparedStatement.setTime(dataObject.getEndDate());
}

วิธีการนี้อนุญาตให้มีการเพิ่มโค้ดเชิงเส้นในขณะที่มีการเพิ่มคุณสมบัติโดยไม่ต้องอนุญาตการสืบค้นที่ไม่มีพารามิเตอร์


0

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


1
ตัวอย่างของสิ่งที่คุณกำลังพูดถึง ... ?
Keith Palmer Jr.

0

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

ฐานข้อมูลมีการดำเนินงานที่สำคัญ 4

  • แทรก
  • ปรับปรุง
  • ลบ
  • สอบถาม

อีกวิธีที่เป็นตัวเลือกอาจดำเนินการฐานข้อมูลบางอย่างที่ไม่อยู่ภายใต้การดำเนินการฐานข้อมูลพื้นฐาน เรียกว่าดำเนินการ

การแทรกและการอัปเดตสามารถรวมกันเป็นหนึ่งการดำเนินการที่เรียกว่าบันทึก

วิธีการของคุณเป็นคำถามมากมาย ดังนั้นคุณสามารถสร้างอินเทอร์เฟซทั่วไปเพื่อตอบสนองความต้องการส่วนใหญ่ในทันที นี่คือตัวอย่างอินเตอร์เฟสทั่วไป:

 public interface IDALService
    {
        DataTransferObject<T> Save<T>(DataTransferObject<T> Dto) where T : IPOCO;
        DataTransferObject<T> Search<T>(DataTransferObject<T> Dto) where T: IPOCO;
        DataTransferObject<T> Delete<T>(DataTransferObject<T> Dto) where T : IPOCO;
        DataTransferObject<T> Execute<T>(DataTransferObject<T> Dto) where T : IPOCO;
    }

วัตถุการถ่ายโอนข้อมูลเป็นข้อมูลทั่วไปและจะมีตัวกรองพารามิเตอร์การเรียงลำดับและอื่น ๆ ทั้งหมดที่อยู่ในนั้น ชั้นข้อมูลจะรับผิดชอบในการแยกวิเคราะห์และแยกออกและตั้งค่าการดำเนินการไปยังฐานข้อมูลผ่านขั้นตอนการจัดเก็บพารามิเตอร์ sql, linq เป็นต้นดังนั้น SQL จะไม่ผ่านระหว่างชั้น โดยทั่วไปนี่คือสิ่งที่ ORM ทำ แต่คุณสามารถหมุนของคุณเองและมีการทำแผนที่ของคุณเอง

ดังนั้นในกรณีของคุณคุณมีวิดเจ็ต วิดเจ็ตจะใช้อินเตอร์เฟส IPOCO

ดังนั้นในโมเดลเลเยอร์บริการของคุณจะมี getList().

จะต้องใช้เลเยอร์การแมปเพื่อจัดการการgetListเปลี่ยนภาพให้เป็น

Search<Widget>(DataTransferObject<Widget> Dto)

และในทางกลับกัน. ดังที่คนอื่น ๆ ได้กล่าวถึงบางครั้งสิ่งนี้ทำได้ผ่าน ORM แต่ท้ายที่สุดคุณก็จบลงด้วยรหัสสำเร็จรูปจำนวนมากโดยเฉพาะอย่างยิ่งถ้าคุณมีตาราง 100 ตาราง ORM สร้าง SQL แบบ parametized อย่างน่าอัศจรรย์และเรียกใช้งานนั้นเทียบกับฐานข้อมูล ถ้ากลิ้งของคุณเองนอกจากนี้ในชั้นข้อมูลของตัวเองแมปจะต้องตั้งค่า SP, linq ฯลฯ (โดยทั่วไป sql ไปที่ฐานข้อมูล)

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

ดังนั้นกลยุทธ์พื้นฐานคือ:

  • บริการเรียกเลเยอร์
  • เปลี่ยน Service Layer Call เป็นฐานข้อมูลโดยใช้ Repository / Mapping บางประเภท
  • ฐานข้อมูลการโทร

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

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