เหตุใดจึงใช้ 'เสมือน' สำหรับคุณสมบัติคลาสในนิยามโมเดลของ Entity Framework


223

ในบล็อกต่อไปนี้: http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx

บล็อกมีตัวอย่างโค้ดต่อไปนี้:

public class Dinner
{
   public int DinnerID { get; set; }
   public string Title { get; set; }
   public DateTime EventDate { get; set; }
   public string Address { get; set; }
   public string HostedBy { get; set; }
   public virtual ICollection<RSVP> RSVPs { get; set; }
}

public class RSVP
{
   public int RsvpID { get; set; }
   public int DinnerID { get; set; }
   public string AttendeeEmail { get; set; }
   public virtual Dinner Dinner { get; set; }
}

จุดประสงค์ของการใช้คืออะไรvirtualเมื่อกำหนดคุณสมบัติในคลาส? มันมีผลกระทบอะไรบ้าง


9
คุณขอให้เข้าใจวัตถุประสงค์ทั่วไปของคำหลัก 'เสมือน' ใน C # หรือเกี่ยวข้องกับ Entity Framework โดยเฉพาะหรือไม่?
M.Babcock

2
@ M.Babcock: ฉันกำลังถามว่าจุดประสงค์นั้นเกี่ยวข้องกับคุณสมบัติอย่างไรเพราะฉันไม่เคยเห็นสิ่งนี้มาก่อน
Gary Jones

1
หากคุณคุ้นเคยกับวิธีการที่คำหลักเสมือนมีผลต่อความหลากหลายในวิธีการมันก็เหมือนกันสำหรับคุณสมบัติ
M.Babcock

20
@ M.Babcock: ฉันจะทำให้มันชัดเจนมากขึ้นได้อย่างไร? คำถามนี้มีชื่อว่า "ทำไมจึงใช้ 'เสมือน' สำหรับคุณสมบัติในคลาส?"
Gary Jones

2
@Gary - คุณสมบัติ getter / setter จะถูกรวบรวมเป็นวิธีการแบบคงที่ ดังนั้นพวกเขาจึงไม่ใช่สาขาวิชาดั้งเดิมเช่น 'อาหารค่ำสาธารณะเสมือน'
Shan Plourde

คำตอบ:


248

อนุญาตให้ Entity Framework สร้างพร็อกซีรอบ ๆ คุณสมบัติเสมือนจริงเพื่อให้คุณสมบัติสามารถรองรับการโหลดแบบสันหลังยาวและการติดตามการเปลี่ยนแปลงที่มีประสิทธิภาพมากขึ้น ดูคำหลักเสมือนจริงที่มีผลกระทบอะไรบ้างที่มีอยู่ใน Entity Framework 4.1 POCO Code ก่อน สำหรับการอภิปรายอย่างละเอียดมากขึ้น

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

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

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

แก้ไขเพื่ออธิบายสาเหตุที่คุณสมบัติถูกทำเครื่องหมายเป็นเสมือน

คุณสมบัติเช่น:

 public ICollection<RSVP> RSVPs { get; set; }

ไม่ใช่เขตข้อมูลและไม่ควรคิดเช่นนี้ สิ่งเหล่านี้เรียกว่า getters และ setters และในเวลารวบรวมจะถูกแปลงเป็นวิธีการ

//Internally the code looks more like this:
public ICollection<RSVP> get_RSVPs()
{
    return _RSVPs;
}

public void set_RSVPs(RSVP value)
{
    _RSVPs = value;
}

private RSVP _RSVPs;

นั่นเป็นเหตุผลว่าทำไมพวกเขาจึงถูกทำเครื่องหมายเป็นเสมือนสำหรับใช้ใน Entity Framework ซึ่งอนุญาตให้คลาสที่สร้างขึ้นแบบไดนามิกเพื่อแทนที่การสร้างภายในgetและsetฟังก์ชัน หาก getter / setters คุณสมบัติการนำทางของคุณทำงานให้คุณในการใช้งาน Entity Framework ลองแก้ไขให้เป็นเพียงคุณสมบัติคอมไพล์ใหม่และดูว่า Entity Framework ยังคงทำงานได้อย่างถูกต้องหรือไม่:

 public virtual ICollection<RSVP> RSVPs;

