EF Core Mapping with TpH, Generic Types and Value Conversion

EF Core has powerful options to map your domain model to a relational database. In this article, I’ll show you how to use the Fluent API to configure a hierarchy of generic classes to map to a single table, and to convert a collection to a store a single string in the database.

Classes with relations

Database tables

The Model

The model that’s defined defines the types GameData, and MoveData. The GameData class specifies a few simple properties as well as a property of type ICollection that specifies a relationship. Using the primary constructor syntax, the C# compiler creates init-only properties and a constructor that initializes the properties. EF Core doesn’t require parameterless constructors. However, relations cannot be assigned using the constructor. The Moves property of type ICollection is initialized with a List. In this scneario, the collection contains types deriving from the MoveData class.

With this sample, the ‘MoveData’ type is of special interest. MoveData is an abstract base class. The generic version MoveData is derived from MoveData. The MoveData class specifies a property of type ICollection that has a collection of fields of a single game move. The type TField is a generic parameter which allows creating different game types. For example, a move can consist of a list of different colors, or with another more complex game type, a list of different shapes and colors. The values of the MoveData class as well as the generic classes deriving from it should be stored in a single table. The field values specified as a collection property should be stored in the same table as a single string.

Models

The types that will be used for the generic type parameter TField are the records ColorField and ShapeAndColorField. ColorField wraps a color specified by a string, ShapeAndColorField wraps two strings specifying a shape and a color. These types will be discussed later in more detail.

EF Core Fluent API

Specifying the mapping of the GameData type is done using the Fluent API. The table the class is mapped to is Games, the primary key and column properties are specified as shown in the following code snippet.

Fluent API for GameData

Specifying a One-to-Many Relation

The relation between GameData and MoveData is specified with the following code snippet. You can specify a one-to-many relation using the HasOne and WithMany methods. Using DeleteBehavior.Cascade specifies that if a game is deleted, the game moves will be deleted as well. There’s a one-to-many relation specified these two types, and a cascade delete. The a game is deleted, all its moves are deleted as well.

Fluent API for MoveData

Using Table per Hierarchy Mapping Strategy

Using the Table per Hierarchy (TpH) TpH hierarchy mapping it’s possible to map a hierarchy of types to a single table. Using the HasDiscriminator method, the column can be specified that will be used as a differentiator column to give EF Core the information which types should be created on reading the data. The HasDiscriminator method is used together with HasValue. HasDiscriminator returns a DiscriminatorBuilder. HasValue is a method of this builder that is used to specify the value that should be stored within the discriminator column when an instance of the specified type is stored. With the sample code, the value color is stored with a MoveData instance, and the value shape is stored with a MoveData instance.

Specifying TpH mapping

> EF Core also supports the Table per Type (TpT) and Table per Concrete Type (TpC) hierarchies. With TpT for every type a different table is created. With TpC for all concrete types a single table is mapped. Thus, abstract types are not mapped to a table.

Converting Values

EF Core supports custom conversions from a .NET type to a column since EF Core 5.0 with the method HasConversion. Using this method, two delegates can be assigned to convert the .NET type to a column, and the other way around.

Before specifying the conversion, the types need to be convertable. The types that should be stored inside a single string are the ColorField and the ShapeAndColorField records. To convert them to a string, the ToString method is overridden:

Overriding ToString

To create a ColorField or ShapeAndColorField instance from a string, the static methods Parse and TryParse can be used. Some types from .NET support these methods, for example the Int32 type to convert a string to a number. One of the new C# language features with C# 11 is the ability to define static members in interfaces. This allows using operators or other static members in generic classes. The IParsable interface defines the Parse and TryParse methods – as a static member.

The ColorField and ShapeAndColorField types implement this interface. The ColorField and ShapeAndColorField types can be converted to a string, and a string can be converted to a ColorField or ShapeAndColorField instance.

Implementing IParsable

> Maybe you’re wondering about the NotNullWhen and MaybeNullWhen attributes. These attributes give hints to the compiler about nullable reference types behavior. These attributes act on the return type of the TryParse method. Using NotNullWhen(true) gives the compiler the informatation if the TryParse method returns true, the argument annotated with NotNullWhen(true) is not null after the method returns. This means that if the variable that’s assigned to the variable s is not null after the method returns with a true value, and the compiler should not complain to require null checks. The TryParse method returns the last paramter with the out modifier. Here, the MaybeNullWhen(false) annotation is applied. If the TryParse method returns false, the compiler should complain about using the out value without a null check. If TryParse fails, the sample implementation returns null. The out type is not declared nullable, so the compiler does not complain about null checks if the TryParse method does not return false.

With the sample code, conversion to and from a string with the ColorField and the ShapeAndColorField is not the only requirement. The MoveData classes uses a collection of these types that needs to be converted. To do this, the MappingExtensions class defines the extension methods ToFieldString and ToFieldCollection. The ToFieldString method invokes the ToString method with every item in the collection and joins the results with a colon. The ToFieldCollection method splits the string on the colon and invokes the Parse method on every item. Because the Parse method is now specified with the IParsable interface, the generic type T can now be used to invoke this method.

MappingExtensions invoking ToString and Parse

Now all what’s needed is to specify the conversion. The next code snippet shows the conversion for the Fields property of the MoveData class to invoke the ToFieldString method using the ICollection fields parameter, ant the ToFieldCollection method using the string fields parameter.

Specifying the conversion with HasConversion

> Instead of converting the ColorField and ShapeAndColorField types to a string, and back, the Fields property can be mapped to a JSON column. How ths feature can be used will be shown in a future article.

Take away

EF Core offers a powerful mapping from the object model to relational databases. It’s not only possible to use constructors with arguments, but also map inherited classes to tables – as shown with the table per hierarchy mapping. The sample code uses generic classes and an abstract base class which all are mapped to the same table. Using value conversions, it’s possible to map complex types to a single value. Value conversion is also a great way to map enum values to a representation in the database.

With this article I’ve also shown a great new C# 11 feature – the ability to define static members in interfaces. The IParsable interface is used in a generic extension method to convert a string to a collection of a generic type by parsing a string to create instances of the generic type.

Enjoy learning and programming!

Christian

If you enjoyed this article, I’m thankful if you support me with a coffee. Thanks!

Buy Me A Coffee

More Information

More information about programming EF Core is available in my book and my workshops.

Read more about EF Core and mapping objects to relations in my book Professional C# and .NET – 2021 Edition

See Chapter 21, "Entity Framework Core".

Trainings

Sample source code

The complete source code of this sample is available on Professional C# source code – see the folder 5_More/EFCore/InhertianceMappingWithConversion

Advertisement

4 thoughts on “EF Core Mapping with TpH, Generic Types and Value Conversion

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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