DbContext و الگوهای Repository و Unit of Work
این DbContext در Entity Framework Core به خودی خود یک پیادهسازی از الگوی Unit of Work است. همچنین، DbSetهای آن نقش مخزن (Repository) را ایفا میکنند. بنابراین، بسیاری از توسعهدهندگان بر این باورند که افزودن لایههای اضافی برای Repository و Unit of Work بر روی DbContext غیر ضروری و غیر کاربردی است و میتواند به عنوان آنتیپترن شناخته شود.
دلایل استفاده مستقیم از DbContext:
- سادگی و کاهش پیچیدگی:
استفاده مستقیم از DbContext باعث کاهش پیچیدگی و نگهداری کد میشود. نیاز نیست که لایههای اضافی Repository و Unit of Work را مدیریت کنید.
- کاهش کد اضافی:
افزودن لایههای اضافی Repository و Unit of Work معمولاً به نوشتن کد تکراری و اضافی منجر میشود که ممکن است به پیچیدگی بیشتر و نگهداری سختتر بیانجامد.
- پشتیبانی کامل از EF Core:
در DbContext از تمامی قابلیتها و ویژگیهای EF Core به طور کامل پشتیبانی میکند. استفاده مستقیم از آن به شما امکان میدهد تا از تمام قابلیتها و انعطافپذیری EF Core بهرهمند شوید.
مثال ساده از استفاده مستقیم DbContext:
در اینجا یک نمونه ساده از استفاده مستقیم DbContext در کنترلر آورده شده است:
public class CustomersController : Controller
{
private readonly DatabaseContext _context;
public CustomersController(DatabaseContext context)
{
_context = context;
}
public async Task<IActionResult> Index()
{
var customers = await _context.Customers.ToListAsync();
return View(customers);
}
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Customer customer)
{
if (ModelState.IsValid)
{
_context.Add(customer);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(customer);
}
}
در این کد، مستقیماً از DbContext استفاده میکنیم تا دادهها را از پایگاه داده دریافت کرده و عملیات CRUD را انجام دهیم.
آنتیپترن بودن افزودن لایه Repository و Unit of Work:
افزودن لایههای Repository و Unit of Work به عنوان آنتیپترن شناخته میشود زیرا:
- افزایش پیچیدگی بدون افزودن ارزش واقعی: با توجه به این که DbContext خود یک پیادهسازی از Unit of Work و Repository است، افزودن لایههای اضافی بدون افزودن ارزش واقعی به کد، تنها پیچیدگی را افزایش میدهد.
- کاهش عملکرد و بهرهوری: لایههای اضافی میتوانند باعث کاهش عملکرد و بهرهوری شوند زیرا به کد اضافی و هزینههای اضافی نیاز دارند.
نتیجهگیری:
استفاده مستقیم از DbContext به عنوان واحد کاری (Unit of Work) و مخزن (Repository) معمولاً به عنوان بهترین عمل شناخته میشود و از افزودن لایههای اضافی خودداری میشود. این رویکرد نه تنها پیچیدگی را کاهش میدهد بلکه از تمام قابلیتها و امکانات EF Core بهرهمند میشود. البته، بسته به نیازهای خاص پروژه شما، ممکن است موارد استثنایی وجود داشته باشد.
پوشه Models پوشه Services پوشه GetCustomerList فایل اینترفیس IGetCustomerListService.cs
using WebApplication5.Models.Context;
namespace WebApplication5.Models.Services.GetCustomerList
{
public interface IGetCustomerListService
{
List<CustomerListDto> Execute();
}
public class GetCustomerListService : IGetCustomerListService
{
private readonly DatabaseContext Context;
public GetCustomerListService()
{
Context = new DatabaseContext();
}
public List<CustomerListDto> Execute()
{
var customers = Context.Customers.Select(p => new CustomerListDto {
Id = p.Id,
Name = p.Name,
UserName = p.UserName,
}).ToList();
return customers;
}
}
public class CustomerListDto
{
public long Id { get; set; }
public string Name { get; set; }
public string UserName { get; set; }
}
}
بیایید کد شما را قدم به قدم بررسی کنیم و هر بخش از آن را توضیح دهیم:
1. افزودن وابستگیها
using WebApplication5.Models.Context;
این دستور using، فضای نام WebApplication5.Models.Context را وارد میکند که فرضاً شامل کلاس DatabaseContext است.
2. تعریف Namespace و اینترفیس
namespace WebApplication5.Models.Services.GetCustomerList
{
public interface IGetCustomerListService
{
List<CustomerListDto> Execute();
}
namespace WebApplication5.Models.Services.GetCustomerList: این بخش نشان میدهد که کدهای زیر در فضای نام WebApplication5.Models.Services.GetCustomerList قرار دارند.
public interface IGetCustomerListService: یک اینترفیس به نام IGetCustomerListService تعریف شده که شامل یک متد به نام Execute است که لیستی از CustomerListDto بازمیگرداند.
3. تعریف کلاس GetCustomerListService
public class GetCustomerListService : IGetCustomerListService
{
private readonly DatabaseContext Context;
public GetCustomerListService()
{
Context = new DatabaseContext();
}
public class GetCustomerListService : IGetCustomerListService: این کلاس از اینترفیس IGetCustomerListService پیروی میکند.
private readonly DatabaseContext Context: یک متغیر خصوصی برای نگهداری یک نمونه از DatabaseContext.
public GetCustomerListService(): سازنده کلاس که یک نمونه جدید از DatabaseContext ایجاد میکند و آن را به متغیر Context اختصاص میدهد.
4. پیادهسازی متد Execute
public List<CustomerListDto> Execute()
{
var customers = Context.Customers.Select(p => new CustomerListDto {
Id = p.Id,
Name = p.Name,
UserName = p.UserName,
}).ToList();
return customers;
}
public List<CustomerListDto> Execute(): این متد از اینترفیس پیروی کرده و پیادهسازی شده است.
Context.Customers.Select(p => new CustomerListDto { ... }).ToList();: این خط یک لیست از CustomerListDto ها بر اساس دادههای موجود در Customers جدول پایگاه داده ایجاد میکند.
return customers;: لیست ایجاد شده از مشتریان را بازمیگرداند.
5. تعریف کلاس CustomerListDto
public class CustomerListDto
{
public long Id { get; set; }
public string Name { get; set; }
public string UserName { get; set; }
}
}
public class CustomerListDto: این کلاس یک مدل دادهای ساده است که شامل اطلاعاتی از مشتری است.
ویژگیها:
Id: شناسه مشتری.
Name: نام مشتری.
UserName: نام کاربری مشتری.
نتیجهگیری
کد شما به این منظور طراحی شده است تا یک سرویس فراهم کند که لیستی از مشتریان را از پایگاه داده با استفاده از Entity Framework و DbContext بازیابی کند. این سرویس، مشتریان را انتخاب کرده و آنها را در قالب CustomerListDto بازمیگرداند. ساختار کلی شامل یک اینترفیس، یک کلاس که این اینترفیس را پیادهسازی میکند، و یک مدل دادهای برای انتقال دادههای مشتریان است.
در Program.cs
using WebApplication5.Models.Services.GetCustomerList;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddTransient<IGetCustomerListService, GetCustomerListService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
کد AddTransient<IGetCustomerListService, GetCustomerListService> بخشی از تنظیمات وابستگی (Dependency Injection) در ASP.NET Core است. بیایید به طور دقیقتر توضیح دهیم که این کد چه کاری انجام میدهد.
توضیح کلی
این کد یک سرویس (GetCustomerListService) را به سیستم تزریق وابستگی اضافه میکند و آن را به اینترفیس (IGetCustomerListService) متصل میکند. این تنظیمات به ASP.NET Core اجازه میدهد تا هر بار که یک وابستگی به IGetCustomerListService نیاز است، یک نمونه جدید از GetCustomerListService ایجاد کند.
اجزای اصلی کد
services.AddTransient<IGetCustomerListService, GetCustomerListService>();
services:
این شیء که معمولاً در متد ConfigureServices موجود است، شامل سرویسهای ثبت شده در کانتینر وابستگی است.
AddTransient:
این متد به کانتینر وابستگی ASP.NET Core میگوید که هر بار که یک وابستگی به IGetCustomerListService نیاز است، یک نمونه جدید از GetCustomerListService ایجاد کند. Transient به این معناست که هر بار که این سرویس درخواست میشود، یک نمونه جدید از کلاس مورد نظر ایجاد میشود. این مورد برای سرویسهایی مناسب است که وضعیت داخلی ندارند یا نیاز است که هر بار یک نمونه جدید ایجاد شود.
IGetCustomerListService:
این اینترفیس نمایانگر قرارداد سرویس است که تعریف میکند چه متدهایی باید توسط پیادهسازیها ارائه شود.
GetCustomerListService:
این کلاس پیادهسازی اینترفیس است و شامل منطق واقعی برای انجام عملیاتهای تعریف شده در اینترفیس میباشد.
مثال کاربردی
با ثبت این سرویس در کانتینر وابستگی، شما میتوانید آن را به هر کلاسی که نیاز به استفاده از این سرویس دارد، تزریق کنید. به عنوان مثال، در یک کنترلر میتوان به این شکل از سرویس استفاده کرد:
public class CustomerController : Controller
{
private readonly IGetCustomerListService _getCustomerListService;
public CustomerController(IGetCustomerListService getCustomerListService)
{
_getCustomerListService = getCustomerListService;
}
public IActionResult Index()
{
var customers = _getCustomerListService.Execute();
return View(customers);
}
}
نتیجهگیری
کد AddTransient<IGetCustomerListService, GetCustomerListService> به شما این امکان را میدهد که از سیستم تزریق وابستگی ASP.NET Core استفاده کنید تا مدیریت ایجاد و حفظ وابستگیهای سرویسها سادهتر شود. با استفاده از این روش، میتوانید به راحتی سرویسهای خود را تست و مدیریت کنید و از تکرار کد جلوگیری کنید.
ایجاد کنترلر و نام کنترلر: CustomerController.cs و کد های زیر
public class CustomerController : Controller
{
private readonly IGetCustomerListService _getCustomerListService;
public CustomerController(IGetCustomerListService getCustomerListService)
{
_getCustomerListService = getCustomerListService;
}
// GET: CustomerController
public ActionResult Index()
{
return View(_getCustomerListService.Execute());
}
بیایید کد شما را به تفصیل بررسی کنیم و هر بخش از آن را توضیح دهیم:
تعریف کلاس کنترلر
public class CustomerController : Controller
{
public class CustomerController :
Controller:
این بخش نشان میدهد که CustomerController یک کلاس عمومی است که از کلاس Controller به ارث برده است. در ASP.NET Core، کنترلرها مسئول مدیریت درخواستهای HTTP و برگرداندن پاسخ مناسب به کاربر هستند.
تعریف فیلد خصوصی برای سرویس
private readonly IGetCustomerListService _getCustomerListService;
private readonly IGetCustomerListService
_getCustomerListService:
این خط یک فیلد خصوصی و فقط خواندنی (readonly) به نام _getCustomerListService از نوع اینترفیس IGetCustomerListService تعریف میکند. این فیلد برای نگهداری یک نمونه از سرویس IGetCustomerListService استفاده میشود.
سازنده کلاس
public CustomerController(IGetCustomerListService getCustomerListService)
{
_getCustomerListService = getCustomerListService;
}
public
CustomerController(IGetCustomerListService getCustomerListService):
این سازنده کلاس CustomerController است که یک پارامتر از نوع IGetCustomerListService دریافت میکند.
_getCustomerListService = getCustomerListService:
در داخل سازنده، نمونهای از IGetCustomerListService که از طریق تزریق وابستگی (Dependency Injection) دریافت شده است، به فیلد خصوصی _getCustomerListService اختصاص داده میشود. این روش به کنترلر اجازه میدهد تا از سرویس IGetCustomerListService استفاده کند.
متد Index
// GET: CustomerController
public ActionResult Index()
{
return View(_getCustomerListService.Execute());
}
}
public ActionResult Index():
این متد یک اکشن به نام Index در کنترلر CustomerController تعریف میکند. این اکشن زمانی اجرا میشود که درخواست GET به آدرس /Customer ارسال شود.
return View(_getCustomerListService.Execute()):
این خط یک ویو (View) را برمیگرداند و دادههای مشتریان را که از طریق متد Execute در سرویس _getCustomerListService بازیابی شدهاند، به ویو ارسال میکند. متد Execute یک لیست از CustomerListDto بازمیگرداند که به ویو ارسال میشود تا نمایش داده شود.
خلاصه
کد شما یک کنترلر به نام CustomerController ایجاد میکند که شامل یک اکشن به نام Index است. این کنترلر از یک سرویس IGetCustomerListService استفاده میکند تا لیستی از مشتریان را بازیابی کرده و آن را به ویو ارسال کند. با استفاده از تزریق وابستگی، نمونهای از سرویس IGetCustomerListService به کنترلر تزریق میشود و از آن برای اجرای منطق بازیابی دادهها استفاده میشود.
این ساختار به شما کمک میکند تا منطق کسب و کار را از منطق ارائه (presentation logic) جدا کنید و کد خود را ماژولار و تستپذیر کنید.
context.Product.Add() و context.Product.AddAsync() و context.Product.AddRange() و context.Product.Attach() و context.Product.Remove() و context.Product.RemoveRange() , context.Product.Find()
بیایید هر یک از این متدها را که در هنگام کار با DbContext در Entity Framework Core استفاده میشوند، بررسی کنیم. این متدها به شما کمک میکنند تا عملیات مختلفی را روی موجودیتها (entities) انجام دهید.
1. context.Products.Add()
این متد برای اضافه کردن یک موجودیت جدید به مجموعهی Products استفاده میشود. این موجودیت به حالت "اضافه شده" تغییر پیدا میکند و در زمان اجرای SaveChanges به پایگاه داده اضافه میشود.
public IActionResult Create(Product product)
{
if (ModelState.IsValid)
{
_context.Products.Add(product);
_context.SaveChanges();
return RedirectToAction(nameof(Index));
}
return View(product);
}
2. context.Products.AddAsync()
این متد مشابه Add است، اما نسخهی غیرهمزمان (async) آن است که در مواردی که نیاز به استفاده از عملیاتهای غیرهمزمان (asynchronous) دارید، استفاده میشود. این متد یک Task برمیگرداند.
public async Task<IActionResult> CreateAsync(Product product)
{
if (ModelState.IsValid)
{
await _context.Products.AddAsync(product);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(product);
}
3. context.Products.AddRange()
این متد برای اضافه کردن مجموعهای از موجودیتها به صورت همزمان استفاده میشود. تمامی موجودیتها به حالت "اضافه شده" تغییر پیدا میکنند و در زمان اجرای SaveChanges به پایگاه داده اضافه میشوند.
public IActionResult CreateMultiple(List<Product> products)
{
if (ModelState.IsValid)
{
_context.Products.AddRange(products);
_context.SaveChanges();
return RedirectToAction(nameof(Index));
}
return View(products);
}
4. context.Products.Attach()
این متد برای "اتصال" یک موجودیت به کانتکست استفاده میشود. این متد موجودیت را به کانتکست اضافه میکند بدون اینکه حالت آن را تغییر دهد. این متد معمولاً برای سناریوهایی استفاده میشود که موجودیت در حال حاضر در پایگاه داده وجود دارد و شما میخواهید تغییراتی را در آن اعمال کنید.
public IActionResult Edit(Product product)
{
if (ModelState.IsValid)
{
_context.Products.Attach(product);
_context.Entry(product).State = EntityState.Modified;
_context.SaveChanges();
return RedirectToAction(nameof(Index));
}
return View(product);
}
5. context.Products.Remove()
این متد برای حذف یک موجودیت از مجموعهی Products استفاده میشود. موجودیت به حالت "حذف شده" تغییر پیدا میکند و در زمان اجرای SaveChanges از پایگاه داده حذف میشود.
public IActionResult Delete(long id)
{
var product = _context.Products.Find(id);
if (product != null)
{
_context.Products.Remove(product);
_context.SaveChanges();
return RedirectToAction(nameof(Index));
}
return NotFound();
}
6. context.Products.RemoveRange()
این متد برای حذف مجموعهای از موجودیتها به صورت همزمان استفاده میشود. تمامی موجودیتها به حالت "حذف شده" تغییر پیدا میکنند و در زمان اجرای SaveChanges از پایگاه داده حذف میشوند.
public IActionResult DeleteMultiple(List<long> ids)
{
var products = _context.Products.Where(p => ids.Contains(p.Id)).ToList();
if (products.Any())
{
_context.Products.RemoveRange(products);
_context.SaveChanges();
return RedirectToAction(nameof(Index));
}
return NotFound();
}
7. context.Products.Find()
این متد برای یافتن یک موجودیت با استفاده از کلید اصلی آن استفاده میشود. اگر موجودیت پیدا شود، برگردانده میشود؛ در غیر این صورت، مقدار null برمیگردد.
public IActionResult Details(long id)
{
var product = _context.Products.Find(id);
if (product != null)
{
return View(product);
}
return NotFound();
}
نتیجهگیری
این متدها به شما کمک میکنند تا عملیات مختلفی مانند ایجاد، ویرایش، حذف، و یافتن موجودیتها را در پایگاه داده انجام دهید. با استفاده از این متدها، میتوانید به سادگی دادههای خود را مدیریت کنید و تغییرات را به پایگاه داده اعمال کنید.
SQL Server Profiler
یک ابزار تجزیه و تحلیل و ردیابی است که توسط Microsoft برای SQL Server توسعه داده شده است. این ابزار به شما اجازه میدهد تا فعالیتها و عملیاتی که در یک پایگاه داده SQL Server انجام میشوند را ردیابی کرده و آنها را تجزیه و تحلیل کنید.
ویژگیهای SQL Server Profiler:
ردیابی فعالیتها: شما میتوانید تمامی فعالیتها و عملیاتی که در پایگاه داده انجام میشوند را ردیابی کنید.
انتخاب و اعمال فیلتر: شما میتوانید فیلترهای مختلفی را برای ردیابی فعالیتهای خاص اعمال کنید.
اختلاط و تجزیه و تحلیل دادهها: دادههای ردیابی میتوانند در فایل یا جدول ذخیره شده و بعداً تجزیه و تحلیل شوند.
استفاده در تشخیص مشکلات: این ابزار میتواند به شما کمک کند تا مشکلات مرتبط با عملکرد پایگاه داده را تشخیص دهید و رفع کنید.
کاربردها:
ردیابی و تشخیص مشکلات: برای ردیابی و تشخیص مشکلات مرتبط با عملکرد پایگاه داده.
مرور عملکرد: برای مرور عملکرد پایگاه داده و تعیین کدام عملیات ممکن است عملکرد را تحت تأثیر قرار بگیرند.
تجزیه و تحلیل عملیات: برای تجزیه و تحلیل عملیات و فعالیتهای مختلف در پایگاه داده.
ملاحظات:
استفاده از تجسمهای گسترده (Extended Events): از آینده به عنوان جایگزینی برای SQL Server Profiler پیشنهاد میشود.