2
คุณหมายถึงอะไรโดย 'สร้างพร็อกซีรอบ ๆ '? เกิดอะไรขึ้นที่นี่จริง ๆ ?
Gary Jones

2
สวัสดีแกรี่ฉันแก้ไขคำตอบของฉันเพื่อชี้แจงสิ่งที่ฉันหมายถึงโดย "สร้างพร็อกซีรอบ ๆ " หวังว่าจะช่วยหน่อย
Shan Plourde

2
การพูดว่า "คุณสมบัติ ... ไม่ใช่คุณสมบัติ" นั้นค่อนข้างไม่ช่วยเหลือ คุณสมบัติทั้งหมดถูกนำมาใช้เป็นวิธี getter และ / หรือ setter ดังนั้นจึงไม่มีเหตุผลที่จะพูดว่า "คุณสมบัตินี้เป็นวิธี getter และ setter ไม่ใช่คุณสมบัติ"
Ben Voigt

1
ขอบคุณสำหรับคำติชมของคุณเบ็นฉันควรชี้แจงว่า "คุณสมบัติไม่ใช่ฟิลด์" แจ้งให้เราทราบหากคุณมีข้อเสนอแนะหรือคำถามอื่น ๆ
Shan Plourde

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

75

virtualคำหลักใน C # ช่วยให้วิธีการหรือสถานที่ให้บริการจะถูกแทนที่โดยเด็กเรียน สำหรับข้อมูลเพิ่มเติมโปรดดูเอกสารประกอบของ MSDN ในคำหลัก 'เสมือน'

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


23
@Hooch สิ่งนี้ไม่ถูกทำเครื่องหมายว่าถูกต้องเพราะสิ่งที่ถือว่า "ถูกต้อง" ไม่เพียงขึ้นอยู่กับชื่อคำถาม ฉันคิดว่าคนส่วนใหญ่ฉันและ OP รวมอยู่ด้วยก่อนอื่นจัดการกับvirtualคุณสมบัติผ่าน Entity Framework - แม้ว่าจะไม่ชัดเจนในชื่อของ OP คำตอบที่ได้รับการยอมรับเป็นเช่นนั้นเพราะมันสัมผัสกับด้านเอนทิตี้ของสิ่งต่าง ๆ และวิธีการ / virtualคุณสมบัติที่ใช้ในบริบทนั้น
Don Cheadle

22

ฉันเข้าใจความหงุดหงิดของการใช้งานการใช้งานเสมือนนี้ไม่ได้มีไว้สำหรับสิ่งที่เป็นนามธรรมที่ตัวปรับเปลี่ยนเสมือน defacto นั้นมีประสิทธิภาพสำหรับ

หากมีใครยังคงดิ้นรนกับเรื่องนี้ฉันจะเสนอมุมมองของฉันในขณะที่ฉันพยายามที่จะทำให้การแก้ปัญหาง่ายและศัพท์แสงน้อยที่สุด:

เอนทิตี้ของเฟรมเวิร์กในชิ้นส่วนง่าย ๆ นั้นใช้การโหลดแบบขี้เกียจซึ่งเทียบเท่ากับการเตรียมบางสิ่งไว้สำหรับการดำเนินการในอนาคต เหมาะกับตัวปรับแต่ง 'เวอร์ชวล' แต่มีมากกว่านี้

ใน Entity Framework การใช้คุณสมบัติการนำทางเสมือนช่วยให้คุณแสดงว่ามันเทียบเท่ากับ Foreign Key ที่ไม่มีค่าใน SQL คุณไม่จำเป็นต้องเข้าร่วมทุกตารางคีย์เมื่อดำเนินการค้นหา แต่เมื่อคุณต้องการข้อมูล - มันจะกลายเป็นความต้องการที่ขับเคลื่อนด้วย

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

