Skip to content
Merged
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
265 changes: 117 additions & 148 deletions src/EFCore/Metadata/Internal/Property.cs
Original file line number Diff line number Diff line change
Expand Up @@ -923,53 +923,17 @@ public virtual (ValueConverter? ValueConverter, Type? ValueConverterType, Type?
}

var principalProperty = foreignKey.PrincipalKey.Properties[propertyIndex];
if (principalProperty == cycleBreakingProperty)
if (principalProperty == cycleBreakingProperty
|| TryGetConversion(
principalProperty, throwOnValueConverterConflict, throwOnProviderClrTypeConflict,
ref valueConverter, ref valueConverterType, ref providerClrType))
{
break;
}

var annotationFound = GetConversion(
principalProperty,
throwOnValueConverterConflict,
throwOnProviderClrTypeConflict,
ref valueConverter,
ref valueConverterType,
ref providerClrType);
if (!annotationFound)
{
var useQueue = queue != null;
if (currentNode != null)
{
useQueue = true;
queue =
new Queue<(Property CurrentProperty, Property CycleBreakingProperty, int CyclePosition, int MaxCycleLength
)>();
queue.Enqueue(currentNode.Value);
visitedProperties = [property];
}

if (visitedProperties?.Contains(principalProperty) == true)
{
break;
}

if (cyclePosition == maxCycleLength - 1)
{
// We need to use different primes to ensure a different cycleBreakingProperty is selected
// each time when traversing properties that participate in multiple relationship cycles
currentNode = (principalProperty, property, 0, HashHelpers.GetPrime(maxCycleLength << 1));
}
else
{
currentNode = (principalProperty, cycleBreakingProperty, cyclePosition + 1, maxCycleLength);
}

if (useQueue)
{
queue!.Enqueue(currentNode.Value);
currentNode = null;
}
}
EnqueueForTraversal(
principalProperty, property, cycleBreakingProperty, cyclePosition, maxCycleLength,
ref currentNode, ref queue, ref visitedProperties);

