دوره آموزشی Entity FrameWork Core - قسمت اول

در پست قبلی و در این لینک پیرامون EF Core مقدماتی را مطرح نمودیم. در این پست نحوه استفاده از EF Core را در یک برنامه ASP.NET Core Web MVC با هم مرور خواهیم نمود.یک برنامه خیالی برای یک فروشگاه اینترنتی را در نظر بگیرید که دارای موجودیت هایی مانند User، Order و Product است.(ما از یک رویکرد code first پیروی می کنیم)رویکرد Code First شما را قادر می سازد تا یک مدل موجودیت را در کد تعریف کنید، یک پایگاه داده از مدل ایجاد کنید و سپس داده ها را به پایگاه داده اضافه کنید.در اجرای این پروژه نیمچه نگاهی به برخی از مفاهیم رویکرد طراحی دامنه محور(Entities, Value Objects Aggregates and Roots) و صرفا در طراحی Class Diagram ها برای ایجاد مدل خود خواهیم داشت و برای سادگی کار فراتر از آن نخواهیم رفت.ایجاد یک پروژه جدید :ویژوال استودیو را باز کنیدروی Create a new project کلیک کنیدنوع پروژه مدنظر خود بر فرض Asp.Net Core Web APP را با تگ #C انتخاب کنید و روی Next کلیک کنیدبرای نام GetStartedWithEFCoreInAspNetMVC را وارد کرده، سایز جزویات را مانند تصاویر زیر پیش ببرید و در نهایت بروی Create کلیک کنید.Create a new projectAsp.Net Core Web APP Project NameAdditional informationنصب EF Core NuGet packages : از طریق Visual Studio NuGet Package Manager Console :Tools > NuGet Package Manager > Package Manager Consoleپکیج های زیر را نصب کنید :Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore Install-Package Microsoft.EntityFrameworkCore.SqlServerبسته Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore NuGet میان افزار ASP.NET Core را برای صفحات خطای EF Core فراهم می کند. این middleware به شناسایی و تشخیص خطاها با migrationهای EF Core کمک می کند.ایجاد Model :قبل از ایجاد مدل من به سراغ ترسیم نمودارهای کلاس(Class diagrams) رفته ام.در مهندسی نرم افزار، نمودار کلاس در زبان مدلسازی یکپارچه (UML) نوعی نمودار ساختار ایستا است که ساختار یک سیستم را با نشان دادن کلاس های سیستم، ویژگی ها(attributes)، عملیات (methods) و روابط بین اشیاء و همچنین محدودیت های اعمال شده بر سیستم را توصیف می کند.نمودار کلاس ستون فقرات ساختار مدل سازی شی گرا است. برای مدل‌سازی مفهومی کلی ساختار برنامه و تبدیل مدل‌ها به کد برنامه‌نویسی استفاده می‌شود. نمودارهای کلاس نیز می توانند برای مدل سازی داده ها استفاده شوند.نمودارهای کلاس به طور گسترده ای در مدل سازی سیستم های شی گرا استفاده می شوند زیرا آنها تنها نمودارهای UML هستند که می توانند مستقیماً با زبان های شی گرا نگاشت شوند.نمودارهای کلاس در میان مهندسان نرم افزار برای مستندسازی معماری نرم افزار بسیار محبوب هستند.یک نمودار کلاس UML از این موارد تشکیل شده است:مجموعه ای از کلاس ها ومجموعه ای از روابط بین کلاس هابیشتر بخوانید : آموزش کاربردی UML برای توسعه دهندگان نرم افزارOrder Class DiagramOrder Class DiagramProduct Class Diagramهمانطور که در ابتدا گفته شد و حالا با نگاهی بر نمودارهای توصیفی فوق و درک بهتر به سراغ طراحی مدل خود می رویم. به این معنی که برای هر یک از این موجودیت ها یک کلاس ایجاد می شود.موجودیت Customer : در پوشه Models، کلاس Customer را با کد زیر ایجاد کنید: public class Customer { public int CustomerID { get; set; } public string Name { get; set; } public string Family { get; set; } public DateTime DateOfBirth { get; set; } }نام ویژگی های موجودیت برای نام ستون ها در جداول پایگاه داده استفاده می شود.ویژگی CustomerID معادل ستون کلید اصلی (PK) در جدول پایگاه داده است که با این کلاس مطابقت دارد. به‌طور پیش‌فرض، EF خصوصیتی را که ID یا classnameID نام دارد به عنوان کلید اصلی تفسیر می‌کند. به عنوان مثال، PK می تواند به جای ID، CustomerID نامیده شود.نکته : در این حالت کلید اصلی یا Primery Key توسط پایگاه داده تولید می شود. اما اگر بخواهیم مقدار کلید اصلی را خودمان اختصاص دهیم چطور ؟[DatabaseGenerated(DatabaseGeneratedOption.None)]از Data Annotation فوق درست قبل از محل تعریف کلید اصلی استفاده می کنیم.ویژگی DatabaseGenerated با پارامتر None در property PK مشخص می‌کند که مقادیر کلید اولیه توسط کاربر ارائه می‌شوند نه اینکه توسط پایگاه داده تولید شوند.حاشیه نویسی داده یا Data Annotation در ASP.NET Core به فرآیند برچسب گذاری داده هایی اشاره دارد که یک برنامه با آن سروکار دارد. به عبارت دیگر، حاشیه‌نویسی داده‌ها ویژگی‌هایی هستند که به ویژگی‌هایی اضافه می‌شوند که به شما امکان می‌دهند محدودیت‌های ورودی داده را که ممکن است ضروری باشد، اعمال کنید.به طور پیش فرض، Entity Framework فرض می کند که مقادیر کلید اولیه توسط پایگاه داده تولید می شود. این چیزی است که در اکثر سناریوها می خواهید.موجودیت Product : public class Product { public int ProductID { get; set; } public string Name { get; set; } public string Description { get; set; } public Decimal Price { get; set; } }موجودیت Order : public enum OrderState { A,B,C,D,E } public class Order { public int OrderID { get; set; } public DateTime OrderDate { get; set; } public int CustomerID { get; set; } public ICollection<OrderItem> OrderItems { get; set; } public Address Address { get; set; } public OrderState? OrderState { get; set; } }ویژگی یا property با نام OrderItems یک ویژگی ناوبری(navigation property) است. ویژگی های ناوبری موجودیت های دیگری را در خود جای می دهد که به این موجودیت مرتبط هستند.ویژگی OrderItemsیک موجودیت Order:شامل تمام موجودیتهایی( آیتم هایی/محصولاتی) است که با آن موجودیت Order مرتبط هستند.اگر یک ردیف Order خاص در پایگاه داده دارای دو ردیف OrderItem مرتبط باشد:ویژگی ناوبری OrderItems آن موجودیت Order شامل آن دو موجودیت OrderItem است.اگر یک ویژگی ناوبری می تواند چندین موجودیت را در خود جای دهد، Type یا نوع آن می بایست یک list باشد مانند :ICollection<T>List<T> HashSet<T>روابط ناوبری چند به چند(Many-to-many) و یک به چند(one-to-many) می تواند شامل چندین موجودیت باشد. هنگامی که <ICollection<T استفاده می شود، EF به طور پیش فرض یک مجموعه <HashSet<T ایجاد می کند.OrderItem Chil Entity : public class OrderItem { public int OrderItemID { get; set; } public int ProductID { get; set; } public int OrderId { get; set; } public Order Order { get; set; } public decimal Price { get; set; } public int Count { get; set; } }ردیف‌های OrderItem حاوی مقدار PKیا کلید اصلی Order در ستون کلید خارجی OrderId (FK) است.Address Value Object public class Address { public string City { get; set; } public string Street { get; set; } public string Plaq { get; set; } }ایجاد Context Class : کلاس اصلی که عملکرد EF را برای یک مدل داده معین هماهنگ می کند، کلاس DbContext است. این کلاس با مشتق شدن از کلاس Microsoft.EntityFrameworkCore.DbContext ایجاد می شود. کلاس مشتق شده DbContext مشخص می کند که کدام موجودیت ها در مدل داده گنجانده شده اند. برخی از رفتارهای EF را می توان سفارشی کرد. در این پروژه نام کلاس ApplicationDBContext می باشد.در پوشه پروژه، پوشه ای به نام Data ایجاد کنید.در پوشه Data یک کلاس ApplicationDBContext با کد زیر ایجاد کنید: public class ApplicationDBContext : DbContext { public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options) : base(options) { } public DbSet<Customer> Customers { get; set; } public DbSet<Product> Products { get; set; } public DbSet<Order> Orders { get; set; } }هنگامی که پایگاه داده ایجاد می شود،به صورت پیش فرض EF جداولی را ایجاد می کند که نام هایی مشابه نام ویژگی های DbSet دارند اما بصورت جمع. اسامی موجودیت ها معمولاً جمع هستند. به عنوان مثال، Customers به جای Customer. در مورد اینکه آیا نام جدول باید جمع شود یا خیر، اختلاف نظر وجود دارد. برای لغو رفتار پیش‌فرض با تعیین نام‌های جداول منفرد در DbContext خواهیم داشت : protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Customer>().ToTable(&quotCustomer&quot); modelBuilder.Entity<Product>().ToTable(&quotProduct&quot); modelBuilder.Entity<Order>().ToTable(&quotOrder&quot); }برای موجودیت هایی که توسط ویژگی DbSet ارجاع نمی شوند، از نام کلاس های موجودیت به عنوان نام جدول استفاده می شود.پیکربندی سرویس‌های API برای پشتیبانی از Entity Framework Core :به کلاس Program.cs پروژه خود بروید. بیایید پشتیبانی از EntityFrameworkCore را اضافه کنیم. فقط این خطوط را اضافه کنید. با این کار EF Core در برنامه Register می شود.builder.Services.AddDbContext<ApplicationDBContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString(&quotDefaultConnection&quot), b => b.MigrationsAssembly(typeof(ApplicationDBContext).Assembly.FullName)); });خط 3 در مورد رشته اتصال که به عنوان DefaultConnection نامگذاری شده است می گوید. اما ما چنین ارتباطی را تعریف نکرده ایم، بیایید آن را نیز انجام دهیمتعریف رشته اتصال در appsettings.jsonما باید یک منبع داده را به API متصل کنیم. برای این کار، باید یک رشته اتصال در appsettings.json موجود در پروژه API تعریف کنیم.&quotConnectionStrings&quot: { &quotDefaultConnection&quot: &quotServer=.;Database=SampleDB;Trusted_Connection=True;MultipleActiveResultSets=true&quot },افزودن فیلتر database exception :builder.Services.AddDatabaseDeveloperPageExceptionFilter();خوب AddDatabaseDeveloperPageExceptionFilter اطلاعات خطای مفیدی را در محیط توسعه ارائه می دهد.به کلاس ApplicationDBContext.cs بروید و آن را با کد زیر به روز کنید:builder.Services.AddDatabaseDeveloperPageExceptionFilter(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler(&quot/Home/Error&quot); app.UseHsts(); } else { app.UseDeveloperExceptionPage(); app.UseMigrationsEndPoint(); }ایجاد DataBase :پکیج زیر را نصب کنیدInstall-Package Microsoft.EntityFrameworkCore.Toolsاکنون مدل های خود و رشته اتصال را آماده کرده ایم، تنها کاری که باید انجام دهیم این است که از مدل های تعریف شده یک پایگاه داده تولید کنیم. برای این کار باید از کنسول Package Manager ویژوال استودیو استفاده کنیم. می‌توانید این را با رفتن بهTools -> Nuget Package Manager -> Package Manager Consoleقبل از ادامه، بیایید بررسی کنیم که آیا مشکلاتی در Build وجود دارد یا خیر. برنامه را یک بار Build کنید تا مطمئن شوید خطایی وجود ندارد، زیرا ممکن است در مراحل بعدی اخطار مناسبی نشان داده نشود.و بعد :add-migration initialupdate-databaseشاید کمی بالاتر برای شما سوالی پیش آمده باشد در قسمت ساخت Model و برای کلاس Address ?وقتی دستورات بالا را اجرا کنید با خطای زیر روبرو خواهید شد !!!The entity type 'Address' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.ویژگی owned entity type اضافه شده در نسخه 2.0 به EF Core به بعد امکان می دهد انواعی را که هویت خود را به صراحت در مدل دامنه تعریف شده ندارند و به عنوان ویژگی، مانند یک value object، در هر یک از موجودیت های شما استفاده می شوند را mapping کنید.بنابراین Value Object Address به عنوان بخشی از موجودیت Order، به عنوان یک نوع موجودیت متعلق به موجودیت مالک، که در اینجا موجودیت Order است، پیاده‌سازی می‌شود. Address یک نوع بدون property شناسه/ID/PK است که در مدل دامنه تعریف شده است. به عنوان یک ویژگی از نوع Order برای تعیین آدرس حمل و نقل برای یک سفارش خاص استفاده می شود.طبق قرارداد، یک shadow primary key برای نوع Owner ایجاد می شود و با استفاده از table splitting به همان جدول مالک/Owner نگاشت(map) می شود.توجه به این نکته مهم است که owned types هیچ‌وقت به صورت قراردادی در EF Core کشف نمی‌شوند، بنابراین باید آنها را به صراحت اعلام کنید. modelBuilder.Entity<Order>().OwnsOne(c => c.Address);در اینجا به متد "OwnsOne" توجه کنید. این همان چیزی است که به EF می گوید که موجودیت "Order" ما می خواهد از یک value object استفاده کند.در واقع OwnsOne نشان می دهد که value object بخشی از موجودیت است. این همان چیزی است که به Entity Framework اجازه می دهد تا mapping را انجام دهد. طبق قرارداد Entity Framework هنگام اجرای migrations نام جدول را ValueObject_PropertyName می‌گذارد و هنگام mapping به دنبال آن می‌گردد. بنابراین در مورد آدرس، ستون‌هایی با نام‌های Address_City، Address_Street و غیره به پایان می‌رسد.اگر در configهای مربوطه از ToTable استفاده کنیم و یک نام جدول به آن بدهیم می توانیم owned typeها را در جدول جداگانه نگهداری کنیمbuilder.OwnsOne(x => x.Address).ToTable(&quotAddress&quot);با این کار یک رابطه یک به یک در دیتابیس ساخته می شود اما در عمکرد owned type تغییری ایجاد نخواهد شد.مجددا دستورات ساخت DataBase را اجرا کنید. به سراغ sql server بروید و جداول پایگاه داده خود را مشاهده و بررسی کنیدکمی بالاتر بروید و به کلاس DBContext و DbSet هایی که معرفی کردیم توجه کنید ما نامی از کلاس OrderItem نبرده بودیم اما جدول آن ساخته شده است. به عنوان تمرین علت آن را پیدا کنید.داده های آزمایشی :خوب EF تا اینجای کار یک پایگاه داده خالی ایجاد می کند. در این بخش، می خواهیم مقدار دهی اولیه پایگاه داده یا Data Seeding به معنای فرآیند پر کردن یک پایگاه داده با مجموعه اولیه داده است را بررسی کنیم.در پوشه Data یک کلاس جدید به نام DbInitializer با کد زیر ایجاد کنید:public class DbInitializer { private readonly ModelBuilder modelBuilder; public DbInitializer(ModelBuilder modelBuilder) { this.modelBuilder = modelBuilder; } public void Seed() { modelBuilder.Entity<Customer>().HasData( new Customer { CustomerID = 25, Name = &quotfarshid&quot, Family = &quotazizi&quot, DateOfBirth = DateTime.Parse(&quot2002-09-01&quot) }, new Customer { CustomerID = 50, Name = &quotreza&quot, Family = &quotamiri&quot, DateOfBirth = DateTime.Parse(&quot2009-11-12&quot) }, new Customer { CustomerID = 75, Name = &quotamir&quot, Family = &quotnaseri&quot, DateOfBirth = DateTime.Parse(&quot1983-09-24&quot) }, new Customer { CustomerID = 100, Name = &quotshirin&quot, Family = &quotemadi&quot, DateOfBirth = DateTime.Parse(&quot2000-06-01&quot) }, new Customer { CustomerID = 125, Name = &quotmaryam&quot, Family = &quottehrani&quot, DateOfBirth = DateTime.Parse(&quot2005-09-01&quot) } ); modelBuilder.Entity<Product>().HasData( new Product { ProductID = 10, Name = &quotproductA&quot, Description = &quot&quot, Price = 1000 }, new Product { ProductID = 20, Name = &quotproductB&quot, Description = &quot&quot, Price = 36000 }, new Product { ProductID = 30, Name = &quotproductC&quot, Description = &quot&quot, Price = 65200 }, new Product { ProductID = 40, Name = &quotproductD&quot, Description = &quot&quot, Price = 1500 }, new Product { ProductID = 50, Name = &quotproductE&quot, Description = &quot&quot, Price = 14000 }, new Product { ProductID = 60, Name = &quotproductF&quot, Description = &quot&quot, Price = 63520 }, new Product { ProductID = 100, Name = &quotproductG&quot, Description = &quot&quot, Price = 500 } ); modelBuilder.Entity<Order>().HasData( new Order { OrderID = 1, OrderDate = DateTime.Parse(&quot2022-08-07&quot), CustomerID = 25, OrderState = OrderState.A, }, new Order { OrderID = 2, OrderDate = DateTime.Parse(&quot2022-06-07&quot), CustomerID = 50, OrderState = OrderState.A, } ); modelBuilder.Entity<OrderItem>().HasData( new OrderItem { OrderItemID = 1, OrderId = 1, ProductID = 10, Price = 1000, Count = 1 }, new OrderItem { OrderItemID = 2, OrderId = 1, ProductID = 60, Price = 630, Count = 2 }, new OrderItem { OrderItemID = 3, OrderId = 2, ProductID = 100, Price = 490, Count = 3 } ); } }فرآیند Data Seeding دارای قواعدی است که می بایست در نظر گرفته شود.نحوه پر کردن داده ها برای Address که یک Owned Type است به عنوان تمرین به شما واگذار می شود.برای بهینه سازی عملکرد، داده های آزمایشی را به جای مجموعه های <List<T در آرایه ها بارگذاری می کنیم.به کلاس ApplicationDBContext.cs بروید و آن را با کد زیر به روز کنید:protected override void OnModelCreating(ModelBuilder modelBuilder) { ... new DbInitializer(modelBuilder).Seed(); }دستورات زیر را اجرا کنید :add-migration DataSeedingupdate-databaseبیشتر بخوانید : دوره آموزشی Entity FrameWork Core - قسمت دومبیشتر بخوانید : دوره آموزشی Entity FrameWork Coreبیشتر بخوانید : نقشه راه توسعه دهندگان Asp.NET Corehttps://zarinp.al/farshidazizi