وبلاگ
آموزش Entity Framework Core در C#: ORM قدرتمند برای داتنت
فهرست مطالب
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان
0 تا 100 عطرسازی + (30 فرمولاسیون اختصاصی حامی صنعت)
دوره آموزش Flutter و برنامه نویسی Dart [پروژه محور]
دوره جامع آموزش برنامهنویسی پایتون + هک اخلاقی [با همکاری شاهک]
دوره جامع آموزش فرمولاسیون لوازم آرایشی
دوره جامع علم داده، یادگیری ماشین، یادگیری عمیق و NLP
دوره فوق فشرده مکالمه زبان انگلیسی (ویژه بزرگسالان)
شمع سازی و عودسازی با محوریت رایحه درمانی
صابون سازی (دستساز و صنعتی)
صفر تا صد طراحی دارو
متخصص طب سنتی و گیاهان دارویی
متخصص کنترل کیفی شرکت دارویی
مقدمه: در دنیای توسعه نرمافزار، مدیریت دادهها و تعامل با پایگاههای داده، یکی از هستههای اصلی هر برنامه کاربردی است. توسعهدهندگان همواره به دنبال راهحلهایی بودهاند که این فرآیند را سادهتر، کارآمدتر و قابل نگهداریتر کند. در اکوسیستم داتنت، چارچوبهایی مانند ADO.NET از دیرباز پایهای برای این تعاملات بودهاند. اما با پیچیدهتر شدن سیستمها و نیاز به انتزاع بیشتر، مفهوم ORM (Object-Relational Mapper) مطرح شد.
Entity Framework Core (EF Core) به عنوان نسل جدیدی از ORM محبوب مایکروسافت، تحولی بزرگ در نحوه کار توسعهدهندگان C# با پایگاههای داده ایجاد کرده است. این ORM قدرتمند، به توسعهدهندگان اجازه میدهد تا با استفاده از مدلهای دامنهای (Domain Models) و اشیاء C#، با پایگاه داده تعامل داشته باشند، بدون اینکه نیاز به نوشتن کوئریهای SQL به صورت مستقیم داشته باشند. EF Core وظیفه نگاشت اشیاء به جداول، مدیریت تغییرات، و اجرای دستورات SQL را به صورت خودکار بر عهده میگیرد.
این مقاله جامع با هدف آموزش Entity Framework Core در C# برای جامعه تخصصی برنامهنویسان داتنت طراحی شده است. ما از مفاهیم پایهای شروع کرده و به تدریج به مباحث پیشرفتهتر از جمله مدلسازی داده، کوئرینویسی پیشرفته، بهینهسازی عملکرد و بهترین شیوهها خواهیم پرداخت. هدف این است که خوانندگان پس از مطالعه این مطلب، درک عمیقی از EF Core پیدا کرده و بتوانند از آن به صورت مؤثر در پروژههای واقعی خود استفاده کنند.
مقدمهای بر Entity Framework Core: چرا ORM؟
قبل از اینکه به جزئیات EF Core بپردازیم، ضروری است که درک کنیم چرا یک ORM مانند Entity Framework Core تا این حد برای توسعه مدرن ضروری است. توسعهدهندگان به صورت سنتی برای تعامل با پایگاه داده، از ADO.NET و دستورات SQL استفاده میکردند. این رویکرد اگرچه کنترل بالایی را فراهم میآورد، اما با چالشهایی همراه بود:
- **عدم تطابق امپدانس (Impedance Mismatch):** دادهها در پایگاه داده به صورت جدولی (Relational) ذخیره میشوند، در حالی که در برنامههای شیگرا، با اشیاء و روابط بین آنها سروکار داریم. نگاشت دستی این دو ساختار به یکدیگر، کاری تکراری، مستعد خطا و زمانبر است.
- **کد تکراری (Boilerplate Code):** برای هر عملیات CRUD (ایجاد، خواندن، بهروزرسانی، حذف) نیاز به نوشتن دستورات SQL، باز کردن اتصال، اجرای دستور، خواندن نتایج و بستن اتصال بود. این حجم بالای کد تکراری، بهرهوری را کاهش میداد.
- **آسیبپذیریهای امنیتی:** نگارش دستی SQL، مخصوصاً در ورودیهای کاربر، میتواند منجر به آسیبپذیریهای SQL Injection شود.
- **وابستگی به پایگاه داده:** تغییر نوع پایگاه داده (مثلاً از SQL Server به PostgreSQL) اغلب مستلزم بازنویسی بخش قابل توجهی از کدهای دسترسی به داده بود.
یک ORM مانند Entity Framework Core این چالشها را با ایجاد یک لایه انتزاعی بین برنامه و پایگاه داده حل میکند. این لایه به توسعهدهندگان اجازه میدهد تا با استفاده از اشیاء C# خود (که به آنها Entity گفته میشود)، دادهها را ذخیره، بازیابی و بهروزرسانی کنند. EF Core مسئول ترجمه عملیات روی اشیاء به دستورات SQL مناسب و اجرای آنها در پایگاه داده است. این امر باعث افزایش بهرهوری، کاهش کدنویسی تکراری، و قابلیت نگهداری بهتر کد میشود.
تکامل Entity Framework: از Classic تا Core
Entity Framework تاریخی طولانی در اکوسیستم داتنت دارد. نسخه اولیه آن در سال 2008 به عنوان بخشی از .NET Framework 3.5 SP1 منتشر شد. با گذشت زمان و رسیدن به EF6، قابلیتهای آن به بلوغ قابل توجهی رسید. اما با ظهور داتنت کور (Dotnet Core)، نیاز به یک ORM Cross-Platform و سبکتر احساس شد. اینجاست که Entity Framework Core پا به میدان گذاشت.
EF Core با بازطراحی کامل، به یک فریمورک سبک، ماژولار و قابل توسعه تبدیل شد که از ابتدا برای کار با داتنت کور (و بعدها داتنت 5، 6، 7 و بالاتر) طراحی شده بود. تفاوتهای کلیدی EF Core با EF6 شامل موارد زیر است:
- **Cross-Platform:** EF Core میتواند روی ویندوز، لینوکس و macOS اجرا شود.
- **عملکرد بهبود یافته:** با بهینهسازیهای داخلی، EF Core معمولاً عملکرد بهتری نسبت به EF6 ارائه میدهد.
- **ماژولار بودن:** EF Core از بستههای NuGet کوچکتر و متمرکزتر تشکیل شده که امکان انتخاب قابلیتهای مورد نیاز را فراهم میکند.
- **پشتیبانی از الگوهای توسعه مدرن:** هماهنگی بهتر با Dependency Injection و تستپذیری بهبود یافته.
- **قابلیتهای جدید:** اضافه شدن قابلیتهایی مانند Global Query Filters، Owned Entities، Interceptors و غیره.
در حال حاضر، EF Core انتخاب استاندارد برای توسعه برنامههای کاربردی داتنت است که نیاز به تعامل با پایگاه دادههای رابطهای دارند.
شروع به کار با Entity Framework Core در C#
برای شروع کار با EF Core، ابتدا نیاز به نصب بستههای NuGet مربوطه دارید. سپس باید مدل دادهای خود (اشیاء C#) را تعریف کرده و یک کلاس DbContext برای مدیریت تعاملات با پایگاه داده ایجاد کنید.
نصب Entity Framework Core
EF Core به صورت بستههای NuGet در دسترس است. حداقل دو بسته اصلی مورد نیاز است:
Microsoft.EntityFrameworkCore
: شامل هسته EF Core.- بسته فراهمکننده پایگاه داده (Database Provider): این بسته به EF Core میگوید که چگونه با یک نوع خاص از پایگاه داده (مثلاً SQL Server، PostgreSQL، MySQL، SQLite) ارتباط برقرار کند.
به عنوان مثال، برای SQL Server، بستههای زیر را باید نصب کنید:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Tools
بسته Microsoft.EntityFrameworkCore.Tools
برای ابزارهای خط فرمان (CLI) EF Core مانند مدیریت Migrationها ضروری است.
رویکرد Code-First در EF Core
EF Core از دو رویکرد اصلی پشتیبانی میکند: Code-First و Database-First. در رویکرد Code-First، شما ابتدا مدلهای دامنهای خود را در C# تعریف میکنید و EF Core بر اساس این مدلها، ساختار پایگاه داده را برای شما ایجاد یا بهروزرسانی میکند. این رویکرد به دلیل مزایایی مانند کنترل کامل بر کد و امکان استفاده از Git برای مدیریت تغییرات شمای پایگاه داده، بسیار محبوب است. در این مقاله ما بر رویکرد Code-First تمرکز خواهیم کرد.
تعریف مدلهای (Entities)
Entities یا موجودیتها، کلاسهای POCO (Plain Old CLR Objects) هستند که نماینده دادهها در دامنهی برنامه و جداول در پایگاه دادهاند. هر ویژگی (Property) در کلاس معمولاً به یک ستون در جدول نگاشت میشود.
public class Blog { public int BlogId { get; set; } public string Name { get; set; } public string Url { get; set; } public ICollection<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public int BlogId { get; set; } public Blog Blog { get; set; } }
در این مثال، BlogId
و PostId
به عنوان کلیدهای اصلی (Primary Key) شناسایی میشوند (به دلیل قرارداد نامگذاری EF Core). BlogId
در کلاس Post
یک کلید خارجی (Foreign Key) است که به Blog
مربوط میشود، و Blog
یک ویژگی ناوبری (Navigation Property) است که نشاندهنده رابطه یک به چند بین Blog
و Post
است.
ایجاد کلاس DbContext
DbContext
هسته EF Core است. این کلاس نماینده یک جلسه با پایگاه داده است و امکان کوئرینویسی، ردیابی تغییرات و ذخیره دادهها را فراهم میکند. هر کلاس DbSet<TEntity>
در DbContext
به یک جدول در پایگاه داده نگاشت میشود.
public class MyDbContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { // Fluent API configurations can go here modelBuilder.Entity<Blog>() .HasMany(b => b.Posts) .WithOne(p => p.Blog) .HasForeignKey(p => p.BlogId); } }
سازنده DbContext
با پارامتر DbContextOptions<T>
امکان پیکربندی پایگاه داده (مانند رشته اتصال) را فراهم میکند. متد OnModelCreating
برای پیکربندی پیشرفته مدل با استفاده از Fluent API استفاده میشود.
پیکربندی DbContext و رشته اتصال
رشته اتصال پایگاه داده و نوع فراهمکننده پایگاه داده معمولاً در متد ConfigureServices
در فایل Startup.cs
(برای ASP.NET Core) یا در فایل Program.cs
(برای داتنت 6 به بعد) پیکربندی میشوند.
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<MyDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); }
رشته اتصال DefaultConnection
باید در فایل appsettings.json
تعریف شود:
{ "ConnectionStrings": { "DefaultConnection": "Server=localhost;Database=MyDatabase;Trusted_Connection=True;MultipleActiveResultSets=true" } }
Migrations: مدیریت تغییرات شمای پایگاه داده
Migrations ابزار قدرتمند EF Core برای مدیریت تکامل شمای پایگاه داده با تغییرات مدل Code-First است. با هر تغییر در مدلهای C#، میتوانید یک Migration جدید ایجاد کنید که تغییرات لازم را برای بهروزرسانی پایگاه داده شما تعریف میکند.
برای ایجاد اولین Migration:
dotnet ef migrations add InitialCreate
این دستور یک فایل Migration در پوشه Migrations
ایجاد میکند که شامل متدهای Up
(برای اعمال تغییرات) و Down
(برای بازگرداندن تغییرات) است.
برای اعمال Migration به پایگاه داده:
dotnet ef database update
این دستور پایگاه داده را ایجاد کرده یا بهروزرسانی میکند تا با مدل شما همگام شود.
مفاهیم اصلی در Entity Framework Core
برای استفاده موثر از EF Core، درک عمیق از مفاهیم اصلی آن حیاتی است.
Entities و نگاشت
همانطور که قبلاً اشاره شد، Entities کلاسهای POCO (Plain Old CLR Objects) هستند که نماینده دادهها در دامنهی برنامه و جداول در پایگاه دادهاند. EF Core از طریق یک سری قراردادها (Conventions) سعی میکند به صورت خودکار کلاسها و ویژگیها را به جداول و ستونها نگاشت کند. به عنوان مثال:
- **نام جدول:** نام کلاس به صورت پیشفرض نام جدول میشود (مثلاً کلاس
Blog
به جدولBlogs
). - **کلید اصلی:** ویژگی با نام
Id
یا<ClassName>Id
به عنوان کلید اصلی شناسایی میشود (مثلاًBlogId
). - **نوع داده:** نوع داده ویژگی C# به مناسبترین نوع داده در پایگاه داده نگاشت میشود (مثلاً
string
بهnvarchar(max)
،int
بهint
).
پیکربندی با Data Annotations و Fluent API
زمانی که قراردادها کافی نیستند یا نیاز به سفارشیسازی بیشتری دارید، EF Core دو راه برای پیکربندی نگاشتها ارائه میدهد:
- **Data Annotations:** ویژگیهای (Attributes) C# که به کلاسها یا ویژگیها اضافه میشوند. سریع و ساده برای سناریوهای معمول.
public class Blog { [Key] public int BlogId { get; set; } [Required] [MaxLength(200)] public string Name { get; set; } [Column("BlogUrl")] public string Url { get; set; } }
OnModelCreating
از DbContext
. قدرتمندتر و انعطافپذیرتر برای سناریوهای پیچیده، مانند روابط Many-to-Many، انواع خاص ستونها، و فیلترهای کوئری گلوبال.protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .ToTable("BlogsData") // Custom table name .HasKey(b => b.BlogId); modelBuilder.Entity<Blog>() .Property(b => b.Name) .IsRequired() .HasMaxLength(200); modelBuilder.Entity<Blog>() .Property(b => b.Url) .HasColumnName("BlogUrl"); }
DbContext و Cycle Life آن
DbContext
یک واحد کار (Unit of Work) و یک ردیاب تغییرات (Change Tracker) است. هر نمونه از DbContext
به گونهای طراحی شده است که عمر کوتاهی داشته باشد (Short-Lived). توصیه میشود که یک نمونه DbContext
برای هر عملیات تجاری یا هر درخواست وب (HTTP Request) ایجاد کرده و پس از اتمام کار، آن را dispose کنید. این کار از مشکلات مربوط به ردیابی تغییرات و مصرف حافظه جلوگیری میکند.
Change Tracking در EF Core
یکی از قویترین ویژگیهای EF Core، سیستم ردیابی تغییرات آن است. هنگامی که Entities را از پایگاه داده بازیابی میکنید، EF Core وضعیت آنها را در DbContext
ردیابی میکند. وقتی ویژگیهای Entities را تغییر میدهید، EF Core این تغییرات را تشخیص میدهد و هنگام فراخوانی SaveChanges()
، فقط دستورات SQL لازم برای بهروزرسانی این تغییرات را اجرا میکند. این سیستم شامل حالات زیر برای Entityها است:
- **Added:** Entity جدید که هنوز به پایگاه داده ذخیره نشده است.
- **Deleted:** Entity که قرار است از پایگاه داده حذف شود.
- **Modified:** Entity که برخی از ویژگیهای آن تغییر کرده است.
- **Unchanged:** Entity که از پایگاه داده بازیابی شده و تغییری نکرده است.
- **Detached:** Entity که توسط
DbContext
ردیابی نمیشود.
ذخیره تغییرات: SaveChanges() و SaveChangesAsync()
پس از انجام تغییرات روی Entities (افزودن، بهروزرسانی، حذف)، باید متد SaveChanges()
یا SaveChangesAsync()
را فراخوانی کنید تا این تغییرات به پایگاه داده اعمال شوند. SaveChangesAsync()
نسخه ناهمگام (Asynchronous) است و در برنامههای وب (ASP.NET Core) برای بهبود مقیاسپذیری و جلوگیری از مسدود شدن Threadها توصیه میشود.
using (var context = new MyDbContext(options)) { var blog = new Blog { Name = "My New Blog", Url = "http://mynewblog.com" }; context.Blogs.Add(blog); await context.SaveChangesAsync(); // Add the new blog var existingBlog = await context.Blogs.FirstOrDefaultAsync(b => b.Name == "My New Blog"); existingBlog.Url = "http://updatedblog.com"; await context.SaveChangesAsync(); // Update the existing blog context.Blogs.Remove(existingBlog); await context.SaveChangesAsync(); // Delete the blog }
روابط بین Entities
مدلسازی روابط بین جداول یکی از وظایف کلیدی در کار با پایگاه داده است. EF Core از انواع مختلفی از روابط پشتیبانی میکند:
- **یک به یک (One-to-One):** یک موجودیت به یک موجودیت دیگر مرتبط است و بالعکس (مثلاً
کاربر
بهپروفایل کاربر
). - **یک به چند (One-to-Many):** یک موجودیت به چندین موجودیت دیگر مرتبط است، اما هر یک از موجودیتهای دیگر فقط به یک موجودیت از نوع اول مرتبط است (مثلاً
Blog
بهPost
). - **چند به چند (Many-to-Many):** هر موجودیت میتواند به چندین موجودیت از نوع دیگر مرتبط باشد و بالعکس. در EF Core 5 و بالاتر، این روابط به صورت خودکار از طریق یک جدول واسط (Join Table) ایجاد میشوند (مثلاً
Student
بهCourse
). در نسخههای قبلی، نیاز به تعریف صریح جدول واسط داشتید.
پیکربندی این روابط میتواند از طریق Data Annotations یا Fluent API انجام شود. Fluent API کنترل بیشتری را برای نامگذاری کلیدهای خارجی، شاخصها و حذف Cascade فراهم میکند.
کوئرینویسی داده با Entity Framework Core
مهمترین بخش هر ORM، قابلیت کوئرینویسی آن است. EF Core از LINQ (Language Integrated Query) برای ساخت کوئریهای قدرتمند و Type-Safe استفاده میکند.
LINQ to Entities: قدرت کوئری شیگرا
LINQ به توسعهدهندگان C# اجازه میدهد تا کوئریها را با استفاده از نحو C# بنویسند، بدون اینکه نیاز به یادگیری SQL داشته باشند. EF Core این کوئریهای LINQ را به دستورات SQL مناسب ترجمه کرده و آنها را روی پایگاه داده اجرا میکند.
کوئریهای ساده
// Get all blogs var allBlogs = await context.Blogs.ToListAsync(); // Get a blog by ID var blogById = await context.Blogs.FindAsync(1); // Get blogs with a specific name var programmingBlogs = await context.Blogs .Where(b => b.Name.Contains("Programming")) .OrderBy(b => b.Name) .ToListAsync(); // Select specific properties (projection) var blogNames = await context.Blogs .Select(b => b.Name) .ToListAsync(); // Select into an anonymous type var blogInfo = await context.Blogs .Select(b => new { b.BlogId, b.Name, PostCount = b.Posts.Count() }) .ToListAsync();
ToListAsync()
و FirstOrDefaultAsync()
و FindAsync()
متدهای ناهمگام هستند که اجرای کوئری را به پایگاه داده انجام میدهند. تا زمانی که این متدها یا متدهای مشابه (مانند Count()
, ToArray()
, First()
) فراخوانی نشوند، کوئری فقط در حافظه ساخته شده و به پایگاه داده ارسال نمیشود (Lazy Execution).
بارگذاری دادههای مرتبط (Related Data)
یکی از چالشهای رایج در ORMها، مدیریت بارگذاری دادههای مرتبط است. EF Core سه استراتژی اصلی برای این کار ارائه میدهد:
-
**Eager Loading (بارگذاری مشتاقانه):** با استفاده از متد
Include()
، دادههای مرتبط در همان کوئری اصلی بارگذاری میشوند. این روش معمولاً بهترین عملکرد را دارد زیرا از مشکل N+1 Query جلوگیری میکند.// Load blogs and their posts in a single query var blogsWithPosts = await context.Blogs .Include(b => b.Posts) .ToListAsync(); // Load multiple levels of related data var blogsWithPostsAndComments = await context.Blogs .Include(b => b.Posts) .ThenInclude(p => p.Comments) .ToListAsync();
-
**Explicit Loading (بارگذاری صریح):** دادههای مرتبط به صورت جداگانه و پس از بارگذاری موجودیت اصلی بارگذاری میشوند. این روش زمانی مفید است که شما نیاز به بارگذاری دادههای مرتبط در شرایط خاصی دارید.
var blog = await context.Blogs.SingleOrDefaultAsync(b => b.BlogId == 1); if (blog != null) { await context.Entry(blog).Collection(b => b.Posts).LoadAsync(); // Load all posts for this blog // Or to load just one specific post // await context.Entry(blog).Collection(b => b.Posts).Query().Where(p => p.Title.Contains("EF Core")).LoadAsync(); }
-
**Lazy Loading (بارگذاری تنبل):** دادههای مرتبط به صورت خودکار زمانی که اولین بار به آنها دسترسی پیدا میکنید، بارگذاری میشوند. این روش نیاز به تعریف ویژگیهای ناوبری به صورت
virtual
و نصب بستهMicrosoft.EntityFrameworkCore.Proxies
دارد. اگرچه راحت است، اما میتواند منجر به مشکل N+1 Query شود، به خصوص در حلقهها، که عملکرد را به شدت تحت تأثیر قرار میدهد. استفاده از آن باید با احتیاط فراوان صورت گیرد.// In DbContextOptions configuration options.UseLazyLoadingProxies(); // In entity class public virtual ICollection<Post> Posts { get; set; } // Accessing posts will automatically load them var blog = await context.Blogs.FirstAsync(); foreach (var post in blog.Posts) // Posts will be loaded here { Console.WriteLine(post.Title); }
کوئریهای No-Tracking
به صورت پیشفرض، EF Core اشیاء بازیابی شده را ردیابی میکند تا تغییرات آنها را تشخیص دهد. اگر فقط قصد خواندن دادهها را دارید و نمیخواهید آنها را بهروزرسانی کنید، میتوانید از کوئریهای No-Tracking استفاده کنید. این کار سربار ردیابی تغییرات را حذف کرده و میتواند عملکرد را در سناریوهای خواندن افزایش دهد.
var blogs = await context.Blogs.AsNoTracking().ToListAsync();
کوئریهای Raw SQL
گاهی اوقات نیاز دارید کوئریهای SQL خام را مستقیماً اجرا کنید. EF Core این امکان را فراهم میکند.
- برای بازیابی Entities:
var blogs = await context.Blogs.FromSqlRaw("SELECT * FROM Blogs WHERE Name LIKE '{0}%'", "Pro").ToListAsync();
- برای اجرای دستورات غیر-کوئری (Insert, Update, Delete):
await context.Database.ExecuteSqlRawAsync("UPDATE Blogs SET Url = 'http://new.com' WHERE Name = 'Old Blog'");
ویژگیهای پیشرفته Entity Framework Core
EF Core فراتر از CRUD و کوئرینویسی ساده، قابلیتهای پیشرفتهای را برای سناریوهای پیچیدهتر ارائه میدهد.
مدیریت همزمانی (Concurrency Handling)
در برنامههای چندکاربره، ممکن است دو کاربر به طور همزمان سعی در تغییر یک رکورد داشته باشند. EF Core از همزمانی خوشبینانه (Optimistic Concurrency) پشتیبانی میکند. در این رویکرد، هیچ قفلی در پایگاه داده اعمال نمیشود. در عوض، هنگامی که یک بهروزرسانی یا حذف انجام میشود، EF Core بررسی میکند که آیا رکورد از زمان خوانده شدن اولیه توسط کاربر، توسط کاربر دیگری تغییر کرده است یا خیر. اگر تغییر کرده باشد، یک DbUpdateConcurrencyException
رخ میدهد.
برای فعال کردن همزمانی خوشبینانه، باید یک ویژگی (Column) به عنوان “Concurrency Token” در مدل خود تعریف کنید. معمولاً از rowversion
(در SQL Server) یا یک ویژگی timestamp
استفاده میشود.
public class Blog { public int BlogId { get; set; } public string Name { get; set; } public string Url { get; set; } [Timestamp] // Data Annotation for concurrency token public byte[] Timestamp { get; set; } } // Or using Fluent API protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Timestamp) .IsConcurrencyToken(); }
هنگام بروز خطا، میتوانید استراتژیهای مختلفی برای حل آن (مانند اطلاع به کاربر، بازخوانی دادهها و تلاش مجدد) پیادهسازی کنید.
تراکنشها (Transactions)
تراکنشها اطمینان حاصل میکنند که مجموعهای از عملیات پایگاه داده به صورت اتمیک (Atomic) انجام شوند؛ یعنی یا همه آنها با موفقیت انجام شوند یا هیچ یک از آنها. EF Core به صورت داخلی از تراکنشها برای SaveChanges()
استفاده میکند. همچنین میتوانید تراکنشهای خود را به صورت دستی مدیریت کنید:
using (var transaction = await context.Database.BeginTransactionAsync()) { try { // Perform multiple operations context.Blogs.Add(new Blog { Name = "Blog 1" }); await context.SaveChangesAsync(); context.Posts.Add(new Post { Title = "Post 1", BlogId = 1 }); await context.SaveChangesAsync(); await transaction.CommitAsync(); } catch (Exception) { await transaction.RollbackAsync(); } }
وراثت (Inheritance)
EF Core از نگاشت سلسله مراتب وراثت در C# به پایگاه داده پشتیبانی میکند. سه استراتژی اصلی برای نگاشت وراثت وجود دارد:
- **Table-Per-Hierarchy (TPH):** تمام کلاسهای سلسله مراتب در یک جدول واحد ذخیره میشوند. یک ستون “Discriminator” برای شناسایی نوع موجودیت اضافه میشود. این سادهترین و معمولاً کارآمدترین استراتژی است.
- **Table-Per-Type (TPT):** هر کلاس در سلسله مراتب به یک جدول جداگانه نگاشت میشود. جداول فرزند از طریق کلیدهای خارجی با جدول والد مرتبط میشوند. این روش دادههای تکراری کمتری دارد اما کوئریهای پیچیدهتری ایجاد میکند.
- **Table-Per-Concrete-Type (TPC):** هر کلاس کانکریت (غیر انتزاعی) در سلسله مراتب به یک جدول جداگانه نگاشت میشود. جداول شامل ستونهای خود و ستونهای کلاسهای والد (به صورت تکراری) هستند. این روش میتواند کوئریها را سادهتر کند اما دادههای تکراری زیادی ایجاد میکند. (معرفی شده در EF Core 7)
Owned Entities
Owned Entities به شما اجازه میدهند تا انواع پیچیده را در مدل خود بدون نیاز به نگاشت آنها به جداول جداگانه، تعریف کنید. آنها به عنوان بخشی از موجودیت والد خود ذخیره میشوند (معمولاً در همان جدول). این برای مدلسازی اشیاء ارزش (Value Objects) مانند آدرسها یا اشیاء تاریخگذاری مفید است.
public class Order { public int OrderId { get; set; } public ShippingAddress ShippingAddress { get; set; } } public class ShippingAddress // Not a DbSet, it's an Owned Entity { public string Street { get; set; } public string City { get; set; } } // In DbContext OnModelCreating modelBuilder.Entity<Order>().OwnsOne(o => o.ShippingAddress);
Shadow Properties
Shadow Properties، ویژگیهایی هستند که بخشی از کلاس CLR شما نیستند، اما بخشی از مدل EF Core هستند و در پایگاه داده نگاشت میشوند. آنها برای دادههایی که نمیخواهید در شیء دامنهای شما نمایش داده شوند اما برای عملیات پایگاه داده ضروری هستند، مفیدند (مثلاً LastUpdatedBy
یا CreatedDate
که به صورت خودکار توسط EF Core پر میشوند).
modelBuilder.Entity<Blog>() .Property<DateTime>("LastUpdated"); // Define a shadow property
میتوانید به این ویژگیها از طریق APIهای EF Core دسترسی پیدا کنید: context.Entry(entity).Property("PropertyName").CurrentValue
.
Value Converters
Value Converters به شما اجازه میدهند تا مقادیر بین انواع CLR و انواع پایگاه داده را تبدیل کنید. این برای ذخیره Enumها به عنوان رشته، یا ذخیره لیستی از رشتهها به عنوان یک رشته جدا شده با کاما در پایگاه داده مفید است.
public enum Status { Pending, Approved, Rejected } public class Document { public int Id { get; set; } public Status CurrentStatus { get; set; } } // In DbContext OnModelCreating modelBuilder.Entity<Document>() .Property(e => e.CurrentStatus) .HasConversion<string>(); // Convert enum to string in DB
Global Query Filters
Global Query Filters به شما اجازه میدهند تا شرطهای LINQ را به صورت گلوبال برای انواع Entity خاص تعریف کنید. این فیلترها به طور خودکار به هر کوئری که شامل آن Entity باشد، اعمال میشوند. این برای پیادهسازی Soft Delete یا فیلترهای چند مستاجری (Multi-Tenancy) بسیار مفید است.
public class TenantSpecificEntity { public int Id { get; set; } public string Name { get; set; } public string TenantId { get; set; } // For multi-tenancy public bool IsDeleted { get; set; } // For soft delete } // In DbContext OnModelCreating modelBuilder.Entity<TenantSpecificEntity>() .HasQueryFilter(e => e.TenantId == "YourCurrentTenantId" && !e.IsDeleted);
توجه داشته باشید که فیلترهای گلوبال با IgnoreQueryFilters()
قابل غیرفعال کردن هستند.
Database Seeding
Database Seeding فرآیند پر کردن پایگاه داده با دادههای اولیه (مانند دادههای پیشفرض، تنظیمات، یا دادههای تستی) است. EF Core از Seeding به عنوان بخشی از Migrationها پشتیبانی میکند:
// In DbContext OnModelCreating modelBuilder.Entity<Blog>().HasData( new Blog { BlogId = 1, Name = "Programming Blog", Url = "http://programming.com" }, new Blog { BlogId = 2, Name = "Lifestyle Blog", Url = "http://lifestyle.com" } );
هر بار که Migrationها را اعمال میکنید، دادههای Seeding نیز بررسی و اعمال میشوند.
Interceptors
Interceptors به شما اجازه میدهند تا منطق سفارشی را قبل یا بعد از عملیاتهای خاص EF Core (مانند ذخیره تغییرات، باز کردن اتصال، اجرای کوئریها) تزریق کنید. این برای سناریوهایی مانند لاگبرداری، auditing، یا تغییر رفتار پیشفرض EF Core بسیار مفید است.
public class MyCommandInterceptor : IDbCommandInterceptor { public InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result) { Console.WriteLine($"Executing command: {command.CommandText}"); return result; } // ... other methods } // In DbContextOptions configuration options.AddInterceptors(new MyCommandInterceptor());
بهینهسازی عملکرد در Entity Framework Core
عملکرد پایگاه داده اغلب گلوگاه برنامههای کاربردی است. بهینهسازی EF Core برای اطمینان از عملکرد بالا در برنامههای C# حیاتی است.
انتخاب استراتژی بارگذاری صحیح دادههای مرتبط
- **Eager Loading (با
Include
/ThenInclude
):** معمولاً بهترین انتخاب برای بارگذاری دادههای مرتبط است، زیرا از مشکل N+1 query جلوگیری میکند. مطمئن شوید کهInclude
های غیرضروری اضافه نکنید، زیرا این کار میتواند منجر به تولید کوئریهای SQL بسیار بزرگ و بازیابی دادههای زیاد شود. - **No-Tracking Queries (با
AsNoTracking()
):** برای سناریوهای فقط خواندن، استفاده ازAsNoTracking()
ضروری است. این کار سربار ردیابی تغییرات را حذف کرده و منجر به کوئریهای سریعتر میشود. - **Projections:** اگر فقط به زیرمجموعهای از ستونها نیاز دارید، از
Select()
برای Projection به یک نوع ناشناس یا یک DTO (Data Transfer Object) استفاده کنید. این کار فقط ستونهای مورد نیاز را از پایگاه داده بازیابی میکند.
var postTitles = await context.Posts.Select(p => p.Title).ToListAsync();
SaveChanges()
را فراخوانی کنید.var postsToUpdate = await context.Posts.Where(p => p.Title.Contains("Old")).ToListAsync(); foreach (var post in postsToUpdate) { post.Title = post.Title.Replace("Old", "New"); } await context.SaveChangesAsync(); // All updates sent in one batch (or fewer)
فیلتر کردن و صفحهبندی کارآمد
همیشه قبل از بارگذاری دادهها در حافظه، فیلتر و صفحهبندی را روی پایگاه داده انجام دهید. متدهایی مانند Where()
، OrderBy()
، Skip()
، و Take()
به دستورات SQL ترجمه میشوند و عملیات را در سمت سرور انجام میدهند.
var paginatedPosts = await context.Posts .Where(p => p.BlogId == blogId) .OrderByDescending(p => p.PostId) .Skip(pageNumber * pageSize) .Take(pageSize) .ToListAsync();
ایندکسگذاری (Indexing)
مطمئن شوید که ستونهایی که در کوئریها (WHERE
، ORDER BY
، JOIN
) زیاد استفاده میشوند، ایندکس مناسبی در پایگاه داده دارند. EF Core میتواند ایندکسها را از طریق Fluent API یا Data Annotations تعریف کند. ایندکسها سرعت بازیابی دادهها را به شدت افزایش میدهند.
// Using Data Annotation public class Blog { public int BlogId { get; set; } [Index] // EF Core 6+ public string Name { get; set; } public string Url { get; set; } } // Using Fluent API modelBuilder.Entity<Blog>() .HasIndex(b => b.Name) .IsUnique(); // For unique index
مانیتورینگ و پروفایلسازی کوئریها
برای تشخیص مشکلات عملکرد، مهم است که کوئریهای SQL تولید شده توسط EF Core را مشاهده کنید. این کار میتواند با استفاده از Logging در ASP.NET Core، یا با استفاده از متد ToQueryString()
در LINQ انجام شود.
var query = context.Blogs.Where(b => b.Name.Contains("Programming")); Console.WriteLine(query.ToQueryString()); // See the generated SQL
ابزارهای پروفایلسازی پایگاه داده (مانند SQL Server Profiler) نیز میتوانند برای تحلیل عملکرد کوئریها مفید باشند.
کاهش Round Trips به پایگاه داده
هر تعامل با پایگاه داده (حتی خواندن یک رکورد) شامل یک Round Trip به سرور پایگاه داده است که هزینهبر است. سعی کنید تعداد Round Tripها را به حداقل برسانید:
- دادهها را در کوئریهای بزرگتر و جامعتر بارگذاری کنید (با احتیاط برای جلوگیری از بارگذاری بیش از حد).
- از
SaveChanges()
برای گروه بندی تغییرات استفاده کنید. - از عملیات Bulk Insert/Update/Delete (با استفاده از کتابخانههای جانبی مانند EF Core.BulkExtensions) برای حجم بالای دادهها استفاده کنید، زیرا EF Core به صورت داخلی از آنها پشتیبانی نمیکند.
تستپذیری برنامههای Entity Framework Core
نوشتن تستهای واحد (Unit Tests) و تستهای یکپارچهسازی (Integration Tests) برای کدهای دسترسی به داده مهم است. EF Core قابلیتهای مختلفی برای تسهیل تستپذیری فراهم میکند.
تست با پایگاه داده در حافظه (In-Memory Database)
EF Core یک فراهمکننده پایگاه داده در حافظه (Microsoft.EntityFrameworkCore.InMemory
) ارائه میدهد که برای تستهای واحد سریع مناسب است. این پایگاه داده در حافظه اجرا میشود و برای هر تست کاملاً ایزوله و پاک میشود.
// In your test setup var options = new DbContextOptionsBuilder<MyDbContext>() .UseInMemoryDatabase(databaseName: "TestDatabase") .Options; using (var context = new MyDbContext(options)) { // Arrange context.Blogs.Add(new Blog { Name = "Test Blog" }); await context.SaveChangesAsync(); // Act var blog = await context.Blogs.FirstAsync(); // Assert Assert.Equal("Test Blog", blog.Name); }
محدودیت اصلی این است که پایگاه داده در حافظه، رفتار پایگاه داده رابطهای واقعی را به طور کامل شبیهسازی نمیکند (مثلاً کوئریهای پیچیده، تراکنشها، یا محدودیتهای SQL خاص).
تست با SQLite in-memory
برای تستهای واحد که به شباهت بیشتری با پایگاه داده واقعی نیاز دارند، میتوانید از فراهمکننده SQLite در حافظه استفاده کنید. SQLite یک پایگاه داده رابطهای واقعی است که میتواند به صورت کاملاً در حافظه اجرا شود و از بسیاری از قابلیتهای SQL پشتیبانی میکند.
// In your test setup var connection = new SqliteConnection("DataSource=:memory:"); connection.Open(); // Important to keep connection open for in-memory DB var options = new DbContextOptionsBuilder<MyDbContext>() .UseSqlite(connection) .Options; using (var context = new MyDbContext(options)) { context.Database.EnsureCreated(); // Create schema // ... then write your test logic }
Mocking DbContext و DbSet
در برخی از تستهای واحد، ممکن است بخواهید DbContext
یا DbSet
را Mock کنید تا کاملاً از پایگاه داده جدا شوید. این رویکرد برای تست منطق تجاری که فقط از توابع IQueryable
روی DbSet
استفاده میکند، مناسب است. کتابخانههای Mocking مانند Moq میتوانند برای این کار استفاده شوند.
این روش برای تست عملکردهای پایگاه داده و ترجمه LINQ به SQL مناسب نیست.
تستهای یکپارچهسازی با پایگاه داده واقعی
برای اطمینان کامل از صحت کارکرد لایه دسترسی به داده، تستهای یکپارچهسازی با پایگاه داده واقعی ضروری هستند. این تستها پایگاه داده را با دادههای تستی پر کرده، عملیات را اجرا کرده و سپس پایگاه داده را تمیز میکنند. میتوان از Docker برای راهاندازی یک نمونه پایگاه داده تمیز برای هر اجرا استفاده کرد.
بهترین شیوهها و دامهای رایج در EF Core
برای توسعه برنامههای پایدار و با کارایی بالا با EF Core، رعایت بهترین شیوهها و آگاهی از دامهای رایج اهمیت دارد.
الگوی Unit of Work و Repository Pattern
EF Core به خودی خود پیادهسازی الگوی Unit of Work (از طریق DbContext
) و بخشی از Repository Pattern (از طریق DbSet<T>
) است. استفاده از DbContext
به عنوان Unit of Work (یک تراکنش اتمیک از عملیاتها) و تزریق آن از طریق Dependency Injection، یک رویکرد استاندارد و مناسب است.
در مورد پیادهسازی Repository Pattern جداگانه (برای انتزاع بیشتر)، دیدگاههای مختلفی وجود دارد. برای پروژههای کوچک و متوسط، اغلب نیازی به لایه Repository اضافی نیست، زیرا DbSet<T>
خود یک Repository ساده را فراهم میکند. اما در پروژههای بزرگ با دامنههای پیچیده یا نیاز به تستپذیری پیشرفتهتر، یک لایه Repository سفارشی میتواند مفید باشد. اگر Repository Pattern را پیادهسازی میکنید، مطمئن شوید که IQueryable<T>
را از Repository خود برمیگردانید تا قابلیت کوئرینویسی LINQ را از دست ندهید.
مدیریت چرخه حیات DbContext با Dependency Injection
همانطور که قبلاً اشاره شد، DbContext
باید کوتاه مدت باشد. در ASP.NET Core، بهترین روش ثبت DbContext
با Lifetime از نوع Scoped
در Dependency Injection است. این به این معنی است که یک نمونه DbContext
برای هر درخواست HTTP (یا هر Scope) ایجاد میشود و در پایان درخواست Dispose میشود. این کار از مشکلات ردیابی و مصرف حافظه جلوگیری میکند.
جلوگیری از مشکل N+1 Query
این مشکل زمانی رخ میدهد که شما یک مجموعه از Entities را بارگذاری میکنید و سپس در یک حلقه، دادههای مرتبط را برای هر Entity به صورت جداگانه بارگذاری میکنید. این کار منجر به تعداد زیادی Round Trip غیرضروری به پایگاه داده میشود. همیشه از Eager Loading (Include
) یا Projection (Select
) برای بارگذاری کارآمد دادههای مرتبط استفاده کنید.
مدیریت دادههای بزرگ و Streaming
برای کار با حجم عظیمی از دادهها، بارگذاری همه آنها در حافظه توصیه نمیشود. در برخی موارد، میتوانید از AsNoTracking()
و IAsyncEnumerable<T>
برای Streaming دادهها (به جای بارگذاری کامل) استفاده کنید. این به شما اجازه میدهد تا دادهها را در حین خواندن پردازش کنید و مصرف حافظه را کاهش دهید.
await foreach (var blog in context.Blogs.AsAsyncEnumerable()) { // Process one blog at a time }
امنیت: جلوگیری از SQL Injection
یکی از بزرگترین مزایای EF Core و LINQ، محافظت ذاتی در برابر SQL Injection است. هنگامی که کوئریهای خود را با LINQ مینویسید و پارامترها را به صورت امن ارسال میکنید، EF Core به صورت خودکار پارامترها را Escaping میکند و از حملات SQL Injection جلوگیری میکند. تنها زمانی که باید نگران باشید، استفاده از FromSqlRaw
یا ExecuteSqlRaw
با ورودیهای مستقیم کاربر است. در این موارد، همیشه از پارامترها استفاده کنید و ورودی کاربر را به صورت مستقیم در رشته SQL Concatenate نکنید.
پشتیبانی از Async/Await
همیشه از متدهای ناهمگام (مانند SaveChangesAsync()
، ToListAsync()
، FirstOrDefaultAsync()
) استفاده کنید. این کار به مقیاسپذیری برنامههای شما (مخصوصاً برنامههای وب) کمک میکند، زیرا Threadهای سرور را مسدود نمیکند و به سرور اجازه میدهد تا درخواستهای بیشتری را همزمان مدیریت کند.
مدیریت Concurrency Exceptions
هنگام پیادهسازی همزمانی خوشبینانه، کد شما باید بتواند DbUpdateConcurrencyException
را مدیریت کند. این کار معمولاً شامل نمایش یک پیام خطا به کاربر و/یا ارائه گزینههایی برای حل تعارض (مانند بازنویسی تغییرات کاربر دیگر، یا ادغام تغییرات) است.
نتیجهگیری
Entity Framework Core یک ORM بسیار قدرتمند و انعطافپذیر برای توسعه برنامههای داتنت است. با درک صحیح مفاهیم اصلی، الگوهای کوئرینویسی، و قابلیتهای پیشرفته آن، توسعهدهندگان میتوانند به طور چشمگیری بهرهوری خود را افزایش داده و کدهایی با کارایی بالا و قابل نگهداری بنویسند. از مدلسازی داده با Code-First و مدیریت تغییرات با Migrations گرفته تا کوئرینویسی پیچیده با LINQ و بهینهسازی عملکرد با تکنیکهای پیشرفته، EF Core ابزاری کامل را برای تعامل با پایگاه دادهها فراهم میآورد.
این مقاله سعی کرد تا پوشش جامعی از جنبههای مختلف Entity Framework Core را ارائه دهد. با این حال، تسلط واقعی بر آن تنها با تمرین و پیادهسازی در پروژههای واقعی به دست میآید. توصیه میشود که با یک پروژه کوچک شروع کنید، مفاهیم را قدم به قدم پیادهسازی کرده و با استفاده از ابزارهای مانیتورینگ، کوئریهای تولید شده را تحلیل کنید. جامعه داتنت و مستندات مایکروسافت منابع عالی برای ادامه یادگیری و حل چالشها هستند.
به یاد داشته باشید که EF Core ابزاری است که با یادگیری صحیح و استفاده بهینه، میتواند فرآیند توسعه شما را متحول کند و به شما کمک کند تا راهحلهای پایدار و مقیاسپذیری را ارائه دهید.
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان