Skip to content

Commit 5f90613

Browse files
committed
EF Core: added support for primitive collections
1 parent 45e79e9 commit 5f90613

File tree

11 files changed

+125
-8
lines changed

11 files changed

+125
-8
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Copyright>(c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved.</Copyright>
5-
<VersionPrefix>8.9.2</VersionPrefix>
5+
<VersionPrefix>8.10.0</VersionPrefix>
66
<Authors>Pawel Gerr</Authors>
77
<GenerateDocumentationFile>true</GenerateDocumentationFile>
88
<PackageProjectUrl>https://github.com/PawelGerr/Thinktecture.Runtime.Extensions</PackageProjectUrl>

src/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Sources/EntityFrameworkCore/Conventions/ThinktectureConventionSetPlugin.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ public ConventionSet ModifyConventions(ConventionSet conventionSet)
2222
conventionSet.NavigationAddedConventions.Add(convention);
2323
conventionSet.PropertyAddedConventions.Add(convention);
2424
conventionSet.EntityTypeAddedConventions.Add(convention);
25+
26+
#if PRIMITIVE_COLLECTIONS
27+
conventionSet.PropertyElementTypeChangedConventions.Add(convention);
28+
#endif
2529
}
2630

2731
return conventionSet;

src/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Sources/EntityFrameworkCore/Conventions/ThinktectureConventionsPlugin.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88

99
namespace Thinktecture.EntityFrameworkCore.Conventions;
1010