break;
}
Expand All @@ -978,134 +942,139 @@ public virtual (ValueConverter? ValueConverter, Type? ValueConverterType, Type?

return (valueConverter, valueConverterType, providerClrType);

bool GetConversion(
static void EnqueueForTraversal(
Property principalProperty,
Property property,
Property cycleBreakingProperty,
int cyclePosition,
int maxCycleLength,
ref (Property CurrentProperty, Property CycleBreakingProperty, int CyclePosition, int MaxCycleLength)? currentNode,
ref Queue<(Property CurrentProperty, Property CycleBreakingProperty, int CyclePosition, int MaxCycleLength)>? queue,
ref HashSet<Property>? visitedProperties)
{
var useQueue = queue != null;
if (currentNode != null)
{
useQueue = true;
queue =
new Queue<(Property CurrentProperty, Property CycleBreakingProperty, int CyclePosition, int MaxCycleLength)>();
queue.Enqueue(currentNode.Value);
visitedProperties = [property];
}

if (visitedProperties?.Contains(principalProperty) == true)
{
return;
}

// We need to use different primes to ensure a different cycleBreakingProperty is selected
// each time when traversing properties that participate in multiple relationship cycles
currentNode = cyclePosition == maxCycleLength - 1
? (principalProperty, property, 0, HashHelpers.GetPrime(maxCycleLength << 1))
: (principalProperty, cycleBreakingProperty, cyclePosition + 1, maxCycleLength);

if (useQueue)
{
queue!.Enqueue(currentNode.Value);
currentNode = null;
}
}

bool TryGetConversion(
Property principalProperty,
bool throwOnValueConverterConflict,
bool throwOnProviderClrTypeConflict,
ref ValueConverter? valueConverter,
ref Type? valueConverterType,
ref Type? providerClrType)
{
var annotationFound = false;
var valueConverterAnnotation = principalProperty.FindAnnotation(CoreAnnotationNames.ValueConverter);
if (valueConverterAnnotation != null)
var vcFound = TryFindAndValidateConversionAnnotation(
principalProperty, CoreAnnotationNames.ValueConverter,
valueConverter?.GetType(), valueConverterType,
throwOnProviderClrTypeConflict ? providerClrType : null, null,
out var vcValue);
if (vcFound && vcValue is ValueConverter vc)
{
var annotationValue = (ValueConverter?)valueConverterAnnotation.Value;
if (annotationValue != null)
{
if (valueConverter != null
&& annotationValue.GetType() != valueConverter.GetType())
{
throw new InvalidOperationException(
CoreStrings.ConflictingRelationshipConversions(
DeclaringType.DisplayName(), Name,
valueConverter.GetType().ShortDisplayName(), annotationValue.GetType().ShortDisplayName()));
}

if (valueConverterType != null
&& annotationValue.GetType() != valueConverterType)
{
throw new InvalidOperationException(
CoreStrings.ConflictingRelationshipConversions(
DeclaringType.DisplayName(), Name,
valueConverterType.ShortDisplayName(), annotationValue.GetType().ShortDisplayName()));
}

if (providerClrType != null
&& throwOnProviderClrTypeConflict)
{
throw new InvalidOperationException(
CoreStrings.ConflictingRelationshipConversions(
DeclaringType.DisplayName(), Name,
providerClrType.ShortDisplayName(), annotationValue.GetType().ShortDisplayName()));
}

valueConverter = annotationValue;
}

annotationFound = true;
valueConverter = vc;
}

var valueConverterTypeAnnotation = principalProperty.FindAnnotation(CoreAnnotationNames.ValueConverterType);
if (valueConverterTypeAnnotation != null)
var vctFound = TryFindAndValidateConversionAnnotation(
principalProperty, CoreAnnotationNames.ValueConverterType,
valueConverter?.GetType(), valueConverterType,
throwOnProviderClrTypeConflict ? providerClrType : null, null,
out var vctValue);
if (vctFound && vctValue is Type vct)
{
var annotationValue = (Type?)valueConverterTypeAnnotation.Value;
if (annotationValue != null)
{
if (valueConverter != null
&& valueConverter.GetType() != annotationValue)
{
throw new InvalidOperationException(
CoreStrings.ConflictingRelationshipConversions(
DeclaringType.DisplayName(), Name,
valueConverter.GetType().ShortDisplayName(), annotationValue.ShortDisplayName()));
}

if (valueConverterType != null
&& valueConverterType != annotationValue)
{
throw new InvalidOperationException(
CoreStrings.ConflictingRelationshipConversions(
DeclaringType.DisplayName(), Name,
valueConverterType.ShortDisplayName(), annotationValue.ShortDisplayName()));
}
valueConverterType = vct;
}

if (providerClrType != null
&& throwOnProviderClrTypeConflict)
{
throw new InvalidOperationException(
CoreStrings.ConflictingRelationshipConversions(
DeclaringType.DisplayName(), Name,
providerClrType.ShortDisplayName(), annotationValue.ShortDisplayName()));
}
var pctFound = TryFindAndValidateConversionAnnotation(
principalProperty, CoreAnnotationNames.ProviderClrType,
providerClrType, null,
throwOnValueConverterConflict ? valueConverter?.GetType() : null,
throwOnValueConverterConflict ? valueConverterType : null,
out var pctValue);
if (pctFound && pctValue is Type pct)
{
providerClrType = pct;
}

valueConverterType = annotationValue;
}
return vcFound || vctFound || pctFound;
}

annotationFound = true;
bool TryFindAndValidateConversionAnnotation(
Property principalProperty,
string annotationName,
Type? firstTypeToCheck, Type? secondTypeToCheck,
Type? firstConflictingType, Type? secondConflictingType,
out object? value)
{
var annotation = principalProperty.FindAnnotation(annotationName);
if (annotation == null)
{
value = null;
return false;
}

var providerClrTypeAnnotation = principalProperty.FindAnnotation(CoreAnnotationNames.ProviderClrType);
if (providerClrTypeAnnotation != null)
var type = annotation.Value switch
{
var annotationValue = (Type?)providerClrTypeAnnotation.Value;
if (annotationValue != null)
{
if (providerClrType != null
&& annotationValue != providerClrType)
{
throw new InvalidOperationException(
CoreStrings.ConflictingRelationshipConversions(
DeclaringType.DisplayName(), Name,
providerClrType.ShortDisplayName(), annotationValue.ShortDisplayName()));
}
ValueConverter vc => vc.GetType(),
Type t => t,
_ => null
};

if (valueConverter != null
&& throwOnValueConverterConflict)
{
throw new InvalidOperationException(
CoreStrings.ConflictingRelationshipConversions(
DeclaringType.DisplayName(), Name,
valueConverter.GetType().ShortDisplayName(), annotationValue.ShortDisplayName()));
}

if (valueConverterType != null
&& throwOnValueConverterConflict)
{
throw new InvalidOperationException(
CoreStrings.ConflictingRelationshipConversions(
DeclaringType.DisplayName(), Name,
valueConverterType.ShortDisplayName(), annotationValue.ShortDisplayName()));
}
if (type != null)
{
ThrowIfIncompatible(type, firstTypeToCheck);
ThrowIfIncompatible(type, secondTypeToCheck);
ThrowIfConflicting(type, firstConflictingType);
ThrowIfConflicting(type, secondConflictingType);
}

providerClrType = annotationValue;
}
value = annotation.Value;
return true;
}
Comment thread
AndriySvyryd marked this conversation as resolved.

annotationFound = true;
void ThrowIfIncompatible(Type newType, Type? existingType)
{
if (existingType != null && newType != existingType)
{
ThrowConflictingConversions(existingType, newType);
}
}

return annotationFound;
void ThrowIfConflicting(Type newType, Type? existingType)
{
if (existingType != null)
{
ThrowConflictingConversions(existingType, newType);
}
}

void ThrowConflictingConversions(Type existingType, Type newType)
=> throw new InvalidOperationException(
CoreStrings.ConflictingRelationshipConversions(
DeclaringType.DisplayName(), Name, existingType.ShortDisplayName(), newType.ShortDisplayName()));
}

/// <summary>
Expand Down
Loading