Entity Framework Core 8: Implementing Complex Type and DateOnly TimeOnly support features of Entity Framework Core 8 like

EntityFrameworkCore 8 (EF Core) is a popular Object Relational Mapping (ORM)technology for working with data access. Version-by-version, EF Core has introduced various features for developers. 

In this article, we will see and implement the following two features

1. Complex Types
2. DateOnly & TimeOnly Support

Using Complex Type

While working with the Code-First approach, several times, we need to use the 
nested data structure to group similar properties e.g. if we are creating 
entities for Employee, Person, Student, then The Address is the complex type 
we will need to store an Address for Employees, Persons, and Students. 
In this case, instead of having repeated properties for Addresses like Stree,
City, etc., we can create an Address class with these properties, and then in 
the actual entity class, we can add a property of the type Address. 
Now in EFCore 8, this can be directly mapped with a table. 
The Table will be created with columns mapped with properties added in 
Complex Type. This approach is typically useful when we are
using Domain-Driven-Development where we prefer to create such re-usable 
value objects. In EF Core 7, we used Owned Entities for this. 
Note that seeding the data with complex types is not supported.

Using DateOnly & TimeOnly Support

In previous versions of EF Core to store Date or Time in the table the only 
option we had was to use DateTime properties in our entities. This leads to 
a messy code where we were explicitly writing code to read only the date or 
the time from the DateTime object. But now in EF Core 8, the support 
for DateOnly and TimeOnly types is available. DateOnly and TimeOnly are value 
types that represent a date or time respectively without a time zone.
They are very useful for storing only dates and times without the overhead of 
a manipulating full DateTime object just to read only dates or times. 

Step 1: Open Visual Studio 2022 and create a new Console Application. 
Make sure that the target Framework selected is .NET 8. Name this application 
as CS_EFCore_8.In this project, add the following NuGet Packages

1. Microsoft.EntityFrameworkCore
2. Microsoft.EntityFrameworkCore.SqlServer
3. Microsoft.EntityFrameworkCore.Relational
4. Microsoft.EntityFrameworkCore.Design
5. Microsoft.EntityFrameworkCore.Tools

Step 2: In this project add a folder and name it 'Models'. In this folder, 
add a new class file and name it 'Person.cs'. In this class file, we will 
add code for the Address class and Person class. The Person class will contain 
a property named 'Address' of the type Address class. The Listing 1 shows
the code for the Address and the Person Class.

 public class Address
     public required string FlatOrBungloNo { get; set; }
     public required string Street { get; init; }
     public required string City { get; init; }
     public required string PinCode { get; init; }
 public class Person
     public int PersonId { get; set; }
     public required string PersonName { get; set; }
     public required Address Address { get; set; }

Listing 1: The Address and the Person class

The Address class is a complex type that contains properties for containing 
schema and data for the Address. We can re-use this in any Entity where 
we want to save data for the Address. The feature of EF Core 8 is that now we 
can provide the information to the ModelBuilder class to map the Address complex
type and its properties to the Table migrations that will be generated. 
This approach prevents an unnecessary property name repetition of the code. 
The reusability and maintainability are increased typically in 
the Domain Driven Development (DDD) approach.

Step 3: In the Models folder add a new class file and name it as BirthRecord.cs. 
In this class file, we will add a code for the BirthRecord class. 
This class will define properties of the DateOnly and TimeOnly types. 
As explained above, the EF Core 8 now supports these types to generate mapping. 
Listing 2 shows the code for the BirthRecord class.

public class BirthRecord
    public int BirthId { get; set; }
    public required string BirthName { get; set; }
    public DateOnly DateOfBirth { get; set; }
    public TimeOnly TimeOfBirth { get; set; }

Listing 2: The BirthRecord Class

Step 4: Once we have these entity classes, we need to create a DbContext 
class where we will define a mapping to generate migrations so that using
the Code-First approach the database and tables can be
generated. The code in Listing 3 shows the DbContext class.

internal class EFCore8FeatureContext : DbContext
    public EFCore8FeatureContext()
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=EFCore8Feature;
                Integrated Security=SSPI;TrustServerCertificate=True");

    public DbSet<Person> Person { get; set; }
    public DbSet<BirthRecord> BirthRecord { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
        modelBuilder.Entity<Person>().ComplexProperty(c => c.Address);

        modelBuilder.Entity<BirthRecord>().HasData(new BirthRecord() 
            BirthId = 1,
            BirthName = "Ganesh",
            DateOfBirth = new DateOnly(2022,1,1),
            TimeOfBirth = new TimeOnly(1,0,0)

Listing 3: The EFCore8FeatureContext class

As shown in Listing 3, the class defines DbSet properties for Person and 
BirthRecord so that tables can be generated in a database. 
The important part of the Listing 3 is the 'OnModelCreating()' method. 
This method uses the 'ComplexProperty()' method to specify that the 'Person' 
the entity has the 'Address'  property, this code will tell the EF Core migration 
to search the type for the 'Address' property of the Penson entity and once the 
Address type is found then while generating mapping the 'Address'
the property will be replaced by the properties from the 'Address' class and 
the table will be generated according to it. The code in the method also seed 
data for the 'BirthRecord' class with the DateOnly and TimeOnly types.

Step 5: Open the Command Prompt navigate to the Project folder and run the 
command as shown in Listing 4 to generate migrations

dotnet ef migrations add firstMigration -c 

Listing 4: Migrations Command

This will add the 'Migrations' folder in the project and the 
20240129123349_firstMigration.cs  file generated in it. 
The Listing 5 shows the migration code for the Person entity.

     name: "Person",
     columns: table => new
         PersonId = table.Column<int>(type: "int", nullable: false)
             .Annotation("SqlServer:Identity", "1, 1"),
         PersonName = table.Column<string>(type: "nvarchar(max)", nullable: false),
         Address_City = table.Column<string>(type: "nvarchar(max)", nullable: false),
         Address_FlatOrBungloNo = table.Column<string>(type: "nvarchar(max)", nullable: false),
         Address_PinCode = table.Column<string>(type: "nvarchar(max)", nullable: false),
         Address_Street = table.Column<string>(type: "nvarchar(max)", nullable: false)
     constraints: table =>
         table.PrimaryKey("PK_Person", x => x.PersonId);

Listing 5: The Migration Code Person Entity

As shown in Listing 5, we can see that the 'Address' property of the Person 
class is replaced with properties from the 'Address' class. 

Update the Migrations using the command shown in Listing 6

dotnet ef database update -c CS_EFCore_8.Models.EFCore8FeatureContext

Listing 6: The Update Command

Once this command is executed, Person and BirthRecord tables will be created.

The code for this article can be downloaded from this link.

Conclusion: The EF Core 8 has provided features mostly needed in modern app 

Popular posts from this blog

Uploading Excel File to ASP.NET Core 6 application to save data from Excel to SQL Server Database

ASP.NET Core 6: Downloading Files from the Server

ASP.NET Core 6: Using Entity Framework Core with Oracle Database with Code-First Approach