11-
internal sealed class ThinktectureConventionsPlugin : INavigationAddedConvention, IPropertyAddedConvention, IEntityTypeAddedConvention
11+
internal sealed class ThinktectureConventionsPlugin
12+
: INavigationAddedConvention,
13+
IPropertyAddedConvention,
14+
IEntityTypeAddedConvention
15+
#if PRIMITIVE_COLLECTIONS
16+
, IPropertyElementTypeChangedConvention
17+
#endif
1218
{
1319
private readonly bool _validateOnWrite;
1420
private readonly bool _useConstructorForRead;
@@ -103,6 +109,31 @@ public void ProcessPropertyAdded(IConventionPropertyBuilder propertyBuilder, ICo
103109
ProcessProperty(propertyBuilder.Metadata);
104110
}
105111

112+
#if PRIMITIVE_COLLECTIONS
113+
public void ProcessPropertyElementTypeChanged(
114+
IConventionPropertyBuilder propertyBuilder,
115+
IElementType? newElementType,
116+
IElementType? oldElementType,
117+
IConventionContext<IElementType> context)
118+
{
119+
var elementType = propertyBuilder.Metadata.GetElementType();
120+
121+
if (elementType is null)
122+
return;
123+
124+
var valueConverter = elementType.GetValueConverter();
125+
126+
if (valueConverter is not null)
127+
return;
128+
129+
if (MetadataLookup.Find(elementType.ClrType) is not Metadata.Keyed metadata)
130+
return;
131+
132+
elementType.SetValueConverter(GetValueConverter(metadata));
133+
_configureEnumsAndKeyedValueObjects(propertyBuilder.Metadata);
134+
}
135+
#endif
136+
106137
private void ProcessNavigation(IConventionNavigation navigation)
107138
{
108139
var naviType = navigation.ClrType;

src/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Sources/Extensions/EntityTypeBuilderExtensions.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ private static void AddConverterToNonNavigation(
378378
if (entity.IsIgnored(propertyInfo.Name))
379379
return;
380380

381-
// wil be handled by AddConverterForScalarProperties
381+
// will be handled by AddConverterForScalarProperties
382382
if (entity.FindProperty(propertyInfo) is not null)
383383
return;
384384

@@ -508,13 +508,43 @@ private static void AddConverterForScalarProperties(
508508
if (valueConverter is not null)
509509
continue;
510510

511+
#if PRIMITIVE_COLLECTIONS
512+
if (property.IsPrimitiveCollection)
513+
{
514+
AddConverterForPrimitiveCollections(property, validateOnWrite, useConstructorForRead, converterLookup, configure);
515+
return;
516+
}
517+
#endif
518+
511519
if (MetadataLookup.Find(property.ClrType) is not Metadata.Keyed metadata)
512520
continue;
513521

514522
SetConverterAndExecuteCallback(validateOnWrite, useConstructorForRead, converterLookup, configure, property, metadata);
515523
}
516524
}
517525

526+
#if PRIMITIVE_COLLECTIONS
527+
private static void AddConverterForPrimitiveCollections(
528+
IMutableProperty property,
529+
bool validateOnWrite,
530+
bool useConstructorForRead,
531+
Dictionary<Type, ValueConverter>? converterLookup,
532+
Action<IMutableProperty> configure)
533+
{
534+
var elementType = property.GetElementType();
535+
536+
if (elementType is null)
537+
return;
538+
539+
if (MetadataLookup.Find(elementType.ClrType) is not Metadata.Keyed metadata)
540+
return;
541+
542+
var valueConverter = GetValueConverter(validateOnWrite, useConstructorForRead, converterLookup, metadata);
543+
elementType.SetValueConverter(valueConverter);
544+
configure(property);
545+
}
546+
#endif
547+
518548
#if COMPLEX_TYPES
519549
private static void AddConverterForComplexProperties(
520550
IMutableTypeBase entity,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#if PRIMITIVE_COLLECTIONS
2+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
3+
using Thinktecture.EntityFrameworkCore.Storage.ValueConversion;
4+
5+
namespace Thinktecture;
6+
7+
/// <summary>
8+
/// Provides extension methods for the <see cref="PrimitiveCollectionBuilder{TProperty}"/> class to configure value object conversions.
9+
/// </summary>
10+
public static class PrimitiveCollectionBuilderExtensions
11+
{
12+
/// <summary>
13+
/// Configures a primitive collection to use value object conversion.
14+
/// </summary>
15+
/// <typeparam name="TProperty">The element type of the collection.</typeparam>
16+
/// <param name="primitiveCollectionBuilder">The primitive collection builder.</param>
17+
/// <param name="validateOnWrite">Whether to validate the value when writing to the database.</param>
18+
/// <param name="useConstructorForRead">For keyed value objects only. Whether to use the constructor when reading from the database.</param>
19+
/// <returns>The primitive collection builder for chaining.</returns>
20+
public static PrimitiveCollectionBuilder<TProperty> HasThinktectureValueConverter<TProperty>(
21+
this PrimitiveCollectionBuilder<TProperty> primitiveCollectionBuilder,
22+
bool validateOnWrite,
23+
bool useConstructorForRead = true)
24+
{
25+
var elementType = primitiveCollectionBuilder.ElementType();
26+
var converter = ThinktectureValueConverterFactory.Create(elementType.Metadata.ClrType, validateOnWrite, useConstructorForRead);
27+
elementType.HasConversion(converter);
28+
29+
return primitiveCollectionBuilder;
30+
}
31+
}
32+
#endif

src/Thinktecture.Runtime.Extensions.EntityFrameworkCore8/Thinktecture.Runtime.Extensions.EntityFrameworkCore8.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<Description>Extends Entity Framework Core to support some components from Thinktecture.Runtime.Extensions.</Description>
55
<PackageTags>smart-enum;value-object;discriminated-union;EntityFrameworkCore</PackageTags>
6-
<DefineConstants>$(DefineConstants);COMPLEX_TYPES;USE_FIND_COMPLEX_PROPERTY_FIX;</DefineConstants>
6+
<DefineConstants>$(DefineConstants);COMPLEX_TYPES;PRIMITIVE_COLLECTIONS;USE_FIND_COMPLEX_PROPERTY_FIX;</DefineConstants>
77
<TargetFramework>net8.0</TargetFramework>
88
</PropertyGroup>
99

src/Thinktecture.Runtime.Extensions.EntityFrameworkCore9/Thinktecture.Runtime.Extensions.EntityFrameworkCore9.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<Description>Extends Entity Framework Core to support some components from Thinktecture.Runtime.Extensions.</Description>
55
<PackageTags>smart-enum;value-object;discriminated-union;EntityFrameworkCore</PackageTags>
6-
<DefineConstants>$(DefineConstants);COMPLEX_TYPES;USE_FIND_COMPLEX_PROPERTY_FIX;</DefineConstants>
6+
<DefineConstants>$(DefineConstants);COMPLEX_TYPES;PRIMITIVE_COLLECTIONS;USE_FIND_COMPLEX_PROPERTY_FIX;</DefineConstants>
77
<TargetFramework>net8.0</TargetFramework>
88
</PropertyGroup>
99

test/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Tests.Sources/EntityFrameworkCore/ValueConversion/ValueObjectValueConverterFactoryTests.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@ public async Task Should_write_and_read_enums_and_value_types()
4242
Boundary = Boundary.Create(10, 20),
4343
BoundaryWithCustomError = BoundaryWithCustomError.Create(11, 21),
4444
BoundaryWithCustomFactoryNames = BoundaryWithCustomFactoryNames.Get(11, 21),
45-
IntBasedReferenceValueObjectWitCustomFactoryName = IntBasedReferenceValueObjectWithCustomFactoryNames.Get(1)
45+
IntBasedReferenceValueObjectWitCustomFactoryName = IntBasedReferenceValueObjectWithCustomFactoryNames.Get(1),
46+
#if PRIMITIVE_COLLECTIONS
47+
CollectionOfIntBasedReferenceValueObject =
48+
[
49+
IntBasedReferenceValueObject.Create(1),
50+
IntBasedReferenceValueObject.Create(2)
51+
]
52+
#endif
4653
};
4754
_ctx.Add(entity);
4855
await _ctx.SaveChangesAsync();

test/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Tests.Sources/TestEntities/TestEntity_with_Enum_and_ValueObjects.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using Microsoft.EntityFrameworkCore;
34
using Thinktecture.Runtime.Tests.TestEnums;
45
using Thinktecture.Runtime.Tests.TestValueObjects;
@@ -27,6 +28,10 @@ public class TestEntity_with_Enum_and_ValueObjects
2728
public required StringBasedStructValueObject StringBasedStructValueObject { get; set; }
2829
public StringBasedReferenceValueObjectWithCustomError? StringBasedReferenceValueObjectWithCustomError { get; set; }
2930

31+
#if PRIMITIVE_COLLECTIONS
32+
public List<IntBasedReferenceValueObject> CollectionOfIntBasedReferenceValueObject { get; set; } = [];
33+
#endif
34+
3035
public Boundary? Boundary { get; set; }
3136
public BoundaryWithCustomError? BoundaryWithCustomError { get; set; }
3237
public BoundaryWithCustomFactoryNames? BoundaryWithCustomFactoryNames { get; set; }
@@ -42,6 +47,10 @@ is ValueConverterRegistration.EntityConfiguration
4247

4348
modelBuilder.Entity<TestEntity_with_Enum_and_ValueObjects>(builder =>
4449
{
50+
#if PRIMITIVE_COLLECTIONS
51+
var primitiveCollectionBuilder = builder.PrimitiveCollection(e => e.CollectionOfIntBasedReferenceValueObject);
52+
#endif
53+
4554
if (valueConverterRegistration == ValueConverterRegistration.PropertyConfiguration)
4655
{
4756
builder.Property(e => e.TestEnum).HasThinktectureValueConverter(true);
@@ -60,6 +69,10 @@ is ValueConverterRegistration.EntityConfiguration
6069
builder.Property(e => e.StringBasedReferenceValueObjectWithCustomError).HasThinktectureValueConverter(true);
6170

6271
builder.Property(e => e.IntBasedReferenceValueObjectWitCustomFactoryName).HasThinktectureValueConverter(true);
72+
73+
#if PRIMITIVE_COLLECTIONS
74+
primitiveCollectionBuilder.HasThinktectureValueConverter(true);
75+
#endif
6376
}
6477

6578
builder.OwnsOne(e => e.Boundary, navigationBuilder =>

test/Thinktecture.Runtime.Extensions.EntityFrameworkCore8.Tests/Thinktecture.Runtime.Extensions.EntityFrameworkCore8.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<DefineConstants>$(DefineConstants);COMPLEX_TYPES</DefineConstants>
4+
<DefineConstants>$(DefineConstants);COMPLEX_TYPES;PRIMITIVE_COLLECTIONS;</DefineConstants>
55
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
66
</PropertyGroup>
77

0 commit comments

Comments
 (0)