📜 ⬆️ ⬇️

CRUD Operations Using MVC

From translator


After the publication of the article, an active discussion began. I advise you to read the comments, since the article is not quite correct from a technical point of view. I recommend it as a material for reasoning, and not a guide to action.

Introduction


Today we will get acquainted with the “General repository” pattern (Generic Repository) and the Unit of Work approach in ASP.NET MVC applications. To do this, create an application "Books", which will be implemented to create, retrieve, update and delete data. To simplify the understanding of the material, the example will be built on the use of only one entity of the book.

In the previous article “ CRUD Operations Using the Repository Pattern in MVC ” we have already created an application using the repository for the book entity that implements CRUD operations. But if we want to create a similar application, but at the corporate level, we will have to create a repository for each new entity. Simply put, we will duplicate the code that goes against the DRY approach - therefore, it is necessary to use the “common repository” pattern.
')


Now look at the picture. Two programmers who are wondering: to create duplicate code or to use the existing code again? We already know how to implement the first approach. Therefore, try to understand the second. Go.

Pattern Repository


This pattern is intended to create an abstraction between the data access layer and the business logic of the application. This is a data access pattern that helps to organize a more loosely coupled approach. We create data access logic in a single class or set of data called a repository.
This time we will create a common repository and the Unit of work class, which will create an instance of the repository for each entity. We will create an instance of the UnitOfWork class in the controller, then we will create an instance of the repository, depending on the required entity, and in the end we will use the necessary repository methods.



The following image shows the relationship between the repository and the Entity Framework data context, in which controllers interact with the repository through the Unit of Work, rather than directly through the Entity Framework.
Now let's see what exactly the Unit of Work is responsible for? He is responsible for creating an instance of our DbContext, as a result of which, all all repositories will use the same DbContext for working with the database. That is, the Unit of Work pattern ensures that all repositories work with the same data context.

Implementing a shared repository and unit of work class


Note: In the article, your user interface uses a specific class object, not an interface, since this approach will be described in the next article. For brevity of the code and ease of perception of the example, error handlers have been removed, but if necessary, you can return them yourself.

We create two projects: EF.Core and EF.Data. We will use the “first code” approach. EF.Core will contain entities whose mapping should be in the database. In the EF.Core project, we create two entities: the BaseEntity class, in which we describe common properties for each entity to inherit, and the Book class. Consider in more detail each class.
The code for BaseEntity is as follows

using System; namespace EF.Core { public abstract class BaseEntity { public Int64 ID { get; set; } public DateTime AddedDate { get; set; } public DateTime ModifiedDate { get; set; } public string IP { get; set; } } } 

Next, create a Book class in the Data folder of the EF.Core project, which in turn inherits BaseEntity

 using System; namespace EF.Core.Data { public class Book : BaseEntity { public string Title { get; set; } public string Author { get; set; } public string ISBN { get; set; } public DateTime Published { get; set; } } } 

The project EF.Data contains the entity Book mapping, the repository and the Unit of Work classes. The “first code” approach implies the creation of a data access class that inherits from DbContext, therefore we create the EFDbContext class in which we will rewrite the OnModelCreating () method. This method is called when the context model is initialized and allows for additional configuration, but before it is locked. Code example of this class:

 using System; using System.Data.Entity; using System.Data.Entity.ModelConfiguration; using System.Linq; using System.Reflection; using EF.Core; namespace EF.Data { public class EFDbContext : DbContext { public EFDbContext() : base("name=DbConnectionString") { } public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity { return base.Set<TEntity>(); } protected override void OnModelCreating(DbModelBuilder modelBuilder) { var typesToRegister = Assembly.GetExecutingAssembly().GetTypes() .Where(type => !String.IsNullOrEmpty(type.Namespace)) .Where(type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>)); foreach (var type in typesToRegister) { dynamic configurationInstance = Activator.CreateInstance(type); modelBuilder.Configurations.Add(configurationInstance); } base.OnModelCreating(modelBuilder); } } } 

It should also be recalled that the concept of “first code” follows a convention on configuration, so you need to pass a string to the constructor with the name of the connection and which exactly matches that in the application settings in the App.Config file. After these actions it is possible to automatically connect to the database server.
In the OnModelCreating () method, we use reflection on the entity map to the class of its settings in this particular project.


And now we define the settings for the book entity that will be used when creating the table in the database. The settings will be in the EF.Data project, in the Mapping folder.

The BookMap class looks like this:

 using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.ModelConfiguration; using EF.Core.Data; namespace EF.Data.Mapping { public class BookMap : EntityTypeConfiguration<Book> { public BookMap() { HasKey(t => t.ID); Property(t => t.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); Property(t => t.Title).IsRequired(); Property(t => t.Author).IsRequired(); Property(t => t.ISBN).IsRequired(); Property(t => t.Published).IsRequired(); ToTable("Books"); } } } 

The time has come to create a common repository. To simplify the example, we will not create an interface. All CRUD operations will be implemented in the repository. Also in the repository there will be a parameterized constructor that will take
Context, so when creating an instance of a repository object, all repositories will have the same context. We will use the context saveChanges () method, however, you can use the save () method of the Unit of Work class, since both of them will have the same data context.

