Skip to content
Merged
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
12 changes: 11 additions & 1 deletion src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ private bool HasNullabilityError(
return false;
}

if (!property.IsNullable && propertyValue == null)
if (!IsNullable(property) && propertyValue == null)
{
nullabilityErrors.Add(property);

Expand All @@ -378,6 +378,16 @@ private bool HasNullabilityError(
return false;
}

private static bool IsNullable(IProperty property)
=> property.IsNullable
|| (property.DeclaringType is IComplexType complexType
&& IsNullable(complexType.ComplexProperty));

private static bool IsNullable(IComplexProperty property)
=> property.IsNullable
||( property.DeclaringType is IComplexType complexType
&& IsNullable(complexType.ComplexProperty));

private void ThrowNullabilityErrorException(
IUpdateEntry entry,
IList<IProperty> nullabilityErrors)
Expand Down
8 changes: 7 additions & 1 deletion src/EFCore.Relational/Update/ColumnModification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,13 @@ private void SetOriginalValue(object? value)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static object? GetCurrentValue(IUpdateEntry entry, IProperty property)
=> entry.GetCurrentValue(property);
=> property.DeclaringType switch
{
IComplexType { ComplexProperty: var complexProperty }
when complexProperty.IsNullable && !complexProperty.IsCollection && entry.GetCurrentValue(complexProperty) == null
=> null,
_ => entry.GetCurrentValue(property)
};

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
208 changes: 208 additions & 0 deletions src/EFCore/Metadata/Builders/ComplexCollectionBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,124 @@ public virtual ComplexCollectionBuilder<TComplex> ComplexProperty<TProperty>(
return this;
}

/// <summary>
/// Returns an object that can be used to configure a complex property of the complex type.
/// If no property with the given name exists, then a new property will be added.
/// </summary>
/// <remarks>
/// When adding a new property, if a property with the same name exists in the complex class
/// then it will be added to the model. If no property exists in the complex class, then
/// a new shadow state complex property will be added. A shadow state property is one that does not have a
/// corresponding property in the complex class. The current value for the property is stored in
/// the <see cref="ChangeTracker" /> rather than being stored in instances of the complex class.
/// </remarks>
/// <typeparam name="TProperty">The type of the property to be configured.</typeparam>
/// <param name="propertyExpression">
/// A lambda expression representing the property to be configured (
/// <c>blog => blog.Url</c>).
/// </param>
/// <returns>An object that can be used to configure the property.</returns>
public virtual ComplexPropertyBuilder<TProperty> ComplexProperty<TProperty>(
Expression<Func<TComplex, TProperty?>> propertyExpression)
where TProperty : struct
=> new(
TypeBuilder.ComplexProperty(
Check.NotNull(propertyExpression).GetMemberAccess(),
complexTypeName: null,
collection: false,
ConfigurationSource.Explicit)!.Metadata);

/// <summary>
/// Returns an object that can be used to configure a complex property of the complex type.
/// If no property with the given name exists, then a new property will be added.
/// </summary>
/// <remarks>
/// When adding a new property, if a property with the same name exists in the complex class
/// then it will be added to the model. If no property exists in the complex class, then
/// a new shadow state complex property will be added. A shadow state property is one that does not have a
/// corresponding property in the complex class. The current value for the property is stored in
/// the <see cref="ChangeTracker" /> rather than being stored in instances of the complex class.
/// </remarks>
/// <typeparam name="TProperty">The type of the property to be configured.</typeparam>
/// <param name="propertyExpression">
/// A lambda expression representing the property to be configured (
/// <c>blog => blog.Url</c>).
/// </param>
/// <param name="complexTypeName">The name of the complex type.</param>
/// <returns>An object that can be used to configure the property.</returns>
public virtual ComplexPropertyBuilder<TProperty> ComplexProperty<TProperty>(
Expression<Func<TComplex, TProperty?>> propertyExpression,
string complexTypeName)
where TProperty : struct
=> new(
TypeBuilder.ComplexProperty(
Check.NotNull(propertyExpression).GetMemberAccess(),
Check.NotEmpty(complexTypeName),
collection: false,
ConfigurationSource.Explicit)!.Metadata);

/// <summary>
/// Configures a complex property of the complex type.
/// If no property with the given name exists, then a new property will be added.
/// </summary>
/// <remarks>
/// When adding a new property, if a property with the same name exists in the complex class
/// then it will be added to the model. If no property exists in the complex class, then
/// a new shadow state complex property will be added. A shadow state property is one that does not have a
/// corresponding property in the complex class. The current value for the property is stored in
/// the <see cref="ChangeTracker" /> rather than being stored in instances of the complex class.
/// </remarks>
/// <typeparam name="TProperty">The type of the property to be configured.</typeparam>
/// <param name="propertyExpression">
/// A lambda expression representing the property to be configured (
/// <c>blog => blog.Url</c>).
/// </param>
/// <param name="buildAction">An action that performs configuration of the property.</param>
/// <returns>The same builder instance so that multiple configuration calls can be chained.</returns>
public virtual ComplexCollectionBuilder<TComplex> ComplexProperty<TProperty>(
Expression<Func<TComplex, TProperty?>> propertyExpression,
Action<ComplexPropertyBuilder<TProperty>> buildAction)
where TProperty : struct
{
Check.NotNull(buildAction);

buildAction(ComplexProperty(propertyExpression));

return this;
}

/// <summary>
/// Configures a complex property of the complex type.
/// If no property with the given name exists, then a new property will be added.
/// </summary>
/// <remarks>
/// When adding a new property, if a property with the same name exists in the complex class
/// then it will be added to the model. If no property exists in the complex class, then
/// a new shadow state complex property will be added. A shadow state property is one that does not have a
/// corresponding property in the complex class. The current value for the property is stored in
/// the <see cref="ChangeTracker" /> rather than being stored in instances of the complex class.
/// </remarks>
/// <typeparam name="TProperty">The type of the property to be configured.</typeparam>
/// <param name="propertyExpression">
/// A lambda expression representing the property to be configured (
/// <c>blog => blog.Url</c>).
/// </param>
/// <param name="complexTypeName">The name of the complex type.</param>
/// <param name="buildAction">An action that performs configuration of the property.</param>
/// <returns>The same builder instance so that multiple configuration calls can be chained.</returns>
public virtual ComplexCollectionBuilder<TComplex> ComplexProperty<TProperty>(
Expression<Func<TComplex, TProperty?>> propertyExpression,
string complexTypeName,
Action<ComplexPropertyBuilder<TProperty>> buildAction)
where TProperty : struct
{
Check.NotNull(buildAction);

buildAction(ComplexProperty(propertyExpression, complexTypeName));

return this;
}

/// <summary>
/// Configures a complex collection of the complex type.
/// If no property with the given name exists, then a new property will be added.
Expand Down Expand Up @@ -506,6 +624,96 @@ public virtual ComplexCollectionBuilder<TComplex> ComplexCollection<TElement>(
return this;
}

/// <summary>
/// Returns an object that can be used to configure a complex collection property of the complex type.
/// If the specified property is not already part of the model, it will be added.
/// </summary>
/// <typeparam name="TElement">The element type.</typeparam>
/// <param name="propertyExpression">
/// A lambda expression representing the property to be configured (
/// <c>blog => blog.Url</c>).
/// </param>
/// <returns>An object that can be used to configure the complex collection property.</returns>
public virtual ComplexCollectionBuilder<TElement> ComplexCollection<TElement>(
Expression<Func<TComplex, IEnumerable<TElement?>?>> propertyExpression)
where TElement : struct
=> new(
TypeBuilder.ComplexProperty(
Check.NotNull(propertyExpression).GetMemberAccess(),
complexTypeName: null,
collection: true,
ConfigurationSource.Explicit)!.Metadata);

/// <summary>
/// Returns an object that can be used to configure a complex collection property of the complex type.
/// If the specified property is not already part of the model, it will be added.
/// </summary>
/// <typeparam name="TElement">The element type.</typeparam>
/// <param name="propertyExpression">
/// A lambda expression representing the property to be configured (
/// <c>blog => blog.Url</c>).
/// </param>
/// <param name="complexTypeName">The name of the complex type.</param>
/// <returns>An object that can be used to configure the complex collection property.</returns>
public virtual ComplexCollectionBuilder<TElement> ComplexCollection<TElement>(
Expression<Func<TComplex, IEnumerable<TElement?>?>> propertyExpression,
string complexTypeName)
where TElement : struct
=> new(
TypeBuilder.ComplexProperty(
Check.NotNull(propertyExpression).GetMemberAccess(),
Check.NotEmpty(complexTypeName),
collection: true,
ConfigurationSource.Explicit)!.Metadata);

/// <summary>
/// Configures a complex collection property of the complex type.
/// If the specified property is not already part of the model, it will be added.
/// </summary>
/// <typeparam name="TElement">The element type.</typeparam>
/// <param name="propertyExpression">
/// A lambda expression representing the property to be configured (
/// <c>blog => blog.Url</c>).
/// </param>
/// <param name="buildAction">An action that performs configuration of the property.</param>
/// <returns>An object that can be used to configure the complex collection property.</returns>
public virtual ComplexCollectionBuilder<TComplex> ComplexCollection<TElement>(
Expression<Func<TComplex, IEnumerable<TElement?>?>> propertyExpression,
Action<ComplexCollectionBuilder<TElement>> buildAction)
where TElement : struct
{
Check.NotNull(buildAction);

buildAction(ComplexCollection(propertyExpression));

return this;
}

/// <summary>
/// Configures a complex collection property of the complex type.
/// If the specified property is not already part of the model, it will be added.
/// </summary>
/// <typeparam name="TElement">The element type.</typeparam>
/// <param name="propertyExpression">
/// A lambda expression representing the property to be configured (
/// <c>blog => blog.Url</c>).
/// </param>
/// <param name="complexTypeName">The name of the complex type.</param>
/// <param name="buildAction">An action that performs configuration of the property.</param>
/// <returns>An object that can be used to configure the complex collection property.</returns>
public virtual ComplexCollectionBuilder<TComplex> ComplexCollection<TElement>(
Expression<Func<TComplex, IEnumerable<TElement?>?>> propertyExpression,
string complexTypeName,
Action<ComplexCollectionBuilder<TElement>> buildAction)
where TElement : struct
{
Check.NotNull(buildAction);

buildAction(ComplexCollection(propertyExpression, complexTypeName));

return this;
}

/// <summary>
/// Excludes the given property from the entity type. This method is typically used to remove properties
/// or navigations from the entity type that were added by convention.
Expand Down
Loading