
دوره آموزشی Entity FrameWork Core - قسمت سیزدهم
بیایید قبل از شروع موضوع اصلی یکسری از تعاریف و مفاهیم پایه در #C را با هم مرور کنیم :
همه چیز در سی شارپ با کلاس ها و اشیاء، همراه با ویژگی ها(attributes) و متدهای(methods) آن، مرتبط است. به عنوان مثال: در زندگی واقعی، Person یک شی است. Person دارای ویژگی هایی مانند وزن و رنگ، قد، نام و ... و متدهایی مانند غداخوردن، دویدن و ... است.
یک کلاس مانند یک سازنده شی(object constructor) یا یک "طرح" برای ایجاد اشیا است.
class Person { string name= ''jane'' }
هنگامی که یک متغیر مستقیماً در یک کلاس اعلام می شود، اغلب به عنوان یک فیلد/field
یا ( ویژگی/attribute) نامیده می شود.
فیلد متغیری از هر نوع است که مستقیماً در یک class یا struct اعلان می شود.
یک class یا struct ممکن است دارای instance fields، static fields یا هر دو باشد.
کلاس زیر را در نظر بگیرید :
class Person { public string Name; public Person(string name) { Name = name; } }
تعریف instance field :
در کلاس Person ویژگی Name یک instance field است که به نمونه ای از کلاس Person محدود شده است.
این بدان معنی است که هر نمونه از کلاس Person دارای فیلد Name خاص خود با مقدار جداگانه است.
حالا دو نمونه از کلاس Person ایجاد کنید:
var p1 = new Person(''John''); var p2 = new Person(''Jane''); Console.WriteLine($''p1 Name: {p1.Name}''); Console.WriteLine($''p2 Name: {p2.Name}'');

همانطور که در تصویر می بینید ، p1 و p2 ارجاعی هستند که به اشیاء مجزای Person با فیلدها و مقادیر نام خود اشاره دارند.
در ادامه یک فیلد ثابت Count به کلاس Person اضافه کنید و مقدار آن را صفر کنید:
class Person { public string Name; public static int Count = 0; public Person(string name) { Name = name; } }
بر خلاف یک فیلد نمونه، کامپایلر یک مکان حافظه جداگانه برای ذخیره یک فیلد استاتیک ایجاد می کند:

برای دسترسی به فیلد استاتیک Count در کلاس Person، مستقیماً به فیلد ارجاع می دهید:
Count
از آنجایی که فیلد استاتیک Count به کلاس Person محدود شده است، نه نمونه ای از کلاس، نمی توانید از کلمه کلیدی this برای ارجاع به آن استفاده کنید. عبارت زیر منجر به خطا می شود:
this.Count
در قدم بعدی بیایید مقدار فیلد استاتیک Count را یک عدد در سازنده افزایش دهید:
class Person { public string Name; public static int Count = 0; public Person(string name) { Name = name; Count++; } }
در ادامه با استفاده از ClassName.StaticField به یک فیلد ثابت خارج از کلاس دسترسی پیدا کنید:
Console.WriteLine(Person.Count); // 0
در نهایت دو نمونه از کلاس Person ایجاد کنید. هر عبارت به طور ضمنی سازنده Person را فراخوانی می کند که مقدار فیلد استاتیک Count را یک برابر افزایش می دهد:
Console.WriteLine($''Person count: {Person.Count}''); // 0 var p1 = new Person(''John''); var p2 = new Person(''Jane''); Console.WriteLine($''Person count: {Person.Count}''); // 2

