GenericServices Design Philosophy + tips and techniques

Last Updated: July 31, 2020 | Created: April 3, 2019

I read Jimmy Bogard’s article called “AutoMapper’s Design Philosophy”, which he wrote to help people understand what job AutoMapper was designed to do. This sort of article helps people who get frustrated with AutoMapper because they are trying to use it to do something it wasn’t really designing to do. I use AutoMapper in my libraries and I was glad to see my usage is right in line with what AutoMapper is designed to do.

I thought I would write a similar article for my GenericServices library (see summary for what GenericServices does) to help anyone who uses, or wants to use my GenericServices library (and associated libraries). While the use of my GenericServices library is tiny compared to AutoMapper (about 0.05% of AutoMapper’s downloads!) I too have issues or requests for features that don’t fit into what GenericServices’s is designed to do. Hopefully this article will help people understand my GenericServices library better, and I also add a few tips and techniques that I have found useful too.

Other related articles in this series:

TL; DR; – Summary

  • The GenericServices library is designed to speed up the development of building front-end Entity Framework 6 and Entity Framework Core (EF Core) databases accesses.
  • GenericServices does this by automating the mapping of database classes to DTOs (Data Transfer Object, also known as a ViewModel in ASP.NET) in a way that builds efficient database accesses.
  • My personal experience I would say that my GenericServices library saved me 2 months of development time over a 12-month period.
  • GenericServices also has a feature where it can work with Domain-Driven Design (DDD) styled EF Core database classes. It can find and call methods or constructors inside a DDD-styled EF Core database class. That gives very good control over creates and updates.
  • This article tells you what GenericServices can, and cannot, do.
  • I then list five GenericServices tips and techniques that I use when using this library.

Setting the scene – what does GenericServices do?

TIP: This is a shortened version of a section from the introduction article on EFcore.GenericServices. The original has more code in it.

GenericServices is designed to make writing front-end CRUD (Create, Read, Update and Delete) EF Core database accesses much easier. It handles both the database access code and the “adaption” of the database data to what the front-end of your application needs. It does this by providing a library with methods for Create, Read, Update and Delete that uses either a EF Core database class or a DTO (Data Transfer Object, also known as a ViewModel in ASP.NET) to define what EF Core class is involved and what data needs to be read or written.

I’m going to take page used to update a database class to describe the typical issues that come up. My example application is an e-commerce site selling technical books and I implement a feature where an authorised user can add a sales promotion to a book, by reducing its price. The ASP.NET Core web page is shown below with the user’s input in red and comments on the left about the Book properties and how they are involved in the update.

In a web/mobile application a feature like this consists of two stages:

1. Read data to display

The display to the user needs five properties taken from the Book and I use a DTO (Data Transfer Object, also known as a ViewModel) than contains the five properties I want out of the Book entity class. GenericServices uses AutoMapper to build a query using LINQ which EF Core turns into an efficient SQL SELECT command that just reads the five columns. Below is the DTO, with the empty interface ILinkToEntity<TEntity> (see line 1) that GenericServices uses to find and map the DTO to the EF Core classes.

public class AddPromotionDto : ILinkToEntity<Book>
{
    [HiddenInput]
    public int BookId { get; set; }

    [ReadOnly(true)] // Tells GenericServices not copy this back to the database
    public decimal OrgPrice { get; set; }

    [ReadOnly(true)] //Tells GenericServices not copy this back to the database
    public string Title { get; set; }

    public decimal ActualPrice { get; set; }

    [Required(AllowEmptyStrings = false)]
    public string PromotionalText { get; set; }
}

Below is the GenericService code that reads in data into the DTO, with the id holding the Book’s primary key (see this link for full list of all the code)

 
var dto = _service.ReadSingle<AddPromotionDto>(id);

NOTE: AutoMapper is great for “Flattening” relationships, that is it can pick properties/columns in related classes – see the article “Building high performance database queries using Entity Framework Core and AutoMapper” for more on this.

2. Update the data

The second part is the update of the Book class with the new ActualPrice and the PromotionalText. This requires a) the Book entity to be read in, b) the Book entity to be updated with the two new values, and c) the updated Book entity to be written back to the database. Below is the GenericService code that does this (see this link for full list of all the code)

_service.UpdateAndSave(dto);

Overall the two GenericService calls replaces about 15 lines of hand-written code that does the same thing.

The problem that GenericServices is aimed at solving

I built GenericServices to make me faster at building .NET applications and to remove some of the tedious coding (e.g. LINQ Selects with lots of properties) around building front-end CRUD EF Core database accesses. Because I care about performance I designed the library to build efficient database accesses using the LINQ Select command, i.e. only loading the properties/columns that are needed.

With the release of EF Core, I rewrote the library (GenericServices (EF6) -> EfCore.GenericServices) and added new features to work with a Domain-Driven Design (DDD) styled database classes. DDD-styled database classes give much better control over how creates and updates are done.

GenericServices is meant to make the simple-to-moderate complexity database reads easy to build. It can also handle all deletes and some single-class creates and updates with normal database classes, but because EfCore.GenericServices supports DDD-styled database classes it can call constructors/methods which can handle every type of create or update.

