diff --git a/docs/en/Community-Articles/2023-10-28-EF-Core-8-Primitive-Collections/POST.md b/docs/en/Community-Articles/2023-10-28-EF-Core-8-Primitive-Collections/POST.md new file mode 100644 index 0000000000..79d1b100c9 --- /dev/null +++ b/docs/en/Community-Articles/2023-10-28-EF-Core-8-Primitive-Collections/POST.md @@ -0,0 +1,118 @@ +# EF Core 8 Primitive collections + +What can we do when we want to store a list of primitive types? Before EF Core 8, there were two options: + +- Create a wrapper class and a related table, then add a foreign key linking each value to its owner of the collection. +- Use [value converter](https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions) to serialize-deserialize as JSON. + +The first option covers most scenarios if we need to add some additional properties to this type but let's say we're never gonna need this type additionality. + +## Which collection types are supported ? + +EF Core has the capability to map the `IEnumerable` public properties that have both a getter and a setter, with the `T` representing a primitive type + +```csharp +public class PrimitiveCollections +{ + public IEnumerable Ints { get; set; } + public ICollection Strings { get; set; } + public ISet DateTimes { get; set; } + public IList Dates { get; set; } + public uint[] UnsignedInts { get; set; } + public List Booleans { get; set; } + public List Urls { get; set; } +} +``` + +> Some generic arguments are not considered primitive on the database side, such as `uint` and `Uri`. However, these types are also considered as primitive because there are [built-in value converters](https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions?tabs=data-annotations#built-in-converters). + +## Demo + +In this sample, we have a `Car` class with a `Color` enum, and the `Car` class has a `Colors` property. + +```csharp +public enum Color +{ + Black, + White, + Red, + Blue +} + +public class Car +{ + public int Id { get; set; } + public string Brand { get; set; } + public string Model { get; set; } + public ISet Colors { get; set; } = new HashSet(); + + public Car(string brand, string model) + { + Brand = brand; + Model = model; + } +} +``` + +When we want to list the cars if they have any of the specific colors. + +```csharp +var colors = new HashSet { Color.Blue, Color.White }; +var cars = await context + .Cars + .Where(x=> x.Colors.Intersect(colors).Any()) + .ToListAsync(); +``` + +The SQL result looks like this; as you can see, it sends colors as parameters instead of adding them inline. It also uses the `json_each` function to deserialize on the database side. + +```sql +SELECT "c"."Id", "c"."Brand", "c"."Colors", "c"."Model" + FROM "Cars" AS "c" + WHERE EXISTS ( + SELECT 1 + FROM ( + SELECT "c0"."value" + FROM json_each("c"."Colors") AS "c0" + INTERSECT + SELECT "c1"."value" + FROM json_each(@__colors_0) AS "c1" + ) AS "t") + +``` + +When we insert to the car table. + +```csharp +var car = new Car("Maserati", "GranTurismo") +{ + Colors = new HashSet() + { + Color.Black, + Color.Blue + } +}; +context.Cars.Add(car); +await context.SaveChangesAsync(); +``` + +The SQL statement looks like this, and as you can see, it automatically serializes into the Colors parameter as JSON. + +```sql + Executed DbCommand (0ms) [Parameters= + [@p0='Maserati' (Nullable = false) (Size = 8), + @p1='[0,3]' (Nullable = false) (Size = 5), + @p2='Flow' (Nullable = false) (Size = 4) + ], CommandType='Text', CommandTimeout='30'] + INSERT INTO "Cars" ("Brand", "Colors", "Model") + VALUES (@p0, @p1, @p2) + RETURNING "Id"; +``` +## Conclusion + +We don't need to do anything if we just use a collection of primitive types. It serializes and deserializes them as JSON automatically. Additionally, it sends the primitive collection as a parameter to cache the query. + +## References + +- [EF Core 8 Primitive collections](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#primitive-collection-properties) +- [.NET Data Community Standup - Collections of primitive values in EF Core](https://www.youtube.com/watch?v=AUS2OZjsA2I)