مانند یک فیلد استاتیک، یک static property به یک کلاس محدود می شود، نه هر نمونه ای از کلاس. مثال زیر کلاس Person را با یک property استاتیک Count دوباره تعریف می کند:
class Person { public string Name; public static int Count { get; private set; } public Person(string name) { Name = name; Count++; } }
در این مثال، ویژگی Count دارای یک دسترسی عمومی get و یک دسترسی خصوصی set است. این بدان معناست که شما می توانید ویژگی Count را در داخل کلاس تغییر دهید و از داخل و خارج از کلاس به آن دسترسی داشته باشید.
خلاصه instance fields، static fields
طبق تعریف، یک Property عضوی از یک کلاس است که راهی انعطافپذیر برای خواندن، نوشتن یا محاسبه مقدار یک فیلد خصوصی ارائه میکند.
به عنوان مثال، شکل زیر کلاس Person را با سه فیلد خصوصی firstName، lastName و age تعریف می کند:
class Person { private string Name; private string Family; private int age; }
برای تخصیص مقادیر و خواندن مقادیر از این فیلدهای خصوصی، از Property ها استفاده می کنید. قطعه کد زیر نحوه اضافه کردن سه Property به کلاس Person را نشان می دهد:
class Person { private string name; private string family; private int age; public string Name { get { return name; } set { name= value; } } public string Family { get { return family; } set { family= value; } } public int Age { get { return age; } set { age = value; } } }
در این مثال، یک Property مانند یک فیلد را با بلوکهای get و set اعلام میکنیم. get و set را دسترسی های Property یا property accessors می نامند.
وقتی از یک Property می خوانید، get accessor اجرا می شود. و وقتی مقداری را به یک Property اختصاص می دهید، set accessor اجرا می شود. مثلا:
var p1 = new Person(); p1.Name = ''farshid'' p1.Family= ''azizi'' Console.WriteLine($''{p1.Name} {p1.Family}'');
در این مثال:
ابتدا یک نمونه از کلاس Person ایجاد کنید.
دوم، مقادیر را به Property های Name و Family اختصاص دهید.
سوم، خواندن مقادیر از Property های Name و Family.
استفاده از C# Property برای اعتبارسنجی داده ها :
از آنجایی که یک Property مکانی مرکزی برای تخصیص یک مقدار به یک فیلد خصوصی است، میتوانید دادهها را اعتبارسنجی کنید و اگر دادهها معتبر نیستند، استثنا(exception) ایجاد کنید.
فرض کنید می خواهید قوانین اعتبارسنجی زیر را پیاده سازی کنید:
برای انجام این کار، میتوانید منطق اعتبارسنجی را به set accessors اضافه کنید، همانطور که در مثال زیر نشان داده شده است:
class Person { private string name; private string family; private int age; public string Name { get { return name; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentException(''The name must not be empty or null''); } name= value; } } public string Family { get { return family; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentException(''The family must not be empty or null''); } fa,ily= value; } } public int Age { get { return age; } set { if (value < 0 || value > 120) { throw new ArgumentException(''The age must be between 1 and 120''); } age = value; } } }
برای ایجاد یک property محاسبهشده، میتوانید get accessor را پیادهسازی کنید. به عنوان مثال، می توانید یک پراپرتی FullName ایجاد کنید که الحاق Name و Family را برمی گرداند:
class Person { private string name; private string family; private int age; public string Name { get { return name; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentException(''The name must not be empty or null''); } name= value; } } public string Family { get { return family; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentException(''The family must not be empty or null''); } family= value; } } public int Age { get { return age; } set { if (value < 0 || value > 120) { throw new ArgumentException(''The age must be between 1 and 120''); } age = value; } } public string FullName { get { return $''{Name} {Family}'' } } }
و می توانید از ویژگی FullName بخوانید:
var p1 = new Person(); p1.Name= ''farshid'' p1.Family= ''azizi'' p1.Age = 25; Console.WriteLine(p1.FullName);
اگر بخواهید مقداری را به ویژگی FullName اختصاص دهید، با یک خطای کامپایل مواجه خواهید شد. مثلا:
var p1 = new Person(); p1.Name= ''farshid'' p1.Family= ''azizi'' p1.Age = 25; Console.WriteLine(p1.FullName); p1.FullName = ''farshid azizi''
خطا :
Property or indexer 'Person.FullName' cannot be assigned to -- it is read only
اگر property دارید که نیازی به منطق اضافی در set or get accessors ندارد ، میتوانید از
auto-implemented properties استفاده کنید.
مثال زیر کلاس Skill را تعریف می کند که دارای دو فیلد خصوصی(private fields) نام(name) و رتبه بندی(rating) است:
class Skill { private string name; private sbyte rating; public string Name { get { return name; } set { name = value; } } public sbyte Rating { get { return rating; } set { rating = value; } } }
از آنجایی که accessors پراپرتی ها منطق اضافی به جز خواندن و نوشتن در فیلدهای خصوصی ندارند، میتوانید از auto-implemented properties مانند این استفاده کنید:
class Skill { public string Name { get; set; } public sbyte Rating { get; set; } }
هنگامی که کامپایلر سی شارپ با یک auto-implemented properties مواجه میشود، یک فیلد خصوصی و anonymous ایجاد میکند که میتوان از طریق set و get accessors به آن دسترسی پیدا کرد.
همانطور که می بینید، auto-implemented properties کد را در این مورد مختصرتر می کند.
class Skill { public string Name { get; set; } public sbyte Rating { get; set; } = 1; }
در مثال بالا، ویژگی Rating را مقداردهی اولیه می کنیم تا زمانی که یک نمونه جدید از کلاس Skill ایجاد
می کنید، مقدار آن یک باشد.
خوب بیایید یک خلاصه و جمع بندی از مطالب تا اینجا داشته باشیم :
خوب اکنون وقت آن رسیده که به سراغ موضوع اصلی این پست یعنی Backing Fields برویم
آنها به EF امکان خواندن و/یا نوشتن در یک field را به جای یک property را می دهند. توجه داشته باشید property ها چیزی را ذخیره نمی کند. شما باید یک Backing Fields داشته باشید تا آنها چیزی را ذخیره کنند.
آنها به شما امکان می دهند پراپرتی های کلاس های خود را کپسوله(encapsulate) کنید. کپسولهسازی به شما امکان میدهد تعامل با کلاسها و APIهای خود را آسانتر کنترل کنید تا اطمینان حاصل کنید که عمدا یا تصادفی از آنها سوء استفاده نمیشود.
این میتواند زمانی مفید باشد که کپسولهسازی در کلاس برای محدود کردن استفاده در دسترسی به دادهها توسط کد APP استفاده شود، اما value باید بدون استفاده از آن محدودیتها از پایگاه داده خوانده و/یا نوشته شود.
طبق قرارداد، فیلدهای زیر به عنوان فیلدهای پشتیبان(Backing Fields) برای یک property مشخص (به ترتیب اولویت فهرست شده) کشف می شوند.
_<camel-cased property name>_<property name>m_<camel-cased property name>m_<property name>در نمونه زیر، پراپرتی Url طوری پیکربندی شده است که دارای url_ به عنوان فیلد پشتیبان آن باشد:
public class Blog { private string _url; public int BlogId { get; set; } public string Url { get { return _url; } set { _url = value; } } }
توجه داشته باشید که فیلدهای پشتیبان فقط برای پراپرتی هایی که در مدل گنجانده شده اند کشف(discovered) می شوند.
همچنین میتوانید فیلدهای پشتیبان را با استفاده از یک Data Annotation (موجود در EFCore 5.0) یا Fluent API، ، پیکربندی کنید. اگر نام فیلد با قراردادهای فوق مطابقت ندارد:
public class Blog { private string _validatedUrl; public int BlogId { get; set; } [BackingField(nameof(_validatedUrl))] public string Url { get { return _validatedUrl; } } public void SetUrl(string url) { // put your validation code here _validatedUrl = url; } }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) .HasField("_validatedUrl"); }
به طور پیشفرض، EF همیشه در فیلد پشتیبان میخواند و مینویسد - با فرض اینکه یکی به درستی پیکربندی شده باشد - و هرگز از property استفاده نخواهد کرد. با این حال، EF از الگوهای دسترسی دیگر نیز پشتیبانی می کند. به عنوان مثال، نمونه زیر به EF دستور می دهد که فقط در حین materializing(هنگام خواندن یک موجودیت از پایگاه داده) در فیلد پشتیبان بنویسد و در سایر موارد از property استفاده کند:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) .HasField(''_validatedUrl'') .UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction); }
شما همچنین می توانید یک پراپرتی مفهومی در مدل خود ایجاد کنید که دارای ویژگی CLR متناظر در کلاس موجودیت نباشد، اما در عوض از یک فیلد برای ذخیره داده ها در موجودیت استفاده می کند. این با Shadow Properties متفاوت است، جایی که داده ها در change tracker ذخیره می شوند، نه در CLR type موجودیت. پراپرتیهای Field-only معمولاً زمانی استفاده میشوند که کلاس موجودیت از متدها به جای ویژگیها برای get/set مقادیر استفاده میکند، یا در مواردی که فیلدها اصلاً نباید در مدل دامنه در معرض دید قرار گیرند (مثلاً کلیدهای اصلی).
شما می توانید یک پراپرتی Field-only را با ارائه یک نام در (...)API Property پیکربندی کنید:
internal class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property("_validatedUrl"); } } public class Blog { private string _validatedUrl; public int BlogId { get; set; } public string GetUrl() { return _validatedUrl; } public void SetUrl(string url) { using (var client = new HttpClient()) { var response = client.GetAsync(url).Result; response.EnsureSuccessStatusCode(); } _validatedUrl = url; } }
ـEF سعی می کند یک CLR property با نام داده شده یا یک فیلد را در صورت پیدا نشدن property پیدا کند. اگر نه یک property و نه یک فیلد یافت نشد، یک shadw property به جای آن ایجاد می شود.
var blogs = db.blogs.OrderBy(b => EF.Property<string>(b, "_validatedUrl"));
بیشتر بخوانید : دوره آموزشی Entity FrameWork Core - قسمت پانزدهم
بیشتر بخوانید : دوره آموزشی Entity FrameWork Core
بیشتر بخوانید : نقشه راه توسعه دهندگان Asp.NET Core
https://zarinp.al/farshidazizi