Overall, I find GenericServices will handle more than 60% of all front-end CRUD accesses, but with DDD-styled database classes this goes up to nearly 80%. It’s only the really complex reads/writes that can be easier to write by hand, and some of those write should really be classed as business logic anyway. The trick is to know when to use GenericServices, and when to hand-code the database access – I cover that next.

What EfCore.GenericServices can/cannot handle

OK, let’s get down to the details of what GenericServices can and cannot do, with a list for good/bad usages.

GenericServices is GOOD at:

  • All reads that use flattening (see Note1)
  • All deletes
  • Create/Update
    • Normal (i.e. non-DDD) database classes: of a single class (see Note2)
    • DDD-styled database classes: any create/update.

GenericServices is BAD at:

  • Any read that needs extra EF Core commands like .Include(), .Load(), etc. (see Note1)
  • Create/Update
    • Normal (i.e. non-DDD) database classes: with relationships (see Note2)

Note1: Read – flatten, not Include.

The GenericServices reads are designed for sending to a display page or a Web API, and I can normally implement any such read by using AutoMapper’s “Flattening” feature. However, sometimes the effort to set up special AutoMapper’s configurations (see docs) can take more effort than just hand-coding the read. Don’t be afraid to build your own read queries if this simpler for you.

You cannot use GenericServices for reads that needs .Include(), .Load(), etc. Typically that sort of read is used in business logic and I have a separate library called EfCore.GenericBizRunner for handling that (see articles “A library to run your business logic when using Entity Framework Core” and “Architecture of Business Layer working with Entity Framework (Core and v6)” for more about handling business logic).

NOTE: Using .Include(), Load() or Lazy Loading is inefficient for a simple read as it means you are either loading data you don’t need, and/or making multiple trips to the database, which is slow.

Note2: Create/Update – single or relationships

When using normal (non-DDD) database classes GenericServices will only create/update a single class mapped to the database via EF Core. However, you can get around this because GenericServices is designed to work with a certain style of DDD entity classes, i.e. GenericServices can find and call constructors or methods inside your EF Core class to do a create or update, which allows your code to handle any level of complexity of a create or update.

GenericServices also gives you the option to validate the data that is written to the database (off by default – turn on via the GenericServiceConfig class). This, coupled with DDD constructor/methods, allows you to write complex validation and checks. However, if I think the code is getting too much like business logic then I use EfCore.GenericBizRunner.

Tips and Techniques

Clearly really know my library very well, and I can do things other’s might not think of. This is a list of things I have do that you might find useful. Here is a list to save you scrolling down to see what’s there.

  1. Try using DDD-styled entity classes with EfCore.GenericServices
  2. Don’t try to use GenericServices for business logic database accesses
  3. How to filter, order, page a GenericService read query
  4. Helper library for using GenericServices with ASP.NET Core Web API
  5. How to unit test your GenericServices code

a. Try using DDD-styled entity classes with EfCore.GenericServices

Personally, I have moved over to using DDD-styled database classes with EF Core, so let me explain the differences/advantages of DDD.

Non-DDD classes have properties with public setters, i.e. anyone can alter a property, while DDD-styled classes have private setters which means you must use a constructor or a method to create/update a property/ies. So DDD-styled classes “locks down” any changes so that no one can bypass the create/update code in that class (see my article “Creating Domain-Driven Design entity classes with Entity Framework Core” for more on this).

Yes, DDD-styled database classes do take some getting used to, but it gives you an unparallel level of control over create/update, including altering not only properties but relationships as well. EfCore.GenericServices works with DDD-styled EF Core classes and finds constructors/methods by matching the parameter name/types (see GenericServices DDD docs here).

b. Don’t try to use GenericServices for business logic database accesses

When I think about database accesses in an application I separate them into two types:

  • CRUD database accesses done by the front-end, e.g. read this, update that, delete the other.
  • Business logic database accesses, e.g. create an order, calculate the price, update the stock status.

The two types of accesses are often different – CRUD front-end is about simple and efficient database accesses, while business logic database accesses are about rules and processes. GenericServices is designed for CRUD database for the front-end and won’t do a good job for business logic database accesses – I use my GenericBizRunner library for that.

Sure, it can get hazy as to whether a database access is a simple CRUD access or business logic – for instance is changing the price of an item a simple CRUD update or a piece of business logic? However, there are some actions, like update the stock status which can trigger a restocking order, that are clearly business logic and should be handled separately (see my article “Architecture of Business Layer working with Entity Framework (Core and v6)” on how I handle business logic).

There are two things that GenericService + DDD-styled database classes can’t do:

  1. GenericServices doesn’t support async calls to methods in a DDD-styled database class. I could support it but I held off for now. If I feel I need Async I use my GenericBizRunner, which has very good async handling throughout.
  2. The constructors/methods in a DDD-styled database class can’t easily have dependency injection added (you could, but you would be pushing the whole DDD pattern a bit to far). You might like to read my article “Three approaches to Domain-Driven Design with Entity Framework Core” and make your own mind up as to whether you want to do that.

