Skip to content

Commit 852fc46

Browse files
committed
Added extension method "HasValueObjectConversion" for property builders
1 parent 31eb593 commit 852fc46

14 files changed

Lines changed: 410 additions & 35 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#if COMPLEX_TYPES
2+
3+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
4+
using Thinktecture.EntityFrameworkCore.Storage.ValueConversion;
5+
6+
namespace Thinktecture;
7+
8+
/// <summary>
9+
/// Provides extension methods for the <see cref="ComplexTypePropertyBuilder{TProperty}"/> class to configure value object conversions.
10+
/// </summary>
11+
public static class ComplexTypePropertyBuilderExtensions
12+
{
13+
/// <summary>
14+
/// Configures a complex type property to use value object conversion.
15+
/// </summary>
16+
/// <typeparam name="TProperty">The property type.</typeparam>
17+
/// <param name="propertyBuilder">The complex type property builder.</param>
18+
/// <param name="validateOnWrite">Whether to validate the value when writing to the database.</param>
19+
/// <param name="useConstructorForRead">For keyed value objects only. Whether to use the constructor when reading from the database.</param>
20+
/// <returns>The complex type property builder for chaining.</returns>
21+
public static ComplexTypePropertyBuilder<TProperty> HasValueObjectConversion<TProperty>(
22+
this ComplexTypePropertyBuilder<TProperty> propertyBuilder,
23+
bool validateOnWrite,
24+
bool useConstructorForRead = true)
25+
{
26+
var converter = ValueObjectValueConverterFactory.Create(typeof(TProperty), validateOnWrite, useConstructorForRead);
27+
propertyBuilder.HasConversion(converter);
28+
29+
return propertyBuilder;
30+
}
31+
}
32+
#endif
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
2+
using Thinktecture.EntityFrameworkCore.Storage.ValueConversion;
3+
4+
namespace Thinktecture;
5+
6+
/// <summary>
7+
/// Provides extension methods for the <see cref="PropertyBuilder{TProperty}"/> class to configure value object conversions.
8+
/// </summary>
9+
public static class PropertyBuilderExtensions
10+
{
11+
/// <summary>
12+
/// Configures a property to use value object conversion.
13+
/// </summary>
14+
/// <typeparam name="TProperty">The property type.</typeparam>
15+
/// <param name="propertyBuilder">The property builder.</param>
16+
/// <param name="validateOnWrite">Whether to validate the value when writing to the database.</param>
17+
/// <param name="useConstructorForRead">For keyed value objects only. Whether to use the constructor when reading from the database.</param>
18+
/// <returns>The property builder for chaining.</returns>
19+
public static PropertyBuilder<TProperty> HasValueObjectConversion<TProperty>(
20+
this PropertyBuilder<TProperty> propertyBuilder,
21+
bool validateOnWrite,
22+
bool useConstructorForRead = true)
23+
{
24+
var converter = ValueObjectValueConverterFactory.Create(typeof(TProperty), validateOnWrite, useConstructorForRead);
25+
propertyBuilder.HasConversion(converter);
26+
27+
return propertyBuilder;
28+
}
29+
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ public class ValueObjectValueConverterFactoryTests : IDisposable
1414

1515
public ValueObjectValueConverterFactoryTests()
1616
{
17-
_ctx = new(new DbContextOptionsBuilder<TestDbContext>().Options, ValueConverterRegistration.OnModelCreating);
17+
_ctx = new(
18+
new DbContextOptionsBuilder<TestDbContext>()
19+
.EnableServiceProviderCaching(false)
20+
.Options,
21+
ValueConverterRegistration.OnModelCreating);
1822
_ctx.Database.OpenConnection();
1923
_ctx.Database.EnsureCreated();
2024
}

test/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Tests.Sources/Extensions/EntityTypeBuilderExtensionsTests/AddValueObjectConverters.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ public class AddValueObjectConverters : IDisposable
2020
private static readonly Type _converterType = typeof(ValueObjectValueConverterFactory).GetNestedTypes(BindingFlags.NonPublic)
2121
.Single(t => t.Name.StartsWith("ValueObjectValueConverter", StringComparison.Ordinal));
2222

23-
private readonly TestDbContext _ctx = new(new DbContextOptionsBuilder<TestDbContext>().UseSqlite("DataSource=:memory:").Options,
23+
private readonly TestDbContext _ctx = new(new DbContextOptionsBuilder<TestDbContext>()
24+
.UseSqlite("DataSource=:memory:")
25+
.EnableServiceProviderCaching(false)
26+
.Options,
2427
ValueConverterRegistration.EntityConfiguration);
2528

2629
[Fact]
@@ -51,7 +54,10 @@ public void Should_add_converters_for_complex_types()
5154
public void Should_add_converters_for_complex_types_inside_complex_type_configuration()
5255
{
5356
using var ctx = new TestDbContext(
54-
new DbContextOptionsBuilder<TestDbContext>().UseSqlite("DataSource=:memory:").Options,
57+
new DbContextOptionsBuilder<TestDbContext>()
58+
.UseSqlite("DataSource=:memory:")
59+
.EnableServiceProviderCaching(false)
60+
.Options,
5561
ValueConverterRegistration.ComplexTypeConfiguration);
5662

5763
var entityType = ctx.Model.FindEntityType(typeof(TestEntityWithComplexType)) ?? throw new Exception("Entity not found");

test/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Tests.Sources/Extensions/ModelBuilderExtensionsTests/AddValueObjectConverters.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ public class AddValueObjectConverters : IDisposable
2020
private static readonly Type _converterType = typeof(ValueObjectValueConverterFactory).GetNestedTypes(BindingFlags.NonPublic)
2121
.Single(t => t.Name.StartsWith("ValueObjectValueConverter", StringComparison.Ordinal));
2222

23-
private readonly TestDbContext _ctx = new(new DbContextOptionsBuilder<TestDbContext>().UseSqlite("DataSource=:memory:").Options,
23+
private readonly TestDbContext _ctx = new(new DbContextOptionsBuilder<TestDbContext>()
24+
.UseSqlite("DataSource=:memory:")
25+
.EnableServiceProviderCaching(false)
26+
.Options,
2427
ValueConverterRegistration.OnModelCreating);
2528

2629
[Fact]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using System;
2+
using System.Linq;
3+
using System.Reflection;
4+
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.EntityFrameworkCore.Metadata;
6+
using Microsoft.Extensions.Caching.Memory;
7+
using Thinktecture.EntityFrameworkCore.Storage.ValueConversion;
8+
using Thinktecture.Runtime.Tests.TestEntities;
9+
using Thinktecture.Runtime.Tests.TestEnums;
10+
11+
// ReSharper disable InconsistentNaming
12+
namespace Thinktecture.Runtime.Tests.Extensions.ModelBuilderExtensionsTests
13+
{
14+
public class HasValueObjectConversion : IDisposable
15+
{
16+
private static readonly Type _validateableConverterType = typeof(ValueObjectValueConverterFactory).GetNestedTypes(BindingFlags.NonPublic)
17+
.Single(t => t.Name.StartsWith("ValidatableEnumValueConverter", StringComparison.Ordinal));
18+
19+
private static readonly Type _testEnumConverterType = _validateableConverterType.MakeGenericType(typeof(TestEnum), typeof(string));
20+
21+
private static readonly Type _converterType = typeof(ValueObjectValueConverterFactory).GetNestedTypes(BindingFlags.NonPublic)
22+
.Single(t => t.Name.StartsWith("ValueObjectValueConverter", StringComparison.Ordinal));
23+
24+
private readonly TestDbContext _ctx = new(new DbContextOptionsBuilder<TestDbContext>()
25+
.UseSqlite("DataSource=:memory:")
26+
.EnableServiceProviderCaching(false)
27+
.Options,
28+
ValueConverterRegistration.PropertyConfiguration);
29+
30+
[Fact]
31+
public void Should_add_converters_for_structs_and_classes()
32+
{
33+
var entityType = _ctx.Model.FindEntityType(typeof(TestEntity_with_Enum_and_ValueObjects));
34+
35+
ValidateConverter(entityType, nameof(TestEntity_with_Enum_and_ValueObjects.TestEnum), _testEnumConverterType);
36+
37+
ValidateConverter(entityType, nameof(TestEntity_with_Enum_and_ValueObjects.TestSmartEnum_Class_IntBased), _converterType.MakeGenericType(typeof(TestSmartEnum_Class_IntBased), typeof(int)));
38+
ValidateConverter(entityType, nameof(TestEntity_with_Enum_and_ValueObjects.TestSmartEnum_Class_StringBased), _converterType.MakeGenericType(typeof(TestSmartEnum_Class_StringBased), typeof(string)));
39+
ValidateConverter(entityType, nameof(TestEntity_with_Enum_and_ValueObjects.TestSmartEnum_Struct_IntBased), _validateableConverterType.MakeGenericType(typeof(TestSmartEnum_Struct_IntBased), typeof(int)));
40+
ValidateConverter(entityType, nameof(TestEntity_with_Enum_and_ValueObjects.TestSmartEnum_Struct_StringBased), _validateableConverterType.MakeGenericType(typeof(TestSmartEnum_Struct_StringBased), typeof(string)));
41+
ValidateConverter(entityType, nameof(TestEntity_with_Enum_and_ValueObjects.NullableTestSmartEnum_Struct_StringBased), _validateableConverterType.MakeGenericType(typeof(TestSmartEnum_Struct_StringBased), typeof(string)));
42+
}
43+
44+
#if COMPLEX_TYPES
45+
[Fact]
46+
public void Should_add_converters_for_complex_types()
47+
{
48+
var entityType = _ctx.Model.FindEntityType(typeof(TestEntityWithComplexType)) ?? throw new Exception("Entity not found");
49+
var complexProperty = entityType.FindComplexProperty(nameof(TestEntityWithComplexType.TestComplexType)) ?? throw new Exception("Complex type property not found");
50+
51+
ValidateConverter(complexProperty.ComplexType, nameof(TestComplexType.TestEnum));
52+
}
53+
54+
[Fact]
55+
public void Should_add_converters_for_complex_types_inside_complex_value_object()
56+
{
57+
var entityType = _ctx.Model.FindEntityType(typeof(ComplexValueObjectWithComplexType)) ?? throw new Exception("Entity not found");
58+
var complexProperty = entityType.FindComplexProperty(nameof(ComplexValueObjectWithComplexType.TestComplexType)) ?? throw new Exception("Complex type property not found");
59+
60+
ValidateConverter(complexProperty.ComplexType, nameof(TestComplexType.TestEnum));
61+
}
62+
63+
[Fact]
64+
public void Should_add_converters_for_complex_value_object_as_complex_type()
65+
{
66+
var entityType = _ctx.Model.FindEntityType(typeof(TestEntityWithComplexValueObjectAsComplexType)) ?? throw new Exception("Entity not found");
67+
var complexProperty = entityType.FindComplexProperty(nameof(TestEntityWithComplexValueObjectAsComplexType.TestComplexType)) ?? throw new Exception("Complex type property not found");
68+
69+
ValidateConverter(complexProperty.ComplexType, nameof(TestComplexValueObject.TestEnum));
70+
}
71+
#endif
72+
73+
[Fact]
74+
public void Should_add_converters_for_owned_types()
75+
{
76+
var entityType = _ctx.Model.FindEntityType(typeof(TestEntity_with_OwnedTypes));
77+
ValidateConverter(entityType, nameof(TestEntity_with_OwnedTypes.TestEnum));
78+
79+
var inline_inline = entityType.FindNavigation(nameof(TestEntity_with_OwnedTypes.Inline_Inline)).TargetEntityType;
80+
var inline_inline_inner = inline_inline.FindNavigation(nameof(OwnedEntity_Owns_Inline.InlineEntity)).TargetEntityType;
81+
ValidateConverter(inline_inline, nameof(TestEntity_with_OwnedTypes.TestEnum));
82+
ValidateConverter(inline_inline_inner, nameof(TestEntity_with_OwnedTypes.TestEnum));
83+
84+
var inline_separateOne = entityType.FindNavigation(nameof(TestEntity_with_OwnedTypes.Inline_SeparateOne)).TargetEntityType;
85+
var inline_separateOne_inner = inline_separateOne.FindNavigation(nameof(OwnedEntity_Owns_SeparateOne.SeparateEntity)).TargetEntityType;
86+
ValidateConverter(inline_separateOne, nameof(TestEntity_with_OwnedTypes.TestEnum));
87+
ValidateConverter(inline_separateOne_inner, nameof(TestEntity_with_OwnedTypes.TestEnum));
88+
89+
var inline_SeparateMany = entityType.FindNavigation(nameof(TestEntity_with_OwnedTypes.Inline_SeparateMany)).TargetEntityType;
90+
var inline_SeparateMany_inner = inline_SeparateMany.FindNavigation(nameof(OwnedEntity_Owns_SeparateMany.SeparateEntities)).TargetEntityType;
91+
ValidateConverter(inline_SeparateMany, nameof(TestEntity_with_OwnedTypes.TestEnum));
92+
ValidateConverter(inline_SeparateMany_inner, nameof(TestEntity_with_OwnedTypes.TestEnum));
93+
94+
var separateMany_Inline = entityType.FindNavigation(nameof(TestEntity_with_OwnedTypes.SeparateMany_Inline)).TargetEntityType;
95+
var separateMany_Inline_inner = separateMany_Inline.FindNavigation(nameof(OwnedEntity_Owns_Inline.InlineEntity)).TargetEntityType;
96+
ValidateConverter(separateMany_Inline, nameof(TestEntity_with_OwnedTypes.TestEnum));
97+
ValidateConverter(separateMany_Inline_inner, nameof(TestEntity_with_OwnedTypes.TestEnum));
98+
99+
var separateMany_SeparateOne = entityType.FindNavigation(nameof(TestEntity_with_OwnedTypes.SeparateMany_SeparateOne)).TargetEntityType;
100+
var separateMany_SeparateOne_inner = separateMany_SeparateOne.FindNavigation(nameof(OwnedEntity_Owns_SeparateOne.SeparateEntity)).TargetEntityType;
101+
ValidateConverter(separateMany_SeparateOne, nameof(TestEntity_with_OwnedTypes.TestEnum));
102+
ValidateConverter(separateMany_SeparateOne_inner, nameof(TestEntity_with_OwnedTypes.TestEnum));
103+
104+
var separateMany_SeparateMany = entityType.FindNavigation(nameof(TestEntity_with_OwnedTypes.SeparateMany_SeparateMany)).TargetEntityType;
105+
var separateMany_SeparateMany_inner = separateMany_SeparateMany.FindNavigation(nameof(OwnedEntity_Owns_SeparateMany.SeparateEntities)).TargetEntityType;
106+
ValidateConverter(separateMany_SeparateMany, nameof(TestEntity_with_OwnedTypes.TestEnum));
107+
ValidateConverter(separateMany_SeparateMany_inner, nameof(TestEntity_with_OwnedTypes.TestEnum));
108+
109+
var separateOne_Inline = entityType.FindNavigation(nameof(TestEntity_with_OwnedTypes.SeparateOne_Inline)).TargetEntityType;
110+
var separateOne_Inline_inner = separateOne_Inline.FindNavigation(nameof(OwnedEntity_Owns_Inline.InlineEntity)).TargetEntityType;
111+
ValidateConverter(separateOne_Inline, nameof(TestEntity_with_OwnedTypes.TestEnum));
112+
ValidateConverter(separateOne_Inline_inner, nameof(TestEntity_with_OwnedTypes.TestEnum));
113+
114+
var separateOne_SeparateOne = entityType.FindNavigation(nameof(TestEntity_with_OwnedTypes.SeparateOne_SeparateOne)).TargetEntityType;
115+
var separateOne_SeparateOne_inner = separateOne_SeparateOne.FindNavigation(nameof(OwnedEntity_Owns_SeparateOne.SeparateEntity)).TargetEntityType;
116+
ValidateConverter(separateOne_SeparateOne, nameof(TestEntity_with_OwnedTypes.TestEnum));
117+
ValidateConverter(separateOne_SeparateOne_inner, nameof(TestEntity_with_OwnedTypes.TestEnum));
118+
119+
var separateOne_SeparateMany = entityType.FindNavigation(nameof(TestEntity_with_OwnedTypes.SeparateOne_SeparateMany)).TargetEntityType;
120+
var separateOne_SeparateMany_inner = separateOne_SeparateMany.FindNavigation(nameof(OwnedEntity_Owns_SeparateMany.SeparateEntities)).TargetEntityType;
121+
ValidateConverter(separateOne_SeparateMany, nameof(TestEntity_with_OwnedTypes.TestEnum));
122+
ValidateConverter(separateOne_SeparateMany_inner, nameof(TestEntity_with_OwnedTypes.TestEnum));
123+
}
124+
125+
private static void ValidateConverter(
126+
#if COMPLEX_TYPES
127+
ITypeBase
128+
#else
129+
IEntityType
130+
#endif
131+
entityType, string propertyName, Type converterType = null)
132+
{
133+
var property = entityType.FindProperty(propertyName) ?? throw new Exception($"Property with the name '{propertyName}' not found.");
134+
property.GetValueConverter().Should().BeOfType(converterType ?? _testEnumConverterType);
135+
}
136+
137+
public void Dispose()
138+
{
139+
_ctx.Dispose();
140+
}
141+
}
142+
}

test/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Tests.Sources/Extensions/ValueObjectDbContextOptionsBuilderExtensionsTests/UseValueObjectValueConverter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public UseValueObjectValueConverter()
2626
{
2727
var options = new DbContextOptionsBuilder<TestDbContext>()
2828
.UseSqlite("DataSource=:memory:")
29+
.EnableServiceProviderCaching(false)
2930
.UseValueObjectValueConverter()
3031
.Options;
3132

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ public static void Configure(
2626
b =>
2727
{
2828
b.IsRequired();
29-
b.Property(t => t.TestEnum);
29+
var propertyBuilder = b.Property(t => t.TestEnum);
30+
31+
if (valueConverterRegistration == ValueConverterRegistration.PropertyConfiguration)
32+
propertyBuilder.HasValueObjectConversion(true);
3033

3134
if (valueConverterRegistration == ValueConverterRegistration.ComplexTypeConfiguration)
3235
b.AddValueObjectConverters(true);

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
3535
{
3636
base.OnModelCreating(modelBuilder);
3737

38-
var configureOnEntityTypeLevel = _valueConverterRegistration is ValueConverterRegistration.EntityConfiguration or ValueConverterRegistration.ComplexTypeConfiguration;
39-
40-
TestEntity_with_OwnedTypes.Configure(modelBuilder, configureOnEntityTypeLevel);
41-
TestEntity_with_Enum_and_ValueObjects.Configure(modelBuilder, configureOnEntityTypeLevel);
38+
TestEntity_with_OwnedTypes.Configure(modelBuilder, _valueConverterRegistration);
39+
TestEntity_with_Enum_and_ValueObjects.Configure(modelBuilder, _valueConverterRegistration);
4240

4341
#if COMPLEX_TYPES
4442
TestEntityWithComplexType.Configure(modelBuilder, _valueConverterRegistration);

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ public static void Configure(
1919
b =>
2020
{
2121
b.IsRequired();
22-
b.Property(t => t.TestEnum);
22+
var propertyBuilder = b.Property(t => t.TestEnum);
23+
24+
if (valueConverterRegistration == ValueConverterRegistration.PropertyConfiguration)
25+
propertyBuilder.HasValueObjectConversion(true);
2326

2427
if (valueConverterRegistration == ValueConverterRegistration.ComplexTypeConfiguration)
2528
b.AddValueObjectConverters(true);

0 commit comments

Comments
 (0)