در هنگام توسعه پروژه زمانی که تعداد جداول افزایش می یابد پیاده سازی عملیات CRUD برای جداول وقت گیر میشود. در این مقاله به پیاده سازی سیستمی میپردازیم که عملیات CRUD را به صورت خودکار انجام دهد.

ابتدا یک پروژه از نوع ASP.NET Core Web Application ایجاد کنید و یک پوشه به نام Repositories بسازید. در این قسمت میخواهیم یک کلاس جنریک پایه ایجاد کنیم که عملیات CRUD را پیاده سازی کرده باشد و در ادامه برای ساخت عملیات CRUD برای سایر جدول یک کلاس جدید میسازیم و از این کلاس جنریک ارث بری میکنیم. محتوای کلاس به صورت زیر است :

public interface IGenericRepository<TEntity> where TEntity : class, new()
{
    Task<TEntity> GetByIdAsync(CancellationToken cancellationToken, params object[] ids);
    Task<IEnumerable<TEntity>> GetAll(CancellationToken cancellationToken);
    Task AddAsync(TEntity entity, CancellationToken cancellationToken, bool saveNow = true);
    Task AddRangeAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken, bool saveNow = true);
    Task DeleteAsync(TEntity entity, CancellationToken cancellationToken, bool saveNow = true);
    Task DeleteRangeAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken, bool saveNow = true);
    Task UpdateAsync(TEntity entity, CancellationToken cancellationToken, bool saveNow = true);
    Task UpdateRangeAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken, bool saveNow = true);
    Task<int> SaveChangeAsync(CancellationToken cancellationToken);
}
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class, new()
{
    private readonly ApplicationDbContext _context;
    private DbSet<TEntity> _table { get; set; }
    public GenericRepository(ApplicationDbContext context)
    {
        _context = context;
        _table = _context.Set<TEntity>();
    }
    public virtual Task<TEntity> GetByIdAsync(CancellationToken cancellationToken, params object[] ids)
    {
        return _table.FindAsync(ids, cancellationToken);
    }
    public virtual async Task AddAsync(TEntity entity, CancellationToken cancellationToken, bool saveNow = true)
    {
        await _table.AddAsync(entity, cancellationToken).ConfigureAwait(false);
        if (saveNow)
            await _context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
    }

    public virtual async Task AddRangeAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken, bool saveNow = true)
    {
        await _table.AddRangeAsync(entities, cancellationToken).ConfigureAwait(false);
        if (saveNow)
            await _context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
    }

    public virtual async Task UpdateAsync(TEntity entity, CancellationToken cancellationToken, bool saveNow = true)
    {
        _table.Update(entity);
        if (saveNow)
            await _context.SaveChangesAsync(cancellationToken);
    }
    public virtual async Task UpdateRangeAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken, bool saveNow = true)
    {
        _table.UpdateRange(entities);
        if (saveNow)
            await _context.SaveChangesAsync(cancellationToken);
    }
    public virtual async Task DeleteAsync(TEntity entity, CancellationToken cancellationToken, bool saveNow = true)
    {
        _table.Remove(entity);
        if (saveNow)
            await _context.SaveChangesAsync(cancellationToken);
    }
    public virtual async Task DeleteRangeAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken, bool saveNow = true)
    {
        _table.RemoveRange(entities);
        if (saveNow)
            await _context.SaveChangesAsync(cancellationToken);
    }
    public virtual async Task<IEnumerable<TEntity>> GetAll(CancellationToken cancellationToken)
    {
        return await _table.ToListAsync(cancellationToken);
    }
    public async Task<int> SaveChangeAsync(CancellationToken cancellationToken)
    {
        return await _context.SaveChangesAsync(cancellationToken);
    }
}

این کلاس یک تایپ جنریک قبول میکند که باید از نوع کلاس ( calss ) باشد و بتوان از آن نمونه بسازیم ( ()new ). اگر شرط زیر را قرار ندهیم نمیتوانیم تایپ ورودی را به DbSet ست کنیم.

public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class, new()

در ادامه میخواهیم یک عملیات ساده CRUD را برای جدول کاربران ایجاد کنیم. برای این کار ابتدا یک پوشه جدید به نام Models ایجاد میکنیم و کلاس Person را با مشخصات زیر درون پوشه Models قرار میدهیم.

public class Person
{
    public int Id { get; set; }
    public string  Username { get; set; }
}

سپس یک کلاس برای پیاده سازی عملیات CRUD برای جدول Person ایجاد میکنیم. این کلاس از جنریک ریپازیتوری ارث بری میکند و کلاس Person را به تایپ جنریک ریپازیتوری پاس میدهد.

سپس باید کانتکست را به کلاس پدر که همان جنریک ریپازیتوری است را ارسال کنیم.

public interface IPersonRepository : IGenericRepository<Person>
{

}
public class PersonRepository : GenericRepository<Person>, IPersonRepository
{
    public PersonRepository(ApplicationDbContext context)
        : base(context)
    {
    }
}

در ادامه  باید یک کنترلر برای جدول Person بسازیم که بتوانیم عملیات CRUD را پیاده سازی کنیم. میتوان مانند جنریک ریپازیتوری یک جنریک کنترلر بسازیم که عملیات اصلی را پیاده سازی کرده باشد و ما فقط با ارث بری کردن از این جنریک کنترلر عملیات CRUD را بسازیم. برای این کار یک کنترلر به نام GenericController در پوشه Controllers میسازیم. محتوای جنریک کنترلر به صورت زیر است :

public class GenericController<TEntity, IRepository> : Controller
    where TEntity : class, new()
    where IRepository : IGenericRepository<TEntity>
{
    private readonly IRepository _repository;

    public GenericController(IRepository repository)
    {
        _repository = repository;
    }

    public async Task<IActionResult> Index(CancellationToken cancellationToken)
    {
        var data = await _repository.GetAll(cancellationToken);
        return View(data);
    }
    public IActionResult Create()
    {
        return View();
    }
    [HttpPost, ValidateAntiForgeryToken]
    public async Task<IActionResult> Create(TEntity entity, CancellationToken cancellationToken)
    {
        if (ModelState.IsValid)
        {
            await _repository.AddAsync(entity, cancellationToken);
        }
        else return View(entity);
        return RedirectToAction(nameof(Index));
    }
    public async Task<IActionResult> Edit(int id, CancellationToken cancellationToken)
    {
        var data = await _repository.GetByIdAsync(cancellationToken, id);
        return View(data);
    }
    [HttpPost, ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(TEntity entity, CancellationToken cancellationToken)
    {
        if (ModelState.IsValid)
        {
            await _repository.UpdateAsync(entity, cancellationToken);
        }
        else return View(entity);
        return RedirectToAction(nameof(Index));
    }
    [HttpGet]
    public async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
    {
        return View(await _repository.GetByIdAsync(cancellationToken, id));
    }
    [HttpPost, ValidateAntiForgeryToken]
    public async Task<IActionResult> Delete(TEntity entity, CancellationToken cancellationToken)
    {
        await _repository.DeleteAsync(entity, cancellationToken);
        return RedirectToAction(nameof(Index));
    }
}

جنریک کنترلر دو تایپ ورودی قبول میکند که یکی از ورودی ها جدول مورد نظر و ورودی دیگر ریپازیتوری مورد نیاز برای عملیات CRUD مربوط به جدول ورودی میباشد.

در ادامه باید مانند جنریک ریپازیتوری یک کنترلر به نام PersonController ایجاد کنیم و با ارث بری کردن از GenericController عملیات CRUD را پیاده سازی کنیم.

public class UserController : GenericController<Person, IPersonRepository>
{
    public UserController(IPersonRepository personRepository)
        : base(personRepository)
    {
    }
}

در ادامه کلاس Startup را به صورت زیر پیاده سازی میکنیم.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; set; }
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
        {
            options.UseSqlServer(Configuration.GetConnectionString("Connection"));
        });
        services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
        services.AddScoped<IPersonRepository, PersonRepository>();
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.UseMvcWithDefaultRoute();
    }
}

دانلود کد ;)