c. How to filter, order, page a GenericService read query

The EfCore.GenericServices’s ReadManyNoTracked<T>() method returns an IQueryable<T> result, which allows you to filter, order, page the data after it has been projected into the DTO. By adding LINQ commands after the ReadManyNoTracked method EF Core will turn them into efficient SQL commands. You then end the query with something like .ToList() or .ToListAsync() to trigger the database access.

Filtering etc. after the mapping to the DTO normally covers 90% of your query manipulation but what happens if you need to filter or change a read prior to the projection to the DTO? Then you need ProjectFromEntityToDto<TEntity,TDto>(preDtoLinqQuery).

The ProjectFromEntityToDto method is useful if you:

  • Want to filter on properties that isn’t in the DTO version.
  • Want to apply the method .IgnoreQueryFilters() to the entity to turn off any Query Filter on the entity, say if you were using a Query Filters for soft delete.

NOTE: If you are using Query Filters then all the EfCore.GenericServices’s methods obey the query filter, apart from the method DeleteWithActionAndSave. This turns OFF any query filters so that you can delete anything – you should provide an action that checks the user is allowed to delete the specific entry.

d. Helper library for using GenericServices with ASP.NET Core Web Core

I use ASP.NET at lot over the years and I have generated several patterns for handling GenericServices (and GenericBizRunner), especially around Web APIs. I have now packaged these patterns into a companion library called EfCore.GenericServices.AspNetCore.

For ASP.NET MVC and Razor Pages the EfCore.GenericServices.AspNetCore has a CopyErrorsToModelState extension method that copies GenericServices’s status into the ASP.NET Core Model so they become validation errors.

The features for Web API are quite comprehensive.

  • GenericServices supports JSON Patch for updates – see my article “Pragmatic Domain-Driven Design: supporting JSON Patch in Entity Framework Core” for full details of this feature.
  • For Web API it can turn GenericServices’s status into the correct response type, with HTTP code, success/errors parts and any result to send. This makes for very short Web API method with a clearly defined output type for Swagger – see example below
[HttpGet("{id}")]
public async Task<ActionResult<WebApiMessageAndResult<TodoItem>>> 
    GetAsync(int id, [FromServices]ICrudServicesAsync service)
{
    return service.Response(
        await service.ReadSingleAsync<TodoItem>(id));
}

For more on EfCore.GenericServices and ASP.NET Core Web APIs have a look at my article “How to write good, testable ASP.NET Core Web API code quickly

e. How to unit test your GenericServices code

I’m a big fan of unit testing, but I also what to write my tests quickly. I therefore have built-in methods to help to unit test code that uses EfCore.GenericServices. I also have a whole library called EfCore.TestSupport to help with unit testing any code that uses EF Core.

EfCore.GenericServices has a number of methods that will set up the data that GenericServices would normally get via dependency injection (DI). See line 11 for one such method in the code below. The other methods, like SqliteInMemory.CreateOptions on line 5, come from my EfCore.TestSupport library.

[Fact]
public void TestProjectBookTitleSingleOk()
{
    //SETUP
    var options = SqliteInMemory.CreateOptions<EfCoreContext>();
    using (var context = new EfCoreContext(options))
    {
        context.Database.EnsureCreated();
        context.SeedDatabaseFourBooks();

        var utData = context.SetupSingleDtoAndEntities<BookTitle>();
        var service = new CrudServices(context, utData.Wrapped);

        //ATTEMPT
        var dto = service.ReadSingle<BookTitle>(1);

        //VERIFY
        service.IsValid.ShouldBeTrue(service.GetAllErrors());
        dto.BookId.ShouldEqual(1);
        dto.Title.ShouldEqual("Refactoring");
    }
}

I also added a ResponseDecoders class containing a number of extension method to my EfCore.GenericServices.AspNetCore that will turn a Web API response created by that library back into its component parts. This makes testing Web API methods simpler. This link to a set of unit tests gives you an idea of how you could use the extension methods in integration testing.

Also see the unit testing section of my  article “How to write good, testable ASP.NET Core Web API code quickly”.

Conclusion

I hope this article helps people to get the best out of my EfCore.GenericServices library and associated libraries like EfCore.GenericServices.AspNetCore and EfCore.GenericBizRunner. All these libraries were built to make me faster at developing applications, and also to remove some of the tedious coding so I can get on with coding the parts that need real thought.

The important section is “What EfCore.GenericServices can/cannot handle”  which tells you what the library can and cannot do. Also note my comments on the difference between front-end CRUD (GenericServices) and business logic (GenericBizRunner). If you stay in the “sweet spot” of each of these libraries, then they will work well for you. But don’t be afraid to abandon either library and write your own code if it’s easier or clearer – pick the approach that is clear, but fast to develop.

I also hope the tips and techniques will alert you to extra parts of the EfCore.GenericServices library that you might not know about. I used my libraries on many projects and learnt a lot. The list are some things I learnt to look out for and links to other libraries/techniques that help me be a fast developer.

Happy coding.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments