Generics to automate code production, reusability and conventions

Generics to automate code production, reusability and conventions

Two years ago we faced a problem at Indesys – it involved repeated code in Business Layer entities. We had a number of different approaches to coding conventions for things like creation, validation, retrieving and saving entities to database in a client's project. 

I decided to use the same approach across the entire application. An application usually has many CRUD operations. OK, but in a very complex system which constantly evolves one has to keep tight control over what can enter it as input and what is allowed to leave it as output at every stage of an entity’s lifecycle. Moreover we had stateless and stateful entities, some with and some without the need to have visual representation in a client (by visual representation I mean having a view and a controller/presenter for an entity). Failing to maintain control over input and output would lead to incompatibilities between different parts and modules and failed tests or even failures in end-user tests. 

In an application with hundreds of tables and entities you need conventions, otherwise you're lost

Technologies like LINQ encourage people to quickly write hundreds of functions, but at the same time discourage them from keeping to conventions. They make it easier to write queries for particular problems in an application, but they need to be strongly typed. So in every business entity you tend to repeat same pieces of code - for example retrieving first 30 rows from a table based on a foreign key or state or retrieving a customer by his/her family name.

Here’s an example. Let's say we have three tables/entities: Customer, Account and Address. A customer can have multiple bank accounts and addresses (e.g. one for correspondence, another for office/ warehouse etc). But we've decided that we will use the same conventions to create and retrieve, which will allow the programmers to write code quickly instead of thinking how on earth do we get data from Customer table, or how to retrieve customers based on a user-given set of filters (instead of hard-coded LINQ queries). Of course the number of conventions we've agreed upon was much, much longer, but let's stick to a short list of functions for this article’s purposes.

So what happens when you have so nice auto-generated Entity Framework classes, that you can run LINQ queries against. Well, you very quickly produce a lot of code like this:

These are just examples of methods. The first creates an object, the second saves it after a bit of validation and the remaining two are simple and quickly written and rudimentary filters. 

Technologies like LINQ allow people to become lazy. They make writing hundreds of methods so easy it does not require any meaningful intellectual effort, which can result in different naming and behaviour conventions within each method.

Note that Customer is a class auto-generated by EntityFramework, derived from Customer table in the database, and CustomerClass is a wrapper around it and is intended to be used at Business Layer of the application. Since Customer is auto-generated following the "datebase first" rule we can't modify its code. We can only extend it or wrap around it. Let's add some useful properties like enums for status and gender, and some properties to save, retrieve and convert enums to and from database string field.

The above modifications make it easier to use the table, and show everybody possible values of fields like Gender and Status. This way you don't need additional documentation to see what gender and status can be used in this system.

All is fine up to this point but imagine you have hundreds of tables in your system and a number of people are developing it. Every programmer has a different approach to creation, saving, retrieving, and manipulating of an object whatever the object might be: a customer, an account, a car, a car part, a schedule, etc. 

Generic types, classes, abstract classes, abstract methods and interfaces can save you 

In this article we're touching only some aspects of the problem so expect to return to this topic in future articles. Generic types and classes allow to reduce the necessary amount of code, reuse code and force people to use keep to your predesigned conventions. 

Let's say you want the following conventions used:

  1. after retrieving an object from database you force validation
  2. before saving to database you force validation
  3. reading an object by Primary Key has always the following form: ObjectType GetByID(int ID)

Writing generic class isn't easy, I know, but once you start it gets better.
We have to write an abstract class that would contain basic reusable code for all Business Layer classes. Let's create EntityBase class which we will mark as abstract. It will only serve as a reusable code container

Let me explain what is done here. EntityBase is just a class name which will be used to derive new classes from. Then come two types of parameters: DbType and TEntity. The first one will be used for storing EntityFramework auto-generated class names, and the second will contain our future Business Layer classes. The phrase "where DbType: new()" is required to tell the compiler that we're going to instantiate objects of class DbType in our EntityBase code (in Create method for example). The phrase "TEntity : EntityBase<DbType, TEntity>" serves only as means to inform that TEntity is just an object derived from EntityBase. Later you will see that it all makes sense. 

Our first method is a generic Create method. Believe it or not but it can create objects of all classes like Customer, Account, Address that we have in our project.

Next we have the GetByID method that can retrieve any object of any class from database using one common ESQL query. It works based on the assumption that all business layer classes follow a rule that Primary Key column is named the same as the table with "ID" added as a suffix, e.g.: CustomerID.

Also let's create a common save method that allows validation. It should make it possible to validate any class object and save it in the database afterwards.

Note that ReturnResult is my custom class that contains the result of a method - either simple true or false for success or failure, or true/false AND the retrieved object itself (like in GetByID method).

Use common reusable code, interfaces and abstract methods to force programmers to follow your rules

Final method is an abstract Validation method. It has no code at this point and needs to be implemented before the application can be compiled. Same goes for interfaces (which I will discuss in another article) - they make the program impossible to be compiled until public methods are implemented. Of course the Validate method does not do anything in the generic class, but it makes it possible to compile abstract EntityBase code.

Now that we have our EntityBase generic class finished we can derive all other classes from it. For example NewCustomerClass:

Of course we have to implement a Validate function that is local and specific to Customer.

If we want the Create method to behave in a special way, we can extend its capabilities by writing a new Create method that uses the one found in the EntityBase class:

See how easy it is to create new classes ? Account class ? There you go:

That's it. Just by deriving a new class from EntityBase, you can create hundreds of classes that follow the same rules.

CRUD is a basic behaviour and we should not create it from scratch every time it is needed. 

To make things even better at Indesys we created a code generation tool that can create such derived classes based on selected tables from database, which allows creation of hundreds of classes in a couple of seconds. It recognizes whether the class has status or not and if it requires presenter/controller. It also can create basic code to retrieve lists of objects based on flexible filters instead of stored procedures which I hate and resort to only for complicated reports that SQL handles better. I hate splitting business logic between view (javascript), Business Layer, Data Access Layer and SQL procedures, but that's a very good topic for another article :)

Remember, this code was written for demonstration purposes only. It should not serve as real application code, but rather as an inspiration.

>> Entire application code here for dowload <<

 Regards

Dominik Steinhauf
CEO, .Net developer, software architect
www.indesys.pl

If you need help with your software project, or need customized software for your company, contact me at: dominik.steinhauf@indesys.pl

 

To view or add a comment, sign in

Insights from the community

Explore topics