آموزش ساخت سرویس CRUD در EF Core

در آموزش ساخت سرویس CRUD در EF Core قصد داریم. یک سرویس ساختاریافته برای مدیریت داده‌ها در پروژه‌های خود بسازیم. عملیات CRUD را با حداقل کدنویسی و حداکثر بازدهی در EF Core پیاده‌سازی کنیم. از ابزارهای Scaffolding و LINQ و AutoMapper برای تسریع توسعه و مدیریت داده‌ها استفاده کنیم. و کدهای خود را طوری بنویسیم که قابلیت تست‌پذیری و انعطاف‌پذیری بیشتری داشته باشد.

سرویس بخشی از برنامه است که وظیفه انجام یک کار مشخص یا ارائه یک قابلیت خاص را بر عهده دارد. سرویس‌ها معمولاً منطق اصلی برنامه، مانند پردازش داده‌ها، ارتباط با پایگاه داده یا اجرای قوانین کسب‌وکار را مدیریت می‌کنند و به بخش‌های دیگر برنامه (مثل کنترلرها یا رابط کاربری) کمک می‌کنند تا وظایف خود را ساده‌تر انجام دهند. هدف اصلی سرویس‌ها، سازماندهی بهتر کد، کاهش تکرار و افزایش قابلیت نگهداری است.

آموزش ساخت سرویس CRUD

در این آموزش فرض ما این است که میخواهیم معلم ها را در سایت خود ثبت نام کنیم. و روی آن عملیات CRUD انجام دهیم. مثلا لیست معلمان را مشاهده کنیم. قابلیت افزودن، حدف و وبرایش معلم ها را نیز داشته باشیم.

  • ساخت مدل
public class Teacher
{
    public int Id { get; set; }
    public required string Name { get; set; }
    public required string Family { get; set; }
    public required string Email { get; set; }
}
  • ساخت ویو مدل
public class TeacherViewModel
{
    public int Id { get; set; }
    public required string Name { get; set; }
    public required string Family { get; set; }
    public required string Email { get; set; }
}
  • افزودن مدل به کلاس DbContext
public DbSet<Teacher> Teachers { get; set; }
  • انجام عملیات Migration
Add-Migration
Update-Database
  • افرودن تنظیمات AutoMapper
CreateMap<Teacher,TeacherViewModel>().ReverseMap();
  • ساخت Interface سرویس Teacher

اینترفیس (Interface) در برنامه‌نویسی، قراردادی است که مجموعه‌ای از متدها و خواص را تعریف می‌کند، بدون اینکه پیاده‌سازی آن‌ها را مشخص کند. اینترفیس به کلاس‌ها یا ساختارها اجازه می‌دهد از این قرارداد پیروی کنند و متدها یا خواص مشخص‌شده را طبق نیاز خود پیاده‌سازی کنند.

public interface ITeacherService
{
    Task<List<TeacherViewModel>> GetAllTeacherAsync();
    Task<TeacherViewModel?> GetTeacherByIdAsync(int id);
    Task<bool> ExistsTeacherAsync(int id);
    Task CreateTeacherAsync(TeacherViewModel model);
    Task UpdateTeacherAsync(TeacherViewModel model);
    Task<bool> DeleteTeacherAsync(int id);
}
  • ساخت سرویس Teacher
public class TeacherService : ITeacherService
{
    private readonly MyDbContext _context;
    private readonly IMapper _mapper;
    private readonly ILogger<TeacherService> _logger;
    public TeacherService(MyDbContext context, IMapper mapper, ILogger<TeacherService> logger)
    {
        _context = context;
        _mapper = mapper;
        _logger = logger;
    }
    /////////////////////////////////////////////////////////////
    public async Task<List<TeacherViewModel>> GetAllTeacherAsync()
    {
        try
        {
            return await _context.Teachers
                .ProjectTo<TeacherViewModel>(_mapper.ConfigurationProvider)
                .AsNoTracking()
                .ToListAsync();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error In GetAllTeacherAsync");
            throw new InvalidOperationException("Error In GetAllTeacherAsync", ex);
        }
    }
    /////////////////////////////////////////////////////////////
    public async Task<TeacherViewModel?> GetTeacherByIdAsync(int id)
    {
        try
        {
            var TeacherVm = await _context.Teachers
                .Where(x => x.Id == id)
                .AsNoTracking()
                .ProjectTo<TeacherViewModel>(_mapper.ConfigurationProvider)
                .FirstOrDefaultAsync();
            if (TeacherVm == null)
            {
                throw new KeyNotFoundException($"Teacher with ID {id} not found");
            }
            return TeacherVm;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error In GetTeacherByIdAsync");
            throw new InvalidOperationException("Error In GetTeacherByIdAsync", ex);
        }
    }
    /////////////////////////////////////////////////////////////
    public async Task<bool> ExistsTeacherAsync(int id)
    {
        try
        {
            return await _context.Teachers
                .AsNoTracking()
                .AnyAsync(a => a.Id == id);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error In ExistsTeacherAsync");
            throw new InvalidOperationException("Error In ExistsTeacherAsync", ex);
        }
    }
    /////////////////////////////////////////////////////////////
    public async Task CreateTeacherAsync(TeacherViewModel model)
    {
        try
        {
            if (model == null)
            {
                throw new ArgumentNullException(nameof(model), "The Teacher Model Cannot Be Null.");
            }
            var TeacherEntity = _mapper.Map<Teacher>(model);
            _context.Add(TeacherEntity);
            await _context.SaveChangesAsync();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error In CreateTeacherAsync");
            throw new InvalidOperationException("Error In CreateTeacherAsync", ex);
        }
    }
    /////////////////////////////////////////////////////////////
    public async Task UpdateTeacherAsync(TeacherViewModel model)
    {
        try
        {
            if (model == null)
            {
                throw new ArgumentNullException(nameof(model), "The Teacher Model Cannot Be Null.");
            }
            var TeacherEntity = await _context.Teachers.FindAsync(model.Id);
            if (TeacherEntity == null)
            {
                throw new KeyNotFoundException($"Teacher with ID {model.Id} not found.");
            }
            _mapper.Map(model, TeacherEntity);
            _context.Update<Teacher>(TeacherEntity);
            await _context.SaveChangesAsync();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error In UpdateTeacherAsync");
            throw new InvalidOperationException("Error In UpdateTeacherAsync", ex);
        }
    }
    /////////////////////////////////////////////////////////////
    public async Task<bool> DeleteTeacherAsync(int id)
    {
        try
        {
            var TeacherEntity = await _context.Teachers.FindAsync(id);
            if (TeacherEntity == null)
            {
                throw new KeyNotFoundException($"Teacher with ID {id} not found");
            }
            _context.Remove(TeacherEntity);
            await _context.SaveChangesAsync();
            return true;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error In DeleteTeacherAsync");
            throw new InvalidOperationException("Error In DeleteTeacherAsync", ex);
        }
    }
}
  • تزریق وابستگی سرویس در کلاس Program
builder.Services.AddScoped<ITeacherService,TeacherService>();
  • کد های کنترلر
public class TeachersController : Controller
{
    private readonly ITeacherService _teacherService;
    public TeachersController(ITeacherService teacherService)
    {
        _teacherService = teacherService;
    }
    public async Task Index()
    {
        return View(await _teacherService.GetAllTeacherAsync());
    }
    public async Task Details(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }
        var teacher = await _teacherService.GetTeacherByIdAsync(id.Value);
        return View(teacher);
    }
    public IActionResult Create()
    {
        return View();
    }
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task Create([Bind("Id,Name,Family,Email")] TeacherViewModel TeacherVm)
    {
        if (ModelState.IsValid)
        {
            await _teacherService.CreateTeacherAsync(TeacherVm);
            return RedirectToAction(nameof(Index));
        }
        return View(TeacherVm);
    }
    public async Task Edit(int? id)
    {
        if (id == null) return NotFound();
        var teacher = await _teacherService.GetTeacherByIdAsync(id.Value);
        return View(teacher);
    }
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task Edit(int id, [Bind("Id,Name,Family,Email")] TeacherViewModel TeacherVm)
    {
        if (id != TeacherVm.Id)
        {
            return NotFound();
        }
        if (ModelState.IsValid)
        {
            await _teacherService.UpdateTeacherAsync(TeacherVm);
            return RedirectToAction(nameof(Index));
        }
        return View(TeacherVm);
    }
    public async Task Delete(int? id)
    {
        if (id == null) return NotFound();
        var TeacherVm = await _teacherService.GetTeacherByIdAsync(id.Value);
        return View(TeacherVm);
    }
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public async Task DeleteConfirmed(int id)
    {
        bool IsSuccess = await _teacherService.DeleteTeacherAsync(id);
        if (IsSuccess)
        {
            return RedirectToAction(nameof(Index));
        }
        return View();
    }
}

چرا باید از سرویس ها استفاده کنیم؟

استفاده از سرویس‌ها در برنامه‌نویسی، به‌ویژه در پروژه‌هایی که از معماری‌هایی مانند ASP.NET Core و Entity Framework Core استفاده می‌کنند، دلایل مهمی دارد. در ادامه به دلایل اصلی استفاده از سرویس‌ها اشاره می‌کنم:

جداسازی منطق کسب‌وکار (Separation of Concerns)

سرویس‌ها به شما کمک می‌کنند که منطق کسب‌وکار (Business Logic) را از لایه‌های دیگر (مانند کنترلرها یا رابط کاربری) جدا کنید. این جداسازی باعث می‌شود کدهای شما تمیزتر، قابل نگهداری‌تر و سازمان‌یافته‌تر باشند.

قابلیت استفاده مجدد (Reusability)

اگر منطق CRUD شما در یک سرویس متمرکز باشد، می‌توانید آن را در بخش‌های مختلف پروژه مجدد استفاده کنید. مثلاً یک سرویس ProductService می‌تواند هم در Web API و هم در برنامه‌های دسکتاپ یا موبایل استفاده شود.

تسهیل تست‌پذیری (Testability)

زمانی که عملیات CRUD و منطق کسب‌وکار در یک سرویس جداگانه پیاده‌سازی شده باشد، می‌توانید با استفاده از ابزارهای Mocking و Unit Testing آن را به‌راحتی تست کنید. این کار برای توسعه پروژه‌های بزرگ بسیار ضروری است.

مدیریت بهتر وابستگی‌ها (Dependency Management)

سرویس‌ها از طریق Dependency Injection (DI) مدیریت می‌شوند. این روش باعث می‌شود وابستگی‌ها به صورت انعطاف‌پذیر و بدون نیاز به وابستگی مستقیم به کلاس‌های خاص تعریف شوند. این ویژگی باعث کاهش Coupling (وابستگی‌های متقابل) بین کلاس‌ها می‌شود.

افزایش خوانایی و نگهداری کد (Maintainability)

وقتی که منطق کسب‌وکار در یک سرویس متمرکز است، تغییرات یا افزودن قابلیت‌های جدید بسیار ساده‌تر خواهد بود. به‌جای جستجو در کل پروژه، فقط باید سرویس مربوطه را تغییر دهید.

بهبود امنیت و مدیریت دسترسی

سرویس‌ها امکان اضافه کردن لایه‌های امنیتی را فراهم می‌کنند. مثلاً می‌توانید در سرویس بررسی کنید که کاربر مجاز به انجام عملیات خاصی هست یا نه. این کار امنیت کد شما را افزایش می‌دهد.

مشاهده در آپارات یا یوتیوب

محتوای ویدیویی این صفحه در سایت قابل مشاهده است. اما اگر عادت دارید توی پلتفرم آپارات یا یوتیوب ویدیوها را مشاهده کنید، می‌توانید از دکمه‌های زیر استفاده کنید.

تماشای ویدیوی آموزش ساخت سرویس CRUD در EF Core در آپارات و یوتیوب

اشتراک‌گذاری با یک کلیک

با یک کلیک، محتوای این صفحه را در شبکه‌های اجتماعی، ایمیل یا پیام‌رسان‌های مورد علاقه‌تان به‌سرعت به اشتراک بگذارید.

اشتراک‌گذاری صفحه آموزش ساخت سرویس CRUD در EF Core در شبکه‌های اجتماعی

نظرات ارزشمند شما

نظرات شما برای ما ارزشمند است و آنها را میخوانیم و پاسخ میدهیم.

تصویر علی قربانی

سلام و درود کارتون عالیه
ویدیو جدید رو کی قرار میدین تو سایت؟

تصویر محمدرضا احدیان

سلام ممنون از شما. ویدیو جدید آپلود شده.

تصویر مرتضی فاضل

یک سوال ،من بعضی جاها دیدم که از Dto استفاده میکنند بجای ویو مدل داخل سرویس ،پیاده سازیش تقریبا مثل همه،ولی کاربرد هاش یجور نیست،چه تفاوتی دارند و کجاها استفاده میشند؟

تصویر محمدرضا احدیان

<ul>
<li>DTO برای انتقال داده بین لایه‌ها و سرویس‌هاست و معمولاً فقط فیلدهای ضروری را بدون هیچ منطق اضافه ای را دارد.</li>

<li>ViewModel در لایهٔ نمایش به‌کار می‌رود و ممکن است شامل ویژگی‌های اعتبارسنجی و فرمت‌های نمایش باشد.</li>
</ul>

