Most of the applications that have to be developed in practice are reduced to a primitive pattern: there is a certain subject area in which the objects and the connections between them are highlighted. All this is easily represented as tables in the database, and the basic functionality of the application is to perform
four basic operations on these tables: creating, modifying, viewing and deleting objects. Further, usually, additional business logic, the reporting module and the rest of the necessary functionality are screwed on this basis.
The natural reaction of the developer’s body to the presence of a particular pattern is the desire to automate its use, for example, using code generation. Joke. Code generation is the same copy-paste method, only a specially written tool does it for the programmer. Sometimes this is justified, but before you decide on the code generation, it is better to think well, is it possible to do with OOP, for example?
Prehistory
Recently, I had to help friends in writing such an application for, say, a course project of a team of students. In short, the task was to write a web application that allowed generating database reports from approximately twenty tables. The difficulty was that the base was not there yet, it needed to be designed and enter a decent amount of data manually, and the creation of the application and filling the base was about a week, after which it was necessary to show the working prototype to the “customer”. After the demonstration, it was planned to decorate the interface, expand the business logic, so the application architecture should have been as flexible as possible. The situation was complicated by the fact that only I had real programming experience, the other participants in the project at the first stage can only help with filling the base or, as a maximum, with design and layout. Before rushing to the embrasure, I sat down and thought for a couple of hours how to write a CRUD basis for the application in the shortest possible time, with which the rest can work. In this article I will try to voice some thoughts that have helped me a lot in solving the problem and may be useful for relatively inexperienced developers. Experienced, most likely, themselves have repeatedly applied similar patterns in practice.
Due to historical circumstances (two years of experience as a .Net developer), the application was based on a combination of MS SQL Server 2008 / ADO.Net Entity Framework / ASP.Net MVC. I deliberately omit some moments in the article, such as “how to create a table in the database” or “how to add an ADO.NET Entity Data Model to the project,” you can read about it in other articles. This is about how, correctly applying the necessary tools, you can quickly create a CRUD basis for the application, with which you can then easily work.
Database
So, as I said above, for all objects in the database, four basic operations must be provided for: create, read, update, delete. Accordingly, this should be taken into account from the very beginning when the database is being designed. In the database, this was expressed in the fact that each table contained the primary key “Id”:
Id int not null identity (1,1)
* This source code was highlighted with Source Code Highlighter .
Such a general approach will greatly simplify our lives in the future, you will soon see why. Next, we sit down and carefully and carefully create tables in the database. Hereinafter I will show everything on the example of two of them:

The rest of the tables are not significantly different.
')
ORM
So, the tables and links between them are created. Add an ADO.NET Entity Data Model to the project, first pointing out our database to it, and the EF generates the ORM code. Now remember that all our objects have an Id field that identifies the object. Add this interface to the code:
public interface IDataObject
{
int Id { get ; }
string DisplayName { get ; }
}
* This source code was highlighted with Source Code Highlighter .
All objects of the model in EF are marked as partial by default, so we can add all the necessary functionality to them (for example, inherit them from the required interface) without affecting the generated code:
public partial class HomeWorkUnitType : IDataObject
{
public string DisplayName
{
get { return Name; }
}
}
public partial class HomeWorkUnit : IDataObject
{
public string DisplayName
{
get { return string .Format( "[{0}] {1}" , Id, Theme); }
}
}
* This source code was highlighted with Source Code Highlighter .
The DisplayName property is useful when it comes to the UI.
Controllers
So, the next step - for each model object you need to create a controller that will support five operations - issuing a list of objects, viewing information about a single object, creating, editing and deleting an object. All our objects are inherited from the IDataObject interface and the internal class EF System.Data.Objects.DataClasses.EntityObject. Let us try to bring out as much logic as possible to the base controller class, from which we will inherit the controllers for each model object:
public abstract class DataObjectController<T> : Controller
where T : EntityObject, IDataObject
{
}
* This source code was highlighted with Source Code Highlighter .
Each controller will use the data context. We implement it as a protected property using lazy initialization (in some actions it is not used, why create it again?):
private DataModelEntities m_DataContext;
protected DataModelEntities DataContext
{
get
{
if (m_DataContext == null )
{
m_DataContext = new DataModelEntities();
}
return m_DataContext;
}
}
* This source code was highlighted with Source Code Highlighter .
To work with a specific object of type T, we add two abstract protected properties:
protected abstract IQueryable<T> Table { get ; }
protected abstract Action<T> AddObject { get ; }
* This source code was highlighted with Source Code Highlighter .
Unfortunately, I did not find a normal way to determine these two properties at the base class level, so all controllers returned them depending on the type of object with which they work.
Next, you need to implement the basic CRUD operations. Here is the final version of the base controller code that allows you to perform basic operations:
public abstract class DataObjectController<T> : Controller
where T : EntityObject, IDataObject
{
private DataModelEntities m_DataContext;
protected DataModelEntities DataContext
{
get
{
if (m_DataContext == null )
{
m_DataContext = new DataModelEntities();
}
return m_DataContext;
}
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base .OnActionExecuted(filterContext);
if (ViewData[ "Error" ] == null )
{
foreach ( var entry in DataContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Deleted | EntityState.Added))
{
throw new InvalidOperationException( "Unsaved entries at data context!" );
}
}
}
protected abstract IQueryable<T> Table { get ; }
protected abstract Action<T> AddObject { get ; }
protected virtual IEnumerable <T> GetAll()
{
foreach ( var t in Table.AsEnumerable())
{
LoadAllDependedObjects(t);
yield return t;
}
}
protected virtual T GetById( int id)
{
var t = Table.First(obj => obj.Id == id);
LoadAllDependedObjects(t);
return t;
}
protected virtual void CreateItem(T data)
{
AddObject(data);
}
protected virtual T EditItem(T data)
{
T existing = GetById(data.Id);
DataContext.ApplyPropertyChanges(existing.EntityKey.EntitySetName, data);
return existing;
}
protected virtual void DeleteItem( int id)
{
DataContext.DeleteObject(GetById(id));
}
public virtual ActionResult Index()
{
return View(GetAll());
}
public virtual ActionResult Details( int id)
{
return View(GetById(id));
}
public virtual ActionResult Create()
{
LoadAllDependedCollections();
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public virtual ActionResult Create(T data)
{
try
{
ValidateIdOnCreate();
ValidateModel();
CreateItem(data);
DataContext.SaveChanges();
return RedirectToAction( "Index" );
}
catch (Exception ex)
{
LoadAllDependedCollections();
ViewData[ "Error" ] = ex.JoinMessages();
return View(data);
}
}
public virtual ActionResult Edit( int id)
{
LoadAllDependedCollections();
return View(GetById(id));
}
[AcceptVerbs(HttpVerbs.Post)]
public virtual ActionResult Edit(T data)
{
try
{
ValidateModel();
data = EditItem(data);
DataContext.SaveChanges();
return RedirectToAction( "Index" );
}
catch (Exception ex)
{
LoadAllDependedCollections();
ViewData[ "Error" ] = ex.JoinMessages();
return View(data);
}
}
public virtual ActionResult Delete( int id)
{
try
{
ValidateModel();
DeleteItem(id);
DataContext.SaveChanges();
}
catch (Exception ex)
{
ViewData[ "Error" ] = ex.JoinMessages();
}
return RedirectToAction( "Index" );
}
protected void ValidateModel()
{
if (!ModelState.IsValid)
{
throw new Exception( "Model contains errors." );
}
}
protected virtual void LoadAllDependedCollections()
{
}
protected virtual void LoadAllDependedObjects(T obj)
{
}
protected virtual void ValidateIdOnCreate()
{
ModelState[ "Id" ].Errors.Clear();
}
}
* This source code was highlighted with Source Code Highlighter .
Simple Object Controllers
Most of the methods are marked as virtual, so that at the level of the implementation of the controller for a particular type of object it was not difficult to modify their behavior. Here is an example of a child controller for a simple object that is not associated with others in the database (the table does not contain foreign keys):
public class HomeWorkUnitTypeController : DataObjectController<HomeWorkUnitType>
{
protected override IQueryable<HomeWorkUnitType> Table
{
get { return DataContext.HomeWorkUnitType; }
}
protected override Action<HomeWorkUnitType> AddObject
{
get { return DataContext.AddToHomeWorkUnitType; }
}
}
* This source code was highlighted with Source Code Highlighter .
Complex Object Controllers
With object controllers whose tables contain foreign keys, everything is a little more complicated. When they are displayed on the UI, instead of identifiers of related objects, you need to display them with a DisplayName, which involves loading the associated object from the database, and when creating and editing it, let the user select the related object from the list of existing objects. In order to deal with the problem of related objects and virtual methods were created:
protected virtual void LoadAllDependedCollections()
{
}
protected virtual void LoadAllDependedObjects(T obj)
{
}
* This source code was highlighted with Source Code Highlighter .
The first loads all objects from related tables to display a list on the interface, and the second starts a lazy initialization for related objects of a particular object.
Another problem is that the basic implementation of the Edit and Create methods does not know how to link objects, therefore complex methods will have to do these methods manually. The implementation of the controller for an object with foreign keys is as follows:
public class HomeWorkUnitController : DataObjectController<HomeWorkUnit>
{
protected override IQueryable<HomeWorkUnit> Table
{
get { return DataContext.HomeWorkUnit; }
}
protected override Action<HomeWorkUnit> AddObject
{
get { return DataContext.AddToHomeWorkUnit; }
}
protected override void LoadAllDependedCollections()
{
ViewData[ "DisciplinePlan" ] = DataContext.DisciplinePlan.AsEnumerable();
ViewData[ "HomeWorkUnitType" ] = DataContext.HomeWorkUnitType.AsEnumerable();
base .LoadAllDependedCollections();
}
protected override void LoadAllDependedObjects(HomeWorkUnit obj)
{
obj.DisciplinePlanReference.Load();
obj.HomeWorkUnitTypeReference.Load();
base .LoadAllDependedObjects(obj);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateHomeWorkUnit(HomeWorkUnit data, int disciplinePlanId, int workTypeId)
{
try
{
ValidateIdOnCreate();
ValidateModel();
CreateItem(data);
data.DisciplinePlan = DataContext.DisciplinePlan.First(d => d.Id == disciplinePlanId);
data.HomeWorkUnitType = DataContext.HomeWorkUnitType.First(c => c.Id == workTypeId);
DataContext.SaveChanges();
return RedirectToAction( "Index" );
}
catch (Exception ex)
{
LoadAllDependedCollections();
ViewData[ "Error" ] = ex.JoinMessages();
return View( "Create" , data);
}
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditHomeWorkUnit(HomeWorkUnit data, int disciplinePlanId, int workTypeId)
{
try
{
ValidateModel();
data = EditItem(data);
data.DisciplinePlan = DataContext.DisciplinePlan.First(d => d.Id == disciplinePlanId);
data.HomeWorkUnitType = DataContext.HomeWorkUnitType.First(c => c.Id == workTypeId);
DataContext.SaveChanges();
return RedirectToAction( "Index" );
}
catch (Exception ex)
{
LoadAllDependedCollections();
ViewData[ "Error" ] = ex.JoinMessages();
return View( "Edit" , data);
}
}
}
* This source code was highlighted with Source Code Highlighter .
Now it remains for the small - to generate views (Views) for each of the created controllers. How to make this process faster and more enjoyable, I will tell in the next article.
Thanks for attention.
PS The article deliberately omitted issues of security, speed, etc., undoubtedly important things that are not directly related to the subject of conversation.