The common repository class looks like this:

 using System; using System.Data.Entity; using System.Data.Entity.Validation; using System.Linq; using EF.Core; namespace EF.Data { public class Repository<T> where T : BaseEntity { private readonly EFDbContext context; private IDbSet<T> entities; string errorMessage = string.Empty; public Repository(EFDbContext context) { this.context = context; } public T GetById(object id) { return this.Entities.Find(id); } public void Insert(T entity) { try { if (entity == null) { throw new ArgumentNullException("entity"); } this.Entities.Add(entity); this.context.SaveChanges(); } catch (DbEntityValidationException dbEx) { foreach (var validationErrors in dbEx.EntityValidationErrors) { foreach (var validationError in validationErrors.ValidationErrors) { errorMessage += string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage) + Environment.NewLine; } } throw new Exception(errorMessage, dbEx); } } public void Update(T entity) { try { if (entity == null) { throw new ArgumentNullException("entity"); } this.context.SaveChanges(); } catch (DbEntityValidationException dbEx) { foreach (var validationErrors in dbEx.EntityValidationErrors) { foreach (var validationError in validationErrors.ValidationErrors) { errorMessage += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage); } } throw new Exception(errorMessage, dbEx); } } public void Delete(T entity) { try { if (entity == null) { throw new ArgumentNullException("entity"); } this.Entities.Remove(entity); this.context.SaveChanges(); } catch (DbEntityValidationException dbEx) { foreach (var validationErrors in dbEx.EntityValidationErrors) { foreach (var validationError in validationErrors.ValidationErrors) { errorMessage += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage); } } throw new Exception(errorMessage, dbEx); } } public virtual IQueryable<T> Table { get { return this.Entities; } } private IDbSet<t> Entities { get { if (entities == null) { entities = context.Set<t>(); } return entities; } } } } 

Now you need to create a class Unit of Work. It inherits from the IDisposable interface and will be deleted in each controller. This class will also initialize the DataContext of the application. And the heart of this class will be the Repository () method, which will return the repository for the desired entity, which in turn inherits from BaseEntity. We look:

 using System; using System.Collections.Generic; using EF.Core; namespace EF.Data { public class UnitOfWork : IDisposable { private readonly EFDbContext context; private bool disposed; private Dictionary<string,object> repositories; public UnitOfWork(EFDbContext context) { this.context = context; } public UnitOfWork() { context = new EFDbContext(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public void Save() { context.SaveChanges(); } public virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { context.Dispose(); } } disposed = true; } public Repository<T> Repository<T>() where T : BaseEntity { if (repositories == null) { repositories = new Dictionary<string,object>(); } var type = typeof(T).Name; if (!repositories.ContainsKey(type)) { var repositoryType = typeof(Repository<>); var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), context); repositories.Add(type, repositoryInstance); } return (Repository<t>)repositories[type]; } } } 


MVC application using common repository pattern


We create MVC application (EF.Web). This will be the third project in the solution, which will contain the user interface for the Book entity and the controller for CRUD operations on it. Create a BookController controller. For each CRUD operation, its ActionResult method will be responsible.
First of all, we create an instance of the Unit of Work class for initialization of the required repository for the corresponding entity by the controller constructor.

 using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using EF.Core.Data; using EF.Data; namespace EF.Web.Controllers { public class BookController : Controller { private UnitOfWork unitOfWork = new UnitOfWork(); private Repository<book> bookRepository; public BookController() { bookRepository = unitOfWork.Repository<book>(); } public ActionResult Index() { IEnumerable<book> books = bookRepository.Table.ToList(); return View(books); } public ActionResult CreateEditBook(int? id) { Book model = new Book(); if (id.HasValue) { model = bookRepository.GetById(id.Value); } return View(model); } [HttpPost] public ActionResult CreateEditBook(Book model) { if (model.ID == 0) { model.ModifiedDate = System.DateTime.Now; model.AddedDate = System.DateTime.Now; model.IP = Request.UserHostAddress; bookRepository.Insert(model); } else { var editModel = bookRepository.GetById(model.ID); editModel.Title = model.Title; editModel.Author = model.Author; editModel.ISBN = model.ISBN; editModel.Published = model.Published; editModel.ModifiedDate = System.DateTime.Now; editModel.IP = Request.UserHostAddress; bookRepository.Update(editModel); } if (model.ID > 0) { return RedirectToAction("Index"); } return View(model); } public ActionResult DeleteBook(int id) { Book model = bookRepository.GetById(id); return View(model); } [HttpPost,ActionName("DeleteBook")] public ActionResult ConfirmDeleteBook(int id) { Book model = bookRepository.GetById(id); bookRepository.Delete(model); return RedirectToAction("Index"); } public ActionResult DetailBook(int id) { Book model = bookRepository.GetById(id); return View(model); } protected override void Dispose(bool disposing) { unitOfWork.Dispose(); base.Dispose(disposing); } } } 

Now you need to create a user interface.

Create / Edit Book View


Let's create a general view for creating and editing the book CreateEditBook.cshtml. To select the date of publication of the book use the date picker. The code for it in javascript is:

 (function ($) { function Book() { var $thisthis = this; function initializeAddEditBook() { $('.datepicker').datepicker({ "setDate": new Date(), "autoclose": true }); } $this.init = function () { initializeAddEditBook(); } } $(function () { var self = new Book(); self.init(); }); }(jQuery)) 

And directly CreateEditBook.cshtml .:

 @model EF.Core.Data.Book @{ ViewBag.Title = "Create Edit Book"; } <div class="book-example panel panel-primary"> <div class="panel-heading panel-head">Add / Edit Book</div> <div class="panel-body"> @using (Html.BeginForm()) { <div class="form-horizontal"> <div class="form-group"> @Html.LabelFor(model => model.Title, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.TextBoxFor(model => model.Title, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.ISBN, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.TextBoxFor(model => model.ISBN, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Author, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.TextBoxFor(model => model.Author, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Published, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.TextBoxFor(model => model.Published, new { @class = "form-control datepicker" }) </div> </div> <div class="form-group"> <div class="col-lg-8"></div> <div class="col-lg-3"> @Html.ActionLink("Back to List", "Index", null, new { @class = "btn btn-default" }) <button class="btn btn-success" id="btnSubmit" type="submit"> Submit </button> </div> </div> </div> } </div> </div> @section scripts { <script src="~/Scripts/bootstrap-datepicker.js" type="text/javascript"></script> <script src="~/Scripts/book-create-edit.js" type="text/javascript"></script> } 

After start should see something similar to the picture.


Book List View


This page is an index. Point of entry. Display a list of all books.

 @model IEnumerable<EF.Core.Data.Book> @using EF.Web.Models <div class="book-example panel panel-primary"> <div class="panel-heading panel-head">Books Listing</div> <div class="panel-body"> <a id="createEditBookModal" href="@Url.Action("CreateEditBook")" class="btn btn-success"> <span class="glyphicon glyphicon-plus"></span>Book </a> <table class="table" style="margin: 4px"> <tr> <th> @Html.DisplayNameFor(model => model.Title) </th> <th> @Html.DisplayNameFor(model => model.Author) </th> <th> @Html.DisplayNameFor(model => model.ISBN) </th> <th>Action </th> <th></th> </tr> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Title) </td> <td> @Html.DisplayFor(modelItem => item.Author) </td> <td> @Html.DisplayFor(modelItem => item.ISBN) </td> <td> @Html.ActionLink("Edit", "CreateEditBook", new { id = item.ID }, new { @class = "btn btn-success" }) | @Html.ActionLink("Details", "DetailBook", new { id = item.ID }, new { @class = "btn btn-primary" }) | @Html.ActionLink("Delete", "DeleteBook", new { id = item.ID }, new { @class = "btn btn-danger" }) </td> </tr> } </table> </div> </div> 



If we press the edit button, we will see:


Book Detail View


Details on the selected book.

 @model EF.Core.Data.Book @{ ViewBag.Title = "Detail Book"; } <div class="book-example panel panel-primary"> <div class="panel-heading panel-head">Book Detail</div> <div class="panel-body"> <div class="form-horizontal"> <div class="form-group"> @Html.LabelFor(model => model.Title, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.Title, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Author, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.Author, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.ISBN, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.ISBN, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Published, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.Published, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.AddedDate, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.AddedDate, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.ModifiedDate, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.ModifiedDate, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.IP, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.IP, new { @class = "form-control" }) </div> </div> @using (Html.BeginForm()) { <div class="form-group"> <div class="col-lg-1"></div> <div class="col-lg-9"> @Html.ActionLink("Edit", "CreateEditBook", new { id = Model.ID }, new { @class = "btn btn-primary" }) @Html.ActionLink("Back to List", "Index", null, new { @class = "btn btn-success" }) </div> </div> } </div> </div> </div> 



Delete Book


When you click the delete button, we are taken to a page with general information on the book and a delete confirmation button. We use HttpGet. When you click on the confirmation button, we use HttpPost.

 @model EF.Core.Data.Book @{ ViewBag.Title = "Delete Book"; } <div class="book-example panel panel-primary"> <div class="panel-heading panel-head">Delete Book</div> <div class="panel-body"> <h3>Are you sure you want to delete this?</h3> <h1>@ViewBag.ErrorMessage</h1> <div class="form-horizontal"> <div class="form-group"> @Html.LabelFor(model => model.Title, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.Title, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Author, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.Author, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.ISBN, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.ISBN, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Published, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.Published, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.AddedDate, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.AddedDate, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.ModifiedDate, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.ModifiedDate, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.IP, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.IP, new { @class = "form-control" }) </div> </div> @using (Html.BeginForm()) { <div class="form-group"> <div class="col-lg-1"></div> <div class="col-lg-9"> <input type="submit" value="Delete" class="btn btn-danger" /> @Html.ActionLink("Back to List", "Index", null, new { @class = "btn btn-success" }) </div> </div> } </div> </div> </div> 

Source: https://habr.com/ru/post/238737/


All Articles