From 753f7e39266c0754a528bb9fc3e7c2ce13d7ad9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sun, 5 Nov 2023 15:12:05 +0300 Subject: [PATCH 1/4] Initial post: Using Complex Types as Value Objects with Entity Framework Core 8.0 --- .../POST.MD | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD diff --git a/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD b/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD new file mode 100644 index 0000000000..5157bb7eac --- /dev/null +++ b/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD @@ -0,0 +1,72 @@ +# Using Complex Types as Value Objects with Entity Framework Core 8.0 + +Entity Framework Core 8.0 is being shipped in a week as a part of .NET 8.0. In this article, I will introduce the new **[Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types)** feature of EF Core 8, and show some examples of how you can use it in your projects built with ABP Framework. + +## What is a Value Object? + +A [Value Object](https://docs.abp.io/en/abp/latest/Value-Objects) is a simple object that has no conceptual identity (Id). A Value Object is typically owned by a parent [Entity](https://docs.abp.io/en/abp/latest/Entities) object. Using [Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types) with EF Core 8 is the best way to create Value Objects that are stored as a part of your main entity. + +## Creating an ABP Project + +I will show code examples, so I am creating a new ABP project using the following [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) command: + +````bash +abp new ComplexTypeDemo -t app-nolayers +```` + +> I prefer a non-layered project to keep the things simple. If you are new to ABP Framework, follow the [Getting Started](https://docs.abp.io/en/abp/latest/Getting-Started-Overall) tutorial to learn how to create a new project from scratch. + +Once I created the solution, I am running the following command to execute the database migrations in order to create the initial database: + +````bash +dotnet run --project ComplexTypeDemo --migrate-database +```` + +> We could also execute the `migrate-database.ps1` (that is coming as a part of the solution) as a shortcut. + +## Creating a Complex Type + +Assume that we have a `Customer` [entity](https://docs.abp.io/en/abp/latest/Entities) as shown below: + +````csharp +public class Customer : AggregateRoot +{ + public int Id { get; set; } + public string Name { get; set; } + public Address HomeAddress { get; set; } + public Address BusinessAddress { get; set; } +} +```` + + + +Let's see the most popular example, an `Address` class: + +````csharp +public class Address +{ + public string Country { get; set; } + public string City { get; set; } + public string Line1 { get; set; } + public string? Line2 { get; set; } + public string PostCode { get; set; } +} +```` + +Once we define such an `Address` class, we can use it as a part of a `Customer` object: + +````csharp +public class Order +{ + public int Id { get; set; } + public required string Contents { get; set; } + public required Address ShippingAddress { get; set; } + public required Address BillingAddress { get; set; } + public Customer Customer { get; set; } = null!; +} +```` + + + +d + From 344d3db89921094e9753380cd98fe3f50e4a6cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sun, 5 Nov 2023 15:59:32 +0300 Subject: [PATCH 2/4] Added sections to the article: EF-Core-8-Complex-Types --- .../POST.MD | 117 +++++++++++++++--- 1 file changed, 103 insertions(+), 14 deletions(-) diff --git a/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD b/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD index 5157bb7eac..394fd80f32 100644 --- a/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD +++ b/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD @@ -8,6 +8,10 @@ A [Value Object](https://docs.abp.io/en/abp/latest/Value-Objects) is a simple ob ## Creating an ABP Project +> **WARNING**: ABP Framework has not a .NET 8.0 compatible version yet. It is planned to be released on November 15, 2023. I created this project with ABP 7.4.1 (based on .NET 7.0), then manually changed the `TargetFramework` (in the `csproj`) to `net8.0` and added the `Microsoft.EntityFrameworkCore.SqlServer` package with version `8.0.0-rc.2.23480.1`. After that, the project is being compiled, but some parts of this article may not work as expected until ABP Framework 8.0-RC.1 is released. +> +> **I will update the article once ABP Framework 8.0-RC.1 is released.** + I will show code examples, so I am creating a new ABP project using the following [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) command: ````bash @@ -29,23 +33,19 @@ dotnet run --project ComplexTypeDemo --migrate-database Assume that we have a `Customer` [entity](https://docs.abp.io/en/abp/latest/Entities) as shown below: ````csharp -public class Customer : AggregateRoot +public class Customer : BasicAggregateRoot { - public int Id { get; set; } public string Name { get; set; } public Address HomeAddress { get; set; } public Address BusinessAddress { get; set; } } ```` - - -Let's see the most popular example, an `Address` class: +Here, we have two address properties, one for home and the other one for business. Since an address is a multi-values property, we can define it as a separate object: ````csharp public class Address { - public string Country { get; set; } public string City { get; set; } public string Line1 { get; set; } public string? Line2 { get; set; } @@ -53,20 +53,109 @@ public class Address } ```` -Once we define such an `Address` class, we can use it as a part of a `Customer` object: +`Address` is a typical complex object. It is actually a part of the `Customer` object, but we wanted to collect its parts into a dedicated class to make it a domain conceptual and easily manage its properties. + +## Configure EF Core Mappings + +We should set the `Address` class as a Complex Type in our EF Core mapping configuration. There are two ways of it. + +As the first way, we can add the `ComplexType` attribute on top of the `Address` class: + +````csharp +[ComplexType] // Added this line +public class Address +{ + ... +} +```` + +As an alternative, we can configure the mapping using the fluent mapping API. You can write the following code into the `OnModelCreating` method of your `DbContext` class: + +````csharp +builder.Entity(b => +{ + b.ToTable("Customers"); + b.ComplexProperty(x => x.HomeAddress); // Mapping a Complex Type + b.ComplexProperty(x => x.BusinessAddress); // Mapping another Complex Type + //... configure other properties +}); + +```` + +You can further configure the properties of the `Address` class: + +````csharp +b.ComplexProperty(x => x.HomeAddress, a => +{ + a.Property(x => x.City).HasMaxLength(50).IsRequired(); +}); +```` + +Once you configure the mappings, you can use the [EF Core command-line tool](https://learn.microsoft.com/en-us/ef/core/cli/dotnet) to create a database migration: + +````bash +dotnet ef migrations add "Added_Customer_And_Address" +```` + +And update the database: + +````bash +dotnet ef database update +```` + +If you check the fields of the `Customers` table in your dayabase, you will see the following fields: + +* `Id` +* `Name` +* `HomeAddress_City` +* `HomeAddress_Line1` +* `HomeAddress_Line2` +* `HomeAddress_PostCode` +* `BusinessAddress_City` +* `BusinessAddress_Line1` +* `BusinessAddress_Line2` +* `BusinessAddress_PostCode` + +As you see, EF Core stores the `Address` properties as a part of your main entity. + +## Querying Objects + +You can query entities from database using the properties of a complex type as same as you query by the properties of the main entity. + +**Example: Find customers by `BusinessAddress.PostCode`:** ````csharp -public class Order +public class MyService : ITransientDependency { - public int Id { get; set; } - public required string Contents { get; set; } - public required Address ShippingAddress { get; set; } - public required Address BillingAddress { get; set; } - public Customer Customer { get; set; } = null!; + private readonly IRepository _customerRepository; + + public MyService(IRepository customerRepository) + { + _customerRepository = customerRepository; + } + + public async Task DemoAsync() + { + var customers = await _customerRepository.GetListAsync( + c => c.BusinessAddress.PostCode == "12345" + ); + + //... + } } ```` -d +## Closing Notes + +* + +## Source Code + +You can get the completed project here: https://github.com/hikalkan/samples/tree/master/EfCoreComplexTypeDemo + +## References + +* ... From e35ea875f8652a768710e215c5b5e7dc6566d348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sun, 5 Nov 2023 16:46:53 +0300 Subject: [PATCH 3/4] Update POST.MD --- .../POST.MD | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD b/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD index 394fd80f32..dc2383e038 100644 --- a/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD +++ b/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD @@ -145,11 +145,32 @@ public class MyService : ITransientDependency } ```` +## Mutable vs Immutable +Entity Framework Core Complex Types can work with mutable and immutable types. In the `Address` example above, I've shown a simple mutable class - that means you can change an individual property of an `Address` object after creating it (or after querying from database). However, designing Value Objects as immutable is a highly common approach. -## Closing Notes +For example, you can use C#'s `struct` type to define an immutable `Address` type: -* +````csharp +public readonly struct Address(string line1, string? line2, string city, string postCode) +{ + public string City { get; } = city; + public string Line1 { get; } = line1; + public string? Line2 { get; } = line2; + public string PostCode { get; } = postCode; +} +```` + +See the [Microsoft's documentation](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#mutability) for more examples and different usages. + +## Final Notes + +There are more details about using Complex Types in your applications. I want to mention some of them here: + +* You can have nested complex types. For example, `Address` may have one or more `PhoneNumber` objects as its properties. Everything will work seamlessly. +* A single instance of a Complex Type can be set to multiple properties (of the same or different entities). In that case, changing the Complex object's properties will affect all of the properties. However, try to avoid that since it may create unnecessary complexities in your code that is hard to understand. +* You can manipulate mutable complex object properties just as another property in your entity. EF Core change tracking system will track them as you expect. +* Currently, EF Core doesn't support to have a collection of complex objects in an entity. It works only for properties. ## Source Code @@ -157,5 +178,6 @@ You can get the completed project here: https://github.com/hikalkan/samples/tree ## References -* ... +* [Value objects using Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types) in [What's new with EF Core 8.0](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew) document by Microsoft. +* [ABP Entity Framework Core integration document](https://docs.abp.io/en/abp/latest/Entity-Framework-Core) From 981b89dba7c53cef90a060bf3037f8f580587810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sun, 5 Nov 2023 17:47:01 +0300 Subject: [PATCH 4/4] Finalize the article Using Complex Types as Value Objects with Entity Framework Core 8.0 --- .../2023-11-05-EF-Core-8-Complex-Types/POST.MD | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD b/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD index dc2383e038..06289387e5 100644 --- a/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD +++ b/docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD @@ -1,10 +1,12 @@ # Using Complex Types as Value Objects with Entity Framework Core 8.0 -Entity Framework Core 8.0 is being shipped in a week as a part of .NET 8.0. In this article, I will introduce the new **[Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types)** feature of EF Core 8, and show some examples of how you can use it in your projects built with ABP Framework. +Entity Framework Core 8.0 is being shipped in a week as a part of .NET 8.0. In this article, I will introduce the new **[Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types)** feature of EF Core 8 and show some examples of how you can use it in your projects built with ABP Framework. ## What is a Value Object? -A [Value Object](https://docs.abp.io/en/abp/latest/Value-Objects) is a simple object that has no conceptual identity (Id). A Value Object is typically owned by a parent [Entity](https://docs.abp.io/en/abp/latest/Entities) object. Using [Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types) with EF Core 8 is the best way to create Value Objects that are stored as a part of your main entity. +A [Value Object](https://docs.abp.io/en/abp/latest/Value-Objects) is a simple object that has no conceptual identity (Id). Instead, a Value Object is identified by its properties. + +A Value Object is typically owned by a parent [Entity](https://docs.abp.io/en/abp/latest/Entities) object. Using [Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types) with EF Core 8 is the best way to create Value Objects that are stored as a part of your main entity. ## Creating an ABP Project @@ -53,7 +55,7 @@ public class Address } ```` -`Address` is a typical complex object. It is actually a part of the `Customer` object, but we wanted to collect its parts into a dedicated class to make it a domain conceptual and easily manage its properties. +`Address` is a typical complex object. It is actually a part of the `Customer` object, but we wanted to collect its parts into a dedicated class to make it a domain concept and easily manage its properties together. ## Configure EF Core Mappings @@ -172,9 +174,13 @@ There are more details about using Complex Types in your applications. I want to * You can manipulate mutable complex object properties just as another property in your entity. EF Core change tracking system will track them as you expect. * Currently, EF Core doesn't support to have a collection of complex objects in an entity. It works only for properties. +For more details and examples, see the Microsoft's document in the *References* section. + ## Source Code -You can get the completed project here: https://github.com/hikalkan/samples/tree/master/EfCoreComplexTypeDemo +You can find the sample project here: + +https://github.com/hikalkan/samples/tree/master/EfCoreComplexTypeDemo ## References