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; }
}
[Table("Person")]
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
{
[Key]
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
CS_EFCore_8.Models.EFCore8FeatureContext
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.
migrationBuilder.CreateTable(
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
development.