Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for primitive collections to the compiled model #35370

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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