Skip to content

Commit

Permalink
Add support for primitive collections to the compiled model
Browse files Browse the repository at this point in the history
Fixes #35047
  • Loading branch information
AndriySvyryd committed Jan 2, 2025
1 parent 281031a commit ba44ddb
Show file tree
Hide file tree
Showing 71 changed files with 21,535 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query.Internal;

Expand Down Expand Up @@ -1179,18 +1180,7 @@ private void Create(
var valueComparerType = (Type?)property[CoreAnnotationNames.ValueComparerType];
if (valueComparerType != null)
{
AddNamespace(valueComparerType, parameters.Namespaces);

var valueComparerString = $"new {_code.Reference(valueComparerType)}()";
if (property.ClrType.IsNullableValueType())
{
var valueComparerElementType = ((ValueComparer)Activator.CreateInstance(valueComparerType)!).Type;
if (!valueComparerElementType.IsNullableValueType())
{
AddNamespace(typeof(NullableValueComparer<>), parameters.Namespaces);
valueComparerString = $"new NullableValueComparer<{_code.Reference(valueComparerType)}>({valueComparerString})";
}
}
var valueComparerString = CreateValueComparerType(valueComparerType, property.ClrType, parameters);

mainBuilder.AppendLine(",")
.Append("valueComparer: ")
Expand Down Expand Up @@ -1240,11 +1230,13 @@ private void Create(
&& converter != null
&& property[CoreAnnotationNames.ValueConverter] != null
&& !parameters.ForNativeAot;
var typeMappingSet = false;

if (parameters.ForNativeAot
|| (shouldSetConverter && converter!.MappingHints != null))
{
shouldSetConverter = false;
typeMappingSet = true;
mainBuilder.Append(variableName).Append(".TypeMapping = ");
_annotationCodeGenerator.Create(property.GetTypeMapping(), property, propertyParameters);
mainBuilder.AppendLine(";");
Expand Down Expand Up @@ -1311,14 +1303,151 @@ private void Create(
.AppendLine(");");
}

var elementType = property.GetElementType();
if (elementType != null)
{
Check.DebugAssert(property.IsPrimitiveCollection, $"{property.Name} has an element type, but it's not a primitive collection.");
Create(elementType, typeMappingSet, propertyParameters);
}

CreateAnnotations(
property,
_annotationCodeGenerator.Generate,
parameters with { TargetName = variableName });
propertyParameters);

mainBuilder.AppendLine();
}

private void Create(IElementType elementType, bool typeMappingSet, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
{
var mainBuilder = parameters.MainBuilder;
var elementVariableName = _code.Identifier(parameters.TargetName + "ElementType", elementType, parameters.ScopeObjects, capitalize: false);
var elementParameters = parameters with { TargetName = elementVariableName };

mainBuilder
.Append("var ").Append(elementVariableName).Append(" = ")
.Append(parameters.TargetName).Append(".SetElementType(").IncrementIndent()
.Append(_code.Literal(elementType.ClrType));

if (elementType.IsNullable)
{
mainBuilder.AppendLine(",")
.Append("nullable: ")
.Append(_code.Literal(elementType.IsNullable));
}

if (elementType.GetMaxLength() != null)
{
mainBuilder.AppendLine(",")
.Append("maxLength: ")
.Append(_code.Literal(elementType.GetMaxLength()));
}

if (elementType.IsUnicode() != null)
{
mainBuilder.AppendLine(",")
.Append("unicode: ")
.Append(_code.Literal(elementType.IsUnicode()));
}

if (elementType.GetPrecision() != null)
{
mainBuilder.AppendLine(",")
.Append("precision: ")
.Append(_code.Literal(elementType.GetPrecision()));
}

if (elementType.GetScale() != null)
{
mainBuilder.AppendLine(",")
.Append("scale: ")
.Append(_code.Literal(elementType.GetScale()));
}

var providerClrType = elementType.GetProviderClrType();
if (providerClrType != null)
{
AddNamespace(providerClrType, parameters.Namespaces);
mainBuilder.AppendLine(",")
.Append("providerClrType: ")
.Append(_code.Literal(providerClrType));
}

var jsonValueReaderWriterType = (Type?)elementType[CoreAnnotationNames.JsonValueReaderWriterType];
if (jsonValueReaderWriterType != null)
{
mainBuilder.AppendLine(",")
.Append("jsonValueReaderWriter: ");
CSharpRuntimeAnnotationCodeGenerator.CreateJsonValueReaderWriter(jsonValueReaderWriterType, parameters, _code);
}

mainBuilder
.AppendLine(");")
.DecrementIndent();

var converter = elementType.FindTypeMapping()?.Converter;
var shouldSetConverter = providerClrType == null
&& converter != null
&& elementType[CoreAnnotationNames.ValueConverter] != null
&& !parameters.ForNativeAot;

if (parameters.ForNativeAot
|| (shouldSetConverter && converter!.MappingHints != null))
{
shouldSetConverter = false;
mainBuilder.Append(elementVariableName).Append(".TypeMapping = ");

if (typeMappingSet)
{
mainBuilder.Append(parameters.TargetName).Append(".TypeMapping.ElementTypeMapping");
}
else
{
_annotationCodeGenerator.Create(elementType.GetTypeMapping(), elementParameters);
}

mainBuilder.AppendLine(";");
}

if (shouldSetConverter)
{
mainBuilder.Append(elementVariableName).Append(".SetValueConverter(");
_annotationCodeGenerator.Create(converter!, parameters);
mainBuilder.AppendLine(");");
}

var valueComparer = elementType.GetValueComparer();
var typeMappingComparer = elementType.GetTypeMapping().Comparer;
if ((!parameters.ForNativeAot || valueComparer != typeMappingComparer)
&& (parameters.ForNativeAot || elementType[CoreAnnotationNames.ValueComparer] != null))
{
SetValueComparer(valueComparer, typeMappingComparer, nameof(CoreTypeMapping.Comparer), elementParameters);
}

CreateAnnotations(
elementType,
_annotationCodeGenerator.Generate,
elementParameters);
}

private string CreateValueComparerType(Type valueComparerType, Type clrType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
{
AddNamespace(valueComparerType, parameters.Namespaces);

var valueComparerString = $"new {_code.Reference(valueComparerType)}()";
if (clrType.IsNullableValueType())
{
var valueComparerElementType = ((ValueComparer)Activator.CreateInstance(valueComparerType)!).Type;
if (!valueComparerElementType.IsNullableValueType())
{
AddNamespace(typeof(NullableValueComparer<>), parameters.Namespaces);
valueComparerString = $"new NullableValueComparer<{_code.Reference(valueComparerType)}>({valueComparerString})";
}
}

return valueComparerString;
}

private void SetValueComparer(
ValueComparer valueComparer,
ValueComparer typeMappingComparer,
Expand Down
6 changes: 0 additions & 6 deletions src/EFCore.Relational/Storage/RelationalTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -572,12 +572,6 @@ public virtual DbParameter CreateParameter(

if (nullable.HasValue)
{
Check.DebugAssert(
nullable.Value
|| !direction.HasFlag(ParameterDirection.Input)
|| value != null,
"Null value in a non-nullable input parameter");

parameter.IsNullable = nullable.Value;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ public override async Task ExecuteAsync(

await ConsumeAsync(dataReader, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex) when (ex is not DbUpdateException and not OperationCanceledException)
catch (Exception ex) when (ex is not DbUpdateException and not OperationCanceledException and not UnreachableException)
{
throw new DbUpdateException(
RelationalStrings.UpdateStoreException,
Expand Down
18 changes: 18 additions & 0 deletions src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,24 @@ public virtual void Generate(IServiceProperty property, CSharpRuntimeAnnotationC
GenerateSimpleAnnotations(parameters);
}

/// <inheritdoc />
public virtual void Generate(IElementType elementType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
{
if (!parameters.IsRuntime)
{
var annotations = parameters.Annotations;
foreach (var (key, _) in annotations)
{
if (CoreAnnotationNames.AllNames.Contains(key))
{
annotations.Remove(key);
}
}
}

GenerateSimpleAnnotations(parameters);
}

/// <inheritdoc />
public virtual void Generate(IKey key, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ public interface ICSharpRuntimeAnnotationCodeGenerator
/// <param name="parameters">Additional parameters used during code generation.</param>
void Generate(IServiceProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters);

/// <summary>
/// Generates code to create the given annotations.
/// </summary>
/// <param name="elementType">The element type to which the annotations are applied.</param>
/// <param name="parameters">Additional parameters used during code generation.</param>
void Generate(IElementType elementType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters);

/// <summary>
/// Generates code to create the given annotations.
/// </summary>
Expand Down
11 changes: 6 additions & 5 deletions src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ protected virtual RuntimeModel Create(IModel model)
var elementType = property.GetElementType();
if (elementType != null)
{
var runtimeElementType = Create(runtimeProperty, elementType, property.IsPrimitiveCollection);
Check.DebugAssert(property.IsPrimitiveCollection, $"{property.Name} has an element type, but it's not a primitive collection.");
var runtimeElementType = Create(runtimeProperty, elementType);
CreateAnnotations(
elementType, runtimeElementType, static (convention, annotations, source, target, runtime) =>
convention.ProcessElementTypeAnnotations(annotations, source, target, runtime));
Expand Down Expand Up @@ -410,7 +411,7 @@ private static RuntimeProperty Create(IProperty property, RuntimeTypeBase runtim
typeMapping: property.GetTypeMapping(),
sentinel: property.Sentinel);

private static RuntimeElementType Create(RuntimeProperty runtimeProperty, IElementType element, bool primitiveCollection)
private static RuntimeElementType Create(RuntimeProperty runtimeProperty, IElementType element)
=> runtimeProperty.SetElementType(
element.ClrType,
element.IsNullable,
Expand All @@ -422,8 +423,7 @@ private static RuntimeElementType Create(RuntimeProperty runtimeProperty, IEleme
element.GetValueConverter(),
element.GetValueComparer(),
element.GetJsonValueReaderWriter(),
element.GetTypeMapping(),
primitiveCollection);
element.GetTypeMapping());

/// <summary>
/// Updates the property annotations that will be set on the read-only object.
Expand Down Expand Up @@ -539,7 +539,8 @@ private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeT
var elementType = property.GetElementType();
if (elementType != null)
{
var runtimeElementType = Create(runtimeProperty, elementType, property.IsPrimitiveCollection);
Check.DebugAssert(property.IsPrimitiveCollection, $"{property.Name} has an element type, but it's not a primitive collection.");
var runtimeElementType = Create(runtimeProperty, elementType);
CreateAnnotations(
elementType, runtimeElementType, static (convention, annotations, source, target, runtime) =>
convention.ProcessElementTypeAnnotations(annotations, source, target, runtime));
Expand Down
6 changes: 6 additions & 0 deletions src/EFCore/Metadata/IElementType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ public interface IElementType : IReadOnlyElementType, IAnnotatable
[DebuggerStepThrough]
get => (IProperty)((IReadOnlyElementType)this).CollectionProperty;
}

/// <summary>
/// Gets the <see cref="ValueComparer" /> for this property.
/// </summary>
/// <returns>The comparer.</returns>
new ValueComparer GetValueComparer();
}
10 changes: 10 additions & 0 deletions src/EFCore/Metadata/Internal/ElementType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,16 @@ void IMutableElementType.SetProviderClrType(Type? providerClrType)
providerClrType,
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

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

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
Loading

0 comments on commit ba44ddb

Please sign in to comment.