Integrate Generated Code With Hand-Written Features

If we build code generators for a standardised solution, we will sooner or later need something special that does not fit into the generator. When that happens, we do not need to forfeit the benefits of our automation approach. Instead, we can build on top with hand-written code. Let us explore how we can do this safely.

 

The reason for partial classes and interfaces

A few weeks ago, I wrote that we always should generate partial classes. Thay may have looked like a useless overhead, but now we can use that extra keyword to our advantage.

The benefit that partial classes offer us is that we can split a class definition over multiple files. We have our generated *.g.cs file and can now add a hand-written *.cs file for the same class next to the generated code.

What works with partial classes does also works with partial interfaces. Then when we define the methods in the interface, we need a way to separate the hand-written methods from the generated ones – not only in the class itself, but also in the interface. Then as with the generated classes, the generated interfaces will also be overwritten when we run the generator again.

If we want to add hand-written code to a generated class, we create a new partial class and most often a partial interface.

 

Extend our Customer table

To get some real-world use case, we need a bit more data inside our Customer table. We can add these new columns:

After extending our table, we can run our three code generators in this order to update the generated code:

  1. ClassGeneratorFromDb.tt
  2. RepositoryGeneratorFromDb.tt
  3. IntegrationTestGeneratorFromDb.tt

We need to update the new fields in the CustomerGenerator.cs by hand to get some useful values for our tests:

 

Extend the Customer class

It would be great if we could add a method to the Customer class that gives us back a formatted address string. We cannot add this method to the generated code, then that would be gone if we regenerate the entity. Instead, we create a Customer.cs file next to the Customer.g.cs and add the method there:

Since our Customer class has no interface, we do not need to create one.

We should make sure that the code behaves as it should by adding a unit test in the folder FromDb\UnitTest and name it CustomerTests.cs:

With that we can prevent bugs from showing up when we change the generator or rename the fields.

 

Extend the repository

If we want to search Customers by a country code, we can create a method in the repository. But as with the Customer.g.cs class, we cannot put it into the generated code. Instead, we add it to a new file CustomerRepository.cs next to CustomerRepository.g.cs:

Our generated repositories have interfaces, so we need to create a partial interface for our new method and name that new file ICustomerRepository.cs and put it next to ICustomerRepository.g.cs:

We can create a partial class for our integration tests and put the code in the file CustomerRepositoryTests.cs next to the generated one:

Our partial integration test class can reuse the setup code from the generated integration test file and does not need its own setup code.

If you dislike the long SQL statement in the hand-written method, you can always create a variable with the statement until the WHERE clause. You then can access that generated part from the hand-written method and save yourself the repetitive work of reproducing the SQL query.

 

Run the tests

Make sure to run the tests, then otherwise we have a test but still may not notice a bug:

Our new tests pass as well.

 

Next

We now have additional methods available to us that build on top of our automation work that will not get deleted when we rerun our code generators. The tests help us to show how to use the new methods and they will prevent us from breaking the code with future changes.

Next week we end this series with more ideas on how to automate smaller parts with T4 and look beyond T4 templates for other automation opportunities.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.