Skip to content

Commit ba44ddb

Browse files
committed
Add support for primitive collections to the compiled model
Fixes #35047
1 parent 281031a commit ba44ddb

File tree

71 files changed

+21535
-91
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+21535
-91
lines changed

src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs

+142-13
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
77
using Microsoft.EntityFrameworkCore.Design.Internal;
88
using Microsoft.EntityFrameworkCore.Internal;
9+
using Microsoft.EntityFrameworkCore.Metadata;
910
using Microsoft.EntityFrameworkCore.Metadata.Internal;
1011
using Microsoft.EntityFrameworkCore.Query.Internal;
1112

@@ -1179,18 +1180,7 @@ private void Create(
11791180
var valueComparerType = (Type?)property[CoreAnnotationNames.ValueComparerType];
11801181
if (valueComparerType != null)
11811182
{
1182-
AddNamespace(valueComparerType, parameters.Namespaces);
1183-
1184-
var valueComparerString = $"new {_code.Reference(valueComparerType)}()";
1185-
if (property.ClrType.IsNullableValueType())
1186-
{
1187-
var valueComparerElementType = ((ValueComparer)Activator.CreateInstance(valueComparerType)!).Type;
1188-
if (!valueComparerElementType.IsNullableValueType())
1189-
{
1190-
AddNamespace(typeof(NullableValueComparer<>), parameters.Namespaces);
1191-
valueComparerString = $"new NullableValueComparer<{_code.Reference(valueComparerType)}>({valueComparerString})";
1192-
}
1193-
}
1183+
var valueComparerString = CreateValueComparerType(valueComparerType, property.ClrType, parameters);
11941184

11951185
mainBuilder.AppendLine(",")
11961186
.Append("valueComparer: ")
@@ -1240,11 +1230,13 @@ private void Create(
12401230
&& converter != null
12411231
&& property[CoreAnnotationNames.ValueConverter] != null
12421232
&& !parameters.ForNativeAot;
1233+
var typeMappingSet = false;
12431234

12441235
if (parameters.ForNativeAot
12451236
|| (shouldSetConverter && converter!.MappingHints != null))
12461237
{
12471238
shouldSetConverter = false;
1239+
typeMappingSet = true;
12481240
mainBuilder.Append(variableName).Append(".TypeMapping = ");
12491241
_annotationCodeGenerator.Create(property.GetTypeMapping(), property, propertyParameters);
12501242
mainBuilder.AppendLine(";");
@@ -1311,14 +1303,151 @@ private void Create(
13111303
.AppendLine(");");
13121304
}
13131305

1306+
var elementType = property.GetElementType();
1307+
if (elementType != null)
1308+
{
1309+
Check.DebugAssert(property.IsPrimitiveCollection, $"{property.Name} has an element type, but it's not a primitive collection.");
1310+
Create(elementType, typeMappingSet, propertyParameters);
1311+
}
1312+
13141313
CreateAnnotations(
13151314
property,
13161315
_annotationCodeGenerator.Generate,
1317-
parameters with { TargetName = variableName });
1316+
propertyParameters);
13181317

13191318
mainBuilder.AppendLine();
13201319
}
13211320

1321+
private void Create(IElementType elementType, bool typeMappingSet, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
1322+
{
1323+
var mainBuilder = parameters.MainBuilder;
1324+
var elementVariableName = _code.Identifier(parameters.TargetName + "ElementType", elementType, parameters.ScopeObjects, capitalize: false);
1325+
var elementParameters = parameters with { TargetName = elementVariableName };
1326+
1327+
mainBuilder
1328+
.Append("var ").Append(elementVariableName).Append(" = ")
1329+
.Append(parameters.TargetName).Append(".SetElementType(").IncrementIndent()
1330+
.Append(_code.Literal(elementType.ClrType));
1331+
1332+
if (elementType.IsNullable)
1333+
{
1334+
mainBuilder.AppendLine(",")
1335+
.Append("nullable: ")
1336+
.Append(_code.Literal(elementType.IsNullable));
1337+
}
1338+
1339+
if (elementType.GetMaxLength() != null)
1340+
{
1341+
mainBuilder.AppendLine(",")
1342+
.Append("maxLength: ")
1343+
.Append(_code.Literal(elementType.GetMaxLength()));
1344+
}
1345+
1346+
if (elementType.IsUnicode() != null)
1347+
{
1348+
mainBuilder.AppendLine(",")
1349+
.Append("unicode: ")
1350+
.Append(_code.Literal(elementType.IsUnicode()));
1351+
}
1352+
1353+
if (elementType.GetPrecision() != null)
1354+
{
1355+
mainBuilder.AppendLine(",")
1356+
.Append("precision: ")
1357+
.Append(_code.Literal(elementType.GetPrecision()));
1358+
}
1359+
1360+
if (elementType.GetScale() != null)
1361+
{
1362+
mainBuilder.AppendLine(",")
1363+
.Append("scale: ")
1364+
.Append(_code.Literal(elementType.GetScale()));
1365+
}
1366+
1367+
var providerClrType = elementType.GetProviderClrType();
1368+
if (providerClrType != null)
1369+
{
1370+
AddNamespace(providerClrType, parameters.Namespaces);
1371+
mainBuilder.AppendLine(",")
1372+
.Append("providerClrType: ")
1373+
.Append(_code.Literal(providerClrType));
1374+
}
1375+
1376+
var jsonValueReaderWriterType = (Type?)elementType[CoreAnnotationNames.JsonValueReaderWriterType];
1377+
if (jsonValueReaderWriterType != null)
1378+
{
1379+
mainBuilder.AppendLine(",")
1380+
.Append("jsonValueReaderWriter: ");
1381+
CSharpRuntimeAnnotationCodeGenerator.CreateJsonValueReaderWriter(jsonValueReaderWriterType, parameters, _code);
1382+
}
1383+
1384+
mainBuilder
1385+
.AppendLine(");")
1386+
.DecrementIndent();
1387+
1388+
var converter = elementType.FindTypeMapping()?.Converter;
1389+
var shouldSetConverter = providerClrType == null
1390+
&& converter != null
1391+
&& elementType[CoreAnnotationNames.ValueConverter] != null
1392+
&& !parameters.ForNativeAot;
1393+
1394+
if (parameters.ForNativeAot
1395+
|| (shouldSetConverter && converter!.MappingHints != null))
1396+
{
1397+
shouldSetConverter = false;
1398+
mainBuilder.Append(elementVariableName).Append(".TypeMapping = ");
1399+
1400+
if (typeMappingSet)
1401+
{
1402+
mainBuilder.Append(parameters.TargetName).Append(".TypeMapping.ElementTypeMapping");
1403+
}
1404+
else
1405+
{
1406+
_annotationCodeGenerator.Create(elementType.GetTypeMapping(), elementParameters);
1407+
}
1408+
1409+
mainBuilder.AppendLine(";");
1410+
}
1411+
1412+
if (shouldSetConverter)
1413+
{
1414+
mainBuilder.Append(elementVariableName).Append(".SetValueConverter(");
1415+
_annotationCodeGenerator.Create(converter!, parameters);
1416+
mainBuilder.AppendLine(");");
1417+
}
1418+
1419+
var valueComparer = elementType.GetValueComparer();
1420+
var typeMappingComparer = elementType.GetTypeMapping().Comparer;
1421+
if ((!parameters.ForNativeAot || valueComparer != typeMappingComparer)
1422+
&& (parameters.ForNativeAot || elementType[CoreAnnotationNames.ValueComparer] != null))
1423+
{
1424+
SetValueComparer(valueComparer, typeMappingComparer, nameof(CoreTypeMapping.Comparer), elementParameters);
1425+
}
1426+
1427+
CreateAnnotations(
1428+
elementType,
1429+
_annotationCodeGenerator.Generate,
1430+
elementParameters);
1431+
}
1432+
1433+
private string CreateValueComparerType(Type valueComparerType, Type clrType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
1434+
{
1435+
AddNamespace(valueComparerType, parameters.Namespaces);
1436+
1437+
var valueComparerString = $"new {_code.Reference(valueComparerType)}()";
1438+
if (clrType.IsNullableValueType())
1439+
{
1440+
var valueComparerElementType = ((ValueComparer)Activator.CreateInstance(valueComparerType)!).Type;
1441+
if (!valueComparerElementType.IsNullableValueType())
1442+
{
1443+
AddNamespace(typeof(NullableValueComparer<>), parameters.Namespaces);
1444+
valueComparerString = $"new NullableValueComparer<{_code.Reference(valueComparerType)}>({valueComparerString})";
1445+
}
1446+
}
1447+
1448+
return valueComparerString;
1449+
}
1450+
13221451
private void SetValueComparer(
13231452
ValueComparer valueComparer,
13241453
ValueComparer typeMappingComparer,

src/EFCore.Relational/Storage/RelationalTypeMapping.cs

-6
Original file line numberDiff line numberDiff line change
@@ -572,12 +572,6 @@ public virtual DbParameter CreateParameter(
572572

573573
if (nullable.HasValue)
574574
{
575-
Check.DebugAssert(
576-
nullable.Value
577-
|| !direction.HasFlag(ParameterDirection.Input)
578-
|| value != null,
579-
"Null value in a non-nullable input parameter");
580-
581575
parameter.IsNullable = nullable.Value;
582576
}
583577

src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ public override async Task ExecuteAsync(
393393

394394
await ConsumeAsync(dataReader, cancellationToken).ConfigureAwait(false);
395395
}
396-
catch (Exception ex) when (ex is not DbUpdateException and not OperationCanceledException)
396+
catch (Exception ex) when (ex is not DbUpdateException and not OperationCanceledException and not UnreachableException)
397397
{
398398
throw new DbUpdateException(
399399
RelationalStrings.UpdateStoreException,

src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs

+18
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,24 @@ public virtual void Generate(IServiceProperty property, CSharpRuntimeAnnotationC
140140
GenerateSimpleAnnotations(parameters);
141141
}
142142

143+
/// <inheritdoc />
144+
public virtual void Generate(IElementType elementType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
145+
{
146+
if (!parameters.IsRuntime)
147+
{
148+
var annotations = parameters.Annotations;
149+
foreach (var (key, _) in annotations)
150+
{
151+
if (CoreAnnotationNames.AllNames.Contains(key))
152+
{
153+
annotations.Remove(key);
154+
}
155+
}
156+
}
157+
158+
GenerateSimpleAnnotations(parameters);
159+
}
160+
143161
/// <inheritdoc />
144162
public virtual void Generate(IKey key, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
145163
{

src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs

+7
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ public interface ICSharpRuntimeAnnotationCodeGenerator
5555
/// <param name="parameters">Additional parameters used during code generation.</param>
5656
void Generate(IServiceProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters);
5757

58+
/// <summary>
59+
/// Generates code to create the given annotations.
60+
/// </summary>
61+
/// <param name="elementType">The element type to which the annotations are applied.</param>
62+
/// <param name="parameters">Additional parameters used during code generation.</param>
63+
void Generate(IElementType elementType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters);
64+
5865
/// <summary>
5966
/// Generates code to create the given annotations.
6067
/// </summary>

src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ protected virtual RuntimeModel Create(IModel model)
6363
var elementType = property.GetElementType();
6464
if (elementType != null)
6565
{
66-
var runtimeElementType = Create(runtimeProperty, elementType, property.IsPrimitiveCollection);
66+
Check.DebugAssert(property.IsPrimitiveCollection, $"{property.Name} has an element type, but it's not a primitive collection.");
67+
var runtimeElementType = Create(runtimeProperty, elementType);
6768
CreateAnnotations(
6869
elementType, runtimeElementType, static (convention, annotations, source, target, runtime) =>
6970
convention.ProcessElementTypeAnnotations(annotations, source, target, runtime));
@@ -410,7 +411,7 @@ private static RuntimeProperty Create(IProperty property, RuntimeTypeBase runtim
410411
typeMapping: property.GetTypeMapping(),
411412
sentinel: property.Sentinel);
412413

413-
private static RuntimeElementType Create(RuntimeProperty runtimeProperty, IElementType element, bool primitiveCollection)
414+
private static RuntimeElementType Create(RuntimeProperty runtimeProperty, IElementType element)
414415
=> runtimeProperty.SetElementType(
415416
element.ClrType,
416417
element.IsNullable,
@@ -422,8 +423,7 @@ private static RuntimeElementType Create(RuntimeProperty runtimeProperty, IEleme
422423
element.GetValueConverter(),
423424
element.GetValueComparer(),
424425
element.GetJsonValueReaderWriter(),
425-
element.GetTypeMapping(),
426-
primitiveCollection);
426+
element.GetTypeMapping());
427427

428428
/// <summary>
429429
/// Updates the property annotations that will be set on the read-only object.
@@ -539,7 +539,8 @@ private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeT
539539
var elementType = property.GetElementType();
540540
if (elementType != null)
541541
{
542-
var runtimeElementType = Create(runtimeProperty, elementType, property.IsPrimitiveCollection);
542+
Check.DebugAssert(property.IsPrimitiveCollection, $"{property.Name} has an element type, but it's not a primitive collection.");
543+
var runtimeElementType = Create(runtimeProperty, elementType);
543544
CreateAnnotations(
544545
elementType, runtimeElementType, static (convention, annotations, source, target, runtime) =>
545546
convention.ProcessElementTypeAnnotations(annotations, source, target, runtime));

src/EFCore/Metadata/IElementType.cs

+6
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,10 @@ public interface IElementType : IReadOnlyElementType, IAnnotatable
1919
[DebuggerStepThrough]
2020
get => (IProperty)((IReadOnlyElementType)this).CollectionProperty;
2121
}
22+
23+
/// <summary>
24+
/// Gets the <see cref="ValueComparer" /> for this property.
25+
/// </summary>
26+
/// <returns>The comparer.</returns>
27+
new ValueComparer GetValueComparer();
2228
}

src/EFCore/Metadata/Internal/ElementType.cs

+10
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,16 @@ void IMutableElementType.SetProviderClrType(Type? providerClrType)
895895
providerClrType,
896896
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
897897

898+
/// <summary>
899+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
900+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
901+
/// any release. You should only use it directly in your code with extreme caution and knowing that
902+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
903+
/// </summary>
904+
[DebuggerStepThrough]
905+
ValueComparer IElementType.GetValueComparer()
906+
=> GetValueComparer()!;
907+
898908
/// <summary>
899909
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
900910
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

0 commit comments

Comments
 (0)