خود آموز entityframework
Welcome to EntityFrameworkTutorial.net. Here you will learn everything about Entity Framework in easy steps. It covers basic to expert level tutorials for all the features of Entity Framework with code snippets. Readers can also download sample project and the School database used in various tutorials.
-
تنها دریافت رکوردهای مورد نیاز
string city = "New York"; List<School> schools = db.Schools.ToList(); List<School> newYorkSchools = schools.Where(s => s.City == city).ToList();
در کد بالا ابتدا کلیه ردیفهای جدول از دیتابیس به حافظه منتقل میشود و سپس برروی آنها کوئری مورد نظر اعمال میگردد که بشدت میتواند برای یک برنامه - خصوصا برنامه وب - بهدلیل دریافت کلیهی ردیفهای جدول بسیار مخرب باشد. کوئری فوق را میتوان به صورت زیر اصلاح کرد:
List<School> newYorkSchools = db.Schools.Where(s => s.City == city).ToList(); یا IQueryable<School> schools = db.Schools; List<School> newYorkSchools = schools.Where(s => s.City == city).ToList();
-
حداقل رفت و برگشت به دیتابیس
کد زیر را در نظر بگیرید:
string city = "New York"; List<School> schools = db.Schools.Where(s => s.City == city).ToList(); var sb = new StringBuilder(); foreach(var school in schools) { sb.Append(school.Name); sb.Append(": "); sb.Append(school.Pupils.Count); sb.Append(Environment.NewLine); }
هدف تکه کد بالا این است که تعداد دانش آموزان مدرسههای واقع در شهر New York را بدست آورد.
توجه داشته باشید:
- یک مدرسه میتواند چندین دانش آموز داشته باشد (وجود رابطه یک به چند)
- LazyLoading فعال است
- تعداد مدرسههای شهر نیویورک 200 عدد میباشد
اگر کوئری بالا را بهوسیلهی یک پروفایلر بررسی نمایید، متوجه خواهید شد 1 + 200 رفت و برگشت به دیتابیس صورت گرفته است که به "N+1 select problem" معروف است. 1 مرتبه جهت دریافت لیست مدرسههای شهر نیویورک و 200 مرتبه جهت دریافت تعداد دانش آموزان هر مدرسه.
بدلیل فعال بودن Lazy Loading، زمانیکه موجودیتی فراخوانی میشود، سایر موجودیتهای وابسته به آن، زمانی از دیتابیس فراخوانی خواهند شد که به آنها دسترسی پیدا کنید. در حلقهی foreach هم به ازای هر مدرسه (200 مدرسه) شهر نیویورک یک رفت و برگشت انجام میشود.
اما راه حل در این مورد خاص استفاده از Eager Loading است. خط دوم کد را بصورت زیر تغییر دهید:
List<School> schools = db.Schools .Where(s => s.City == city) .Include(x => x.Pupils) .ToList();
حال با یک رفت و برگشت، همراه هر مدرسه اطلاعات مربوط به دانش آموزان وابستهی آن نیز در دسترس خواهد بود.
-
تنها استفاده از ستونهای مورد نیاز
فرض کنید قصد دارید نام و نام خانوادگی دانش آموزان یک مدرسه را بدست آورید.
int schoolId = 1; List<Pupil> pupils = db.Pupils .Where(p => p.SchoolId == schoolId) .ToList(); foreach(var pupil in pupils) { textBox_Output.Text += pupil.FirstName + " " + pupil.LastName; textBox_Output.Text += Environment.NewLine; }
- انتقال اطلاعات بلا استفاده که ممکن است باعث کاهش کارآیی Sql Server I/O و شبکه و اشغال حافظهی کلاینت گردد.
- کاهش کارآیی ایندکس گذاری. فرض کنید برروی جدول دانش آموزان ایندکسی شامل 2 ستون نام و نام خانوادگی تعریف کردهاید. با انتخاب تمام ستونهای جدول توسط خط دوم (select * from...) به کارآیی ایندکس گذاری برروی این جدول آسیب زدهاید. توضیح بیشتر در اینجا مطرح شده است.
اما راه حل:
var pupils = db.Pupils .Where(p => p.SchoolId == schoolId) .Select(x => new { x.FirstName, x.LastName }) .ToList();
-
عدم تطابق نوع ستون با نوع خصیصه مدل
فرض کنید نوع ستون جدول دانش آموزان (VARCHAR(20 است و خصیصه کدپستی مدل دانش آموز مانند زیر تعریف شده است:
public string PostalZipCode { get; set; }
انتخاب نوع داده و تطابق نوع داده مدل با ستون جدول دارای اهمیت زیادی است و در صورت عدم رعایت، باعث کاهش کارآیی شدید میگردد. در کد زیر قصد دارید لیست نام و نام خانوادگی دانش آموزانی را که کدپستی آنها 90210 میباشد، بدست بیاورید.
string zipCode = "90210"; var pupils = db.Pupils .Where(p => p.PostalZipCode == zipCode) .Select(x => new {x.FirstName, x.LastName}) .ToList();
هنگامیکه کوئری بالا را اجرا نمایید، زمان زیادی جهت اجرای آن صرف خواهد شد. در صورتی که از یک پروفایلر استفاده نمایید، میتوانید عملیات پرهزینه را شناسایی نمایید و اقدام به کاهش هزینهها کنید.
همانطور که در شکل بالا مشخص است عملیات index scan از سایر عملیاتها پرهزینهتر است. حال به بررسی علت بهوجود آمدن این عملیات پرهزینه خواهیم پرداخت.
Index Scan زمانی رخ میدهد که اس کیو ال سرور مجبور است هر صفحهی از ایندکس را بخواند و شرایط را (کدپستی برابر 90210) اعمال نماید و نتیجه را برگرداند. Index Scan بسیار هزینه بر است، چون اس کیو ال سرور، کل ایندکس را بررسی مینماید. نقطهی مقابل و بهینهی آن، Index Seek است که اس کیو ال سرور به صفحهی مورد نظر ایندکسی که به شرایط نزدیکتر است، منتقل میگردد.
خب چرا اس کیو ال سرور Index Scan را بجای Index Seek انتخاب کرده است؟!
اشکالی در قسمت سمت چپ شکل بالا که به رنگ قرمز نمایش داده شده است، وجود دارد:
Type conversion: Seek Plan for CONVERT_IMPLICIT(nvarchar(20), [Extent1].[PostalZipCode],0)=[@p__linq__0]
پارامتر کوئری تولید شدهی توسط EF از نوع NVARCHAR است و تبدیل نوع NVARCHAR پارامتر کدپستی، که محدودهی اطلاعات بیشتری (Unicode Strings) را نسبت به نوع VARCHAR ستون دارد، بهدلیل از دست رفتن اطلاعات امکان پذیر نیست. بههمین جهت برای مقایسهی پارامتر کدپستی با ستون VARCHAR ، اس کیو ال سرور باید هر ردیف ایندکس را از VARCHAR به NVARCHAR تبدیل نماید که منجر به Index Scan میشود. اما راه حل بسیار ساده این است که فقط نوع خصیصه را با ستون جدول یکسان کنید.
[Column(TypeName = "varchar")] public string PostalZipCode { get; set; }
برای انتقال جداول احراز هویت (Identity) از SQL Server به بانک اطلاعاتی
MongoDB و نحوه استفاده از آن در ASP.Net Core از سورس نمونه در لینک بالا استفاده کنید.
همچنین میتوانید از پکیج AspNetCore.Identity.Mongo استفاده کنید.
البته همیشه درخواست کنترل این جدول واسط که کاملا از دیدگاه ORMها (تمام آنها) مخفی است، وجود داشتهاست و قرار است این پشتیبانی توسط مفهوم ویژهای به نام shadow properties به نگارشهای بعدی EF Core اضافه شود.
اما فعلا در نگارش اول آن، توصیه شدهاست که رابطهی many-to-many را به صورت دو رابطهی one-to-many مدلسازی کنید که در ادامه آنرا بررسی خواهیم کرد. بنابراین پیشنیاز آن مطالعهی مطلب «شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطهی One-to-Many» میباشد.
مدلسازی موجودیتهای یک رابطهی چند به چند در EF Core 1.0 RTM توسط Fluent API
در اینجا نحوهی مدلسازی یک رابطهی چند به چند را توسط دو رابطهی one-to-many مشاهده میکنید. تنها تفاوت آن با EF 6.x، قید صریح جدول واسط BlogPostsJoinTags است که یک چنین جدولی در EF 6.x به صورت خودکار تشکیل شده و مدیریت میشود و کاملا از دید برنامه مخفی است. اما در اینجا (در نگارش اول EF Core) نیاز است این جدول واسط را از حالت مخفی خارج کرد و سپس دو رابطهی یک به چند را به جداول مطالب و تگهای آنها تشکیل داد.
مزیت این حالت، دسترسی کامل به طراحی جدول واسط، توسط برنامه است. بنابراین اگر به هر دلیلی نیاز به افزودن خواص بیشتری به این جدول ویژه دارید، اکنون امکان آن میسر است.
public class Tags { public Tags() { BlogPostsJoinTags = new HashSet<BlogPostsJoinTags>(); } public int Id { get; set; } public string Name { get; set; } public virtual ICollection<BlogPostsJoinTags> BlogPostsJoinTags { get; set; } }
public class BlogPostsJoinTags { public virtual BlogPosts BlogPost { get; set; } public int BlogPostId { get; set; } public virtual Tags Tag { get; set; } public int TagId { get; set; } }
public class BlogPosts { public BlogPosts() { BlogPostsJoinTags = new HashSet<BlogPostsJoinTags>(); } public int Id { get; set; } public string Title { get; set; } public string Body { get; set; } public virtual ICollection<BlogPostsJoinTags> BlogPostsJoinTags { get; set; } }
به علاوه در اینجا تعریف یک composite key را هم بر روی خواص کلید خارجی جدول واسط مشاهده میکنید. وجود این کلید ترکیبی سبب خواهد شد که ملزم به ثبت هر دو Id (کلیدهای جداول مطلب و تگ) در حین ثبت در این جدول شویم (یا قید اجباری هر دو طرف رابطه).
public class MyDBDataContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=testdb2;Integrated Security = true"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<BlogPosts>(entity => { entity.Property(e => e.Title) .IsRequired() .HasMaxLength(450); }); modelBuilder.Entity<Tags>(entity => { entity.Property(e => e.Name) .IsRequired() .HasMaxLength(450); }); modelBuilder.Entity<BlogPostsJoinTags>(entity => { entity.HasKey(e => new { e.TagId, e.BlogPostId }) .HasName("PK_dbo.BlogPostsJoinTags"); entity.HasIndex(e => e.BlogPostId) .HasName("IX_BlogPostId"); entity.HasIndex(e => e.TagId) .HasName("IX_TagId"); entity.HasOne(d => d.BlogPost) .WithMany(p => p.BlogPostsJoinTags) .HasForeignKey(d => d.BlogPostId) .HasConstraintName("FK_dbo.BlogPostsJoinTags_dbo.BlogPosts_BlogPostId"); entity.HasOne(d => d.Tag) .WithMany(p => p.BlogPostsJoinTags) .HasForeignKey(d => d.TagId) .HasConstraintName("FK_dbo.BlogPostsJoinTags_dbo.Tags_TagId"); }); } public virtual DbSet<BlogPosts> BlogPosts { get; set; } public virtual DbSet<BlogPostsJoinTags> BlogPostsJoinTags { get; set; } public virtual DbSet<Tags> Tags { get; set; } }
مدلسازی موجودیتهای یک رابطهی چند به چند در EF Core 1.0 RTM توسط Data Annotations
در حالت مدلسازی توسط ویژگیها، ذکر InversePropertyها و همچنین ForeignKeyها مقداری واضحتر به نظر میرسند. به علاوه، یک سری از تنظیمات هم معادل data annotations ایی ندارند؛ مانند composite key تعریف شدهی بر روی خواص جدول واسط و همچنین ایندکسهای تعریف شده، که حتما باید توسط Fluent API تنظیم شوند.
public class Tags { public Tags() { BlogPostsJoinTags = new HashSet<BlogPostsJoinTags>(); } public int Id { get; set; } [Required] [MaxLength(450)] public string Name { get; set; } [InverseProperty("Tag")] public virtual ICollection<BlogPostsJoinTags> BlogPostsJoinTags { get; set; } }
public class BlogPostsJoinTags { [ForeignKey("BlogPostId")] [InverseProperty("BlogPostsJoinTags")] public virtual BlogPosts BlogPost { get; set; } public int BlogPostId { get; set; } [ForeignKey("TagId")] [InverseProperty("BlogPostsJoinTags")] public virtual Tags Tag { get; set; } public int TagId { get; set; } }
public class BlogPosts { public BlogPosts() { BlogPostsJoinTags = new HashSet<BlogPostsJoinTags>(); } public int Id { get; set; } [Required] [MaxLength(450)] public string Title { get; set; } public string Body { get; set; } [InverseProperty("BlogPost")] public virtual ICollection<BlogPostsJoinTags> BlogPostsJoinTags { get; set; } }
public class MyDBDataContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=testdb2;Integrated Security = true"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<BlogPostsJoinTags>(entity => { entity.HasKey(e => new { e.TagId, e.BlogPostId }) .HasName("PK_dbo.BlogPostsJoinTags"); entity.HasIndex(e => e.BlogPostId) .HasName("IX_BlogPostId"); entity.HasIndex(e => e.TagId) .HasName("IX_TagId"); }); } public virtual DbSet<BlogPosts> BlogPosts { get; set; } public virtual DbSet<BlogPostsJoinTags> BlogPostsJoinTags { get; set; } public virtual DbSet<Tags> Tags { get; set; } }
نحوهی ثبت اطلاعات در دو رابطهی یک به چند به همراه جدول واسط
در EF 6.x، کار مقدار دهی Idهای جدول واسط به صورت خودکار انجام میشود. در اینجا این مقدار دهی را باید به صورت صریح انجام داد:
var post = new BlogPosts { ... }; context.BlogPosts.Add(post); var tag = new Tags { ... }; context.Tags.Add(tag); var postTag = new BlogPostsJoinTags { Tag = tag, BlogPost = post }; context.PostsTags.Add(postTag); context.SaveChanges();
نحوهی واکشی اطلاعات به هم مرتبط در دو رابطهی یک به چند به همراه جدول واسط
در مورد متدهای Include و ThenInclude در مطلب «شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطهی One-to-Many» پیشتر بحث شد.
BlogPosts post1 = this.BlogPosts .Include(blogPosts => blogPosts.BlogPostsJoinTags) .ThenInclude(joinTags => joinTags.Tag) .First(blogPosts => blogPosts.Id == 1); IEnumerable<Tags> post1Tags = post1.BlogPostsJoinTags.Select(x => x.Tag);
یک نکتهی تکمیلی
وضعیت پشتیبانی از رابطهی many-to-many را همانند EF 6.x در EF Core، در اینجا میتوانید پیگیری کنید.