ดังนั้นในขณะที่มันอาจดูเป็นความลับในการดำเนินการจริงในเวลาใช้งานฉันได้พบกฎที่ดีที่สุดที่จะใช้: ถ้าคุณกำลังส่งออกข้อมูล (การอ่านในรูปแบบการดูหรือแบบจำลองอนุกรม) ใช้เสมือน หากขอบเขตของคุณกำลังรวบรวมข้อมูลที่อาจไม่สมบูรณ์หรือจำเป็นต้องค้นหาและไม่ต้องการพารามิเตอร์การค้นหาทุกตัวที่สมบูรณ์สำหรับการค้นหารหัสจะใช้การอ้างอิงที่ดีคล้ายกับการใช้คุณสมบัติค่าที่เป็นโมฆะ int? ยาว?. นอกจากนี้การทำให้ตรรกะทางธุรกิจของคุณเป็นนามธรรมจากการรวบรวมข้อมูลของคุณจนกว่าความต้องการในการฉีดจะมีประโยชน์มากมายต่อประสิทธิภาพเช่นเดียวกับการทำให้วัตถุเป็นอินสแตนซ์และเริ่มต้นด้วยค่าว่าง Entity Framework ใช้การสะท้อนและการเปลี่ยนแปลงจำนวนมากซึ่งสามารถลดประสิทธิภาพการทำงานและความต้องการที่จะมีรูปแบบที่ยืดหยุ่นที่สามารถปรับขนาดตามความต้องการมีความสำคัญต่อการจัดการประสิทธิภาพ

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


14

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

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

จากหนังสือ "ASP.NET MVC 5 พร้อม Bootstrap และ Knockout.js"


3

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


0

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

public virtual double Area() 
{
    return x * y;
}

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

class MyBaseClass
{
    // virtual auto-implemented property. Overrides can only
    // provide specialized behavior if they implement get and set accessors.
    public virtual string Name { get; set; }

    // ordinary virtual property with backing field
    private int num;
    public virtual int Number
    {
        get { return num; }
        set { num = value; }
    }
}


class MyDerivedClass : MyBaseClass
{
    private string name;

    // Override auto-implemented property with ordinary property
    // to provide specialized accessor behavior.
    public override string Name
    {
        get
        {
            return name;
        }
        set
        {
            if (value != String.Empty)
            {
                name = value;
            }
            else
            {
                name = "Unknown";
            }
        }
    }
}

นี่เป็นเรื่องที่ไม่เกี่ยวข้องเลย
Eru

0

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

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

ตัวอย่าง ของ Let พิจารณาToString ()วิธีการในSystem.Object เนื่องจากวิธีนี้เป็นสมาชิกของ System.Object ซึ่งสืบทอดมาในคลาสทั้งหมดและจะมีวิธีการ ToString () ให้กับพวกเขาทั้งหมด

namespace VirtualMembersArticle
{
    public class Company
    {
        public string Name { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Company company = new Company() { Name = "Microsoft" };
            Console.WriteLine($"{company.ToString()}");
            Console.ReadLine();
        }   
    }
}

ผลลัพธ์ของรหัสก่อนหน้าคือ:

VirtualMembersArticle.Company

ลองพิจารณาว่าเราต้องการเปลี่ยนพฤติกรรมมาตรฐานของวิธีการ ToString () ที่สืบทอดมาจาก System.Object ในระดับ บริษัท ของเรา เพื่อให้บรรลุเป้าหมายนี้ก็เพียงพอที่จะใช้คีย์เวิร์ด override เพื่อประกาศการใช้งานเมธอดนั้นอีก

public class Company
{
    ...
    public override string ToString()
    {
        return $"Name: {this.Name}";
    }         
}

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

Name: Microsoft

ในความเป็นจริงถ้าคุณตรวจสอบคลาส System.Object คุณจะพบว่าวิธีการทำเครื่องหมายเป็นเสมือน

namespace System
{
    [NullableContextAttribute(2)]
    public class Object
    {
        ....
        public virtual string? ToString();
        ....
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.