در نتیجه، DTO بیشتر در سرویس و API به‌عنوان قرارداد داده استفاده می‌شود، درحالی‌که ViewModel برای نمایش یا دریافت اطلاعات از کاربر در رابط کاربری مناسب است.
چه زمانی از DTO و چه زمانی از ViewModel استفاده کنیم؟
<img src="https://tehranit.net/wp-content/uploads/2025/02/DTO-VS-ViewModel.jpg" style="width: 100%;" alt="چه زمانی از DTO و چه زمانی از ViewModel استفاده کنیم؟" />
دلیل اینکه در سرویس از ViewModel استفاده کردیم این است که در پروژهٔ ما سرویس مستقیماً با لایهٔ UI در تعامل است و معماری پیچیده‌ای نداریم که نیازمند DTOهای جداگانه باشد. به بیان دیگر، تمام فیلدها و اعتبارسنجی‌ها در سطح ViewModel برای نمایش یا دریافت داده مناسب بود و ما نیازی به جداسازی آن در قالب DTO احساس نکردیم.
ما هنوز در خصوص DTO صحبت نکردیم و به امید خدا در دوره Asp.Net Core در فصل Web Api به آن خواهیم پرداخت.

تصویر مرتضی فاضل

من واقعا از زحمات شما ممنونم ، توی این یکی دو ماهی که با تهران ای تی اشنا شدم اندازه یک سال تجربه بدست اوردم،

تصویر محمدرضا احدیان

سلامت باشید ممنون از محبتی که دارید.

تصویر قاسم زارعی

با سلام
واقعا از زحماتتون ممنونیم و آموزشای شما اصلا نسبت به بقیه یه چیز دیگست کاملا ساده،گویا،قابل فهم و کامل هست

تصویر قاسم زارعی

اگر ممکنه همه این کارا رو در قالب یه معماری مثل معماری تمیز هم انجام بدین که کار با لایه ها و معماری تمیز هم یاد بگیریم

تصویر محمدرضا احدیان

سلام ممنون از انرژی مثبتی که میدین. امیدوارم رشد بیشتری با لطف و محبت شما تو بحث آموزش داشته باشیم.

تصویر محمدرضا احدیان

معماری ها مساله مهمی است و حتما در دوره Asp.Net Core به آن میپردازیم. البته در قالب پروژه های عملی. نکته ای که هست هنور چند مورد مونده برای آموزش. حتما بعد از این موارد به بحث معماری ها میپردازیم.

تصویر ملیکا حسنی

سلام استاد ممنون از آموزش خوبتون. خیلی فیلم در خصوص سرویس ها دیده بودم ولی متوجه نمیشدم. آموزش شما رو خیلی خوب متوجه میشم و یاد میگیرم. استاد شما تدریس خصوصی یا کلاس حضوری هم دارید؟

تصویر محمدرضا احدیان

ممنون از محبتی که به ما دارید. خوشحالم آموزش ها برای شما مفید بوده. و تدریس خصوصی یا کلاس حضوری در حال حاضر خیر. درصورت برگزاری در سایت اعلام میکنیم.

تصویر رضا فلاح

سلام استاد
ضمن تشکر از آموزش های عالی شما
مشکلی توی migration داشتم که چون دلیل بروزش رو متوجه نمیشم، هر بار بصورت دستی حلش می کنم. ممنون میشم راهنمایی بفرمایید.
بعد از ایجاد مدل و مدل ویو دلخواه و ثبت اون در DbContex ، با فرمان Add-Migration و سپس دادن نامی مانند addTableTeacher می بینم که در فایل ایجاد شده موارد قبلی از ویومدلهای مثلا Manager هم وجود داره(در ایجاد Manager این موارد از Developer وجود داشت و...)
اگر اصلاحش نکنم برای این ویومدل ها هم در دیتابیس جدولی میسازه. لذا بصورت دستی موارد قبلی رو برمی دارم و بعد دستور update-database رو می زنم تا صرفا جدول موردنظر ساخته بشه. مشکل من از کجا ناشی میشه؟؟

تصویر محمدرضا احدیان

سلام دوست عزیز و ممنون از شما
در خیلی موارد در آموزش ها ما برای سرعت بیشتر کنترلر و ویو های خود را با scaffolding و از روی ViewModel های خود ایجاد میکنیم.
که این کار باعث میشه scaffolding احساس کند ViewModel ما یک مدل است و به صورت خود کار DbSet آن را در فایل Database Context ایجاد کند.
در صورتی که این کار اشتباه است. و فعلا راه کاری وجود ندارد که جلوی افزوده شدن ViewModel ها را به صورت خود کار در فایل DatabaseContext بگیریم.
تنها راه اینه قبل از عملیات Migration فایل Database Context خود را برسی و در صورتی که ViewModel ای به صورت ناخواسته به آن اضافه شده آن را حذف کنیم.

فرم افزودن دیدگاه

با ارسال نظرات خود ما را در ایجاد محتوای بهتر کمک کنید.

با خدمات تهران آی تی آشنا شوید

تهران آی تی با افتخار خدمات زیر را ارائه می‌دهد.