CRUD Repository

The CRUD repository class makes easy all CRUD operations, and paged data retrieval on a EF DBSets possibly connected with other DBSets.

The class itself has two generic arguments, the DBContext type and the DBSet type, while all its methods have the ViewModel type, and/or its principal key type as generic arguments. The class constructor accepts the DBContext, the DBSet and also optional "read access" and "modify access"conditions whose main purpose is to filter records the user has right to read and/or modify.

Retrieval operations use the projection on a ViewModel" extension . Customized projections on a specific ViewModel may be declared by calling a DeclareProjection method.

Update operations include: Add<T>, Delete<U>, Update<U>, and UpdateList<U> that accepts a list of ViewModels before and after CRUD modifications and autodetects all Add, Update and Delete.

The SaveChanges() method saves changes to the DB and, if needed, copies the newly created keys of all Added DB models back in their related ViewModels. The UpdateKeys() method may be called whenever changes are saved by another repository, since it performs just the keys copy operation.

Single entities connected with the entity being updated are updated automatically if the ViewModel contains data for updating them (ViewModel property name must be the concatenation of all property names on the path to the connected entity to update). ViewModes decide whih connected property to update by implementing the IUpdateConnections interface whose unique method bool MayUpdate(string prefix) returns true whenever prefix is the name of a property containing a connected entity that must be updated.

The first time a ViewModel is passed for update the code for transferring its data to the corresponding DB model (and connected DB models) is generated dynamically, compiled and the compiled code is cached. Thus all update operations are as fast as handwritten operations.

All CRUD repository operations are implementations of the ICRUDRepository interface methods, that is used by CRUD controllers. Thus CRUD repository or classes inheriting from it may be injecetd into them without writing any extra code.

Since the CRUD repository is used by all detail form and grid examples, please refers to these examples to see it live. The CRUD repository may be used as it is or one may inherit from it to create a DM model specific repository that possibly uses other CRUD repositories. Below an example:

public class ProductRepository : DefaultCRUDRepository<ApplicationDbContextProduct>
{
    private ApplicationDbContext db;
    public ProductRepository(ApplicationDbContext db)
       : base(db, db.Products){this.db = db;}
    public async Task<IEnumerable<AutoCompleteItem>> GetTypes(string search, int maxpages)
    {
        return (await DefaultCRUDRepository.Create(db, db.ProductTypes)
             .GetPage<AutoCompleteItem>(m => m.Display.StartsWith(search), 
                m => m.OrderBy(n => n.Display), 1, maxpages))
             .Data;
    }
    static ProductRepository()
    {
        DeclareProjection(
             m => m.Maintenance == null ?
                new ProductViewModel{} :
                new ProductMaintenanceViewModel
                {
                    MaintenanceYearlyRate = (decimal)m.Maintenance.YearlyRate
                });
        DeclareProjection(
             m => m.Maintenance == null ?
                new ProductViewModelDetail{}:
                new ProductMaintenanceViewModelDetail{
                    MaintenanceYearlyRate = (decimal)m.Maintenance.YearlyRate
                });
    }
}
[HttpGet]
public async Task<IActionResult> IndexBatch(int? page)
{
    int pg = page.HasValue ? page.Value : 1;
    if (pg < 1) pg = 1;
 
    var model = new ProductlistBatchViewModel
    {
        Products = await Repository.GetPage<ProductViewModel>(
        null,
        q => q.OrderBy(m => m.Name),
        pg, 3)
    };
    model.ModifiedProducts = model.Products.Data;
    return View(model);
}
[HttpPost]
public async Task<IActionResult> IndexBatch(ProductlistBatchViewModel model)
{
    if (ModelState.IsValid)
    {
        Repository.UpdateList(false, 
            model.Products.Data, model.ModifiedProducts);
        await Repository.SaveChanges();
        return View(model);
    }
    else
    {
        return View(model);
    }
}
[HttpGet]
public async Task<ActionResult> GetTypes(string search)
{
    var res = search == null || search.Length < 3 ?
        new List<AutoCompleteItem>() :
        await (Repository as ProductRepository).GetTypes(search, 10);
    return Json(res);
}
[HttpGet]
public async Task<IActionResult> IndexBatch(int? page)
{
    int pg = page.HasValue ? page.Value : 1;
    if (pg < 1) pg = 1;
 
    var model = new ProductlistBatchViewModel
    {
        Products = await Repository.GetPage<ProductViewModel>(
        null,
        q => q.OrderBy(m => m.Name),
        pg, 3)
    };
    model.ModifiedProducts = model.Products.Data;
    return View(model);
}
[HttpPost]
public async Task<IActionResult> IndexBatch(ProductlistBatchViewModel model)
{
    if (ModelState.IsValid)
    {
        Repository.UpdateList(false, 
            model.Products.Data, model.ModifiedProducts);
        await Repository.SaveChanges();
        return View(model);
    }
    else
    {
        return View(model);
    }
}
[HttpGet]
public async Task<ActionResult> GetTypes(string search)
{
    var res = search == null || search.Length < 3 ?
        new List<AutoCompleteItem>() :
        await (Repository as ProductRepository).GetTypes(search, 10);
    return Json(res);
}

Fork me on GitHub