Skip to content

Commit

Permalink
Allow to ignore entity types and properties by base type or interface (
Browse files Browse the repository at this point in the history
…#25025)

Allow to specify a value conversion and other facets for all properties of a given type

Fixes #3867
Fixes #10784
  • Loading branch information
AndriySvyryd committed Jun 9, 2021
1 parent bd735ed commit 4a67481
Show file tree
Hide file tree
Showing 49 changed files with 2,404 additions and 733 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Utilities;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// Relational database specific extension methods for <see cref="PropertyBuilder" />.
/// </summary>
public static class PropertiesConfigurationBuilderExtensions
{
/// <summary>
/// Configures the data type of the column that the property maps to when targeting a relational database.
/// This should be the complete type name, including precision, scale, length, etc.
/// </summary>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <param name="typeName"> The name of the data type of the column. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertiesConfigurationBuilder HaveColumnType(
this PropertiesConfigurationBuilder propertyBuilder,
string typeName)
{
Check.NotNull(propertyBuilder, nameof(propertyBuilder));
Check.NotEmpty(typeName, nameof(typeName));

propertyBuilder.HaveAnnotation(RelationalAnnotationNames.ColumnType, typeName);

return propertyBuilder;
}

/// <summary>
/// Configures the data type of the column that the property maps to when targeting a relational database.
/// This should be the complete type name, including precision, scale, length, etc.
/// </summary>
/// <typeparam name="TProperty"> The type of the property being configured. </typeparam>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <param name="typeName"> The name of the data type of the column. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertiesConfigurationBuilder<TProperty> HaveColumnType<TProperty>(
this PropertiesConfigurationBuilder<TProperty> propertyBuilder,
string typeName)
=> (PropertiesConfigurationBuilder<TProperty>)HaveColumnType((PropertiesConfigurationBuilder)propertyBuilder, typeName);

/// <summary>
/// Configures the property as capable of storing only fixed-length data, such as strings.
/// </summary>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <param name="fixedLength"> A value indicating whether the property is constrained to fixed length values. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public static PropertiesConfigurationBuilder AreFixedLength(
this PropertiesConfigurationBuilder propertyBuilder,
bool fixedLength = true)
{
Check.NotNull(propertyBuilder, nameof(propertyBuilder));

propertyBuilder.HaveAnnotation(RelationalAnnotationNames.IsFixedLength, fixedLength);

return propertyBuilder;
}

/// <summary>
/// Configures the property as capable of storing only fixed-length data, such as strings.
/// </summary>
/// <typeparam name="TProperty"> The type of the property being configured. </typeparam>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <param name="fixedLength"> A value indicating whether the property is constrained to fixed length values. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public static PropertiesConfigurationBuilder<TProperty> AreFixedLength<TProperty>(
this PropertiesConfigurationBuilder<TProperty> propertyBuilder,
bool fixedLength = true)
=> (PropertiesConfigurationBuilder<TProperty>)AreFixedLength((PropertiesConfigurationBuilder)propertyBuilder, fixedLength);

/// <summary>
/// Configures the property to use the given collation. The database column will be created with the given
/// collation, and it will be used implicitly in all collation-sensitive operations.
/// </summary>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <param name="collation"> The collation for the column. </param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static PropertiesConfigurationBuilder UseCollation(this PropertiesConfigurationBuilder propertyBuilder, string collation)
{
Check.NotNull(propertyBuilder, nameof(propertyBuilder));
Check.NotEmpty(collation, nameof(collation));

propertyBuilder.HaveAnnotation(RelationalAnnotationNames.Collation, collation);

return propertyBuilder;
}

/// <summary>
/// Configures the property to use the given collation. The database column will be created with the given
/// collation, and it will be used implicitly in all collation-sensitive operations.
/// </summary>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <param name="collation"> The collation for the column. </param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static PropertiesConfigurationBuilder<TProperty> UseCollation<TProperty>(
this PropertiesConfigurationBuilder<TProperty> propertyBuilder,
string collation)
=> (PropertiesConfigurationBuilder<TProperty>)UseCollation((PropertiesConfigurationBuilder)propertyBuilder, collation);
}
}
57 changes: 57 additions & 0 deletions src/EFCore/Infrastructure/Internal/MemberInfoNameComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Reflection;

namespace Microsoft.EntityFrameworkCore.Infrastructure.Internal
{
/// <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>
// Sealed for perf
public sealed class MemberInfoNameComparer : IComparer<MemberInfo>
{
/// <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>
public static readonly MemberInfoNameComparer Instance = new();

private MemberInfoNameComparer()
{
}

/// <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>
public int Compare(MemberInfo? x, MemberInfo? y)
{
if (ReferenceEquals(x, y))
{
return 0;
}

if (x is null)
{
return -1;
}

if (y is null)
{
return 1;
}

return StringComparer.Ordinal.Compare(x.Name, y.Name);
}
}
}
55 changes: 30 additions & 25 deletions src/EFCore/Infrastructure/ModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ public virtual void Validate(IModel model, IDiagnosticsLogger<DbLoggerCategory.M
{
throw new InvalidOperationException(
CoreStrings.PropertyNotMapped(
entityType.DisplayName(), unmappedProperty.Name, unmappedProperty.ClrType.ShortDisplayName()));
entityType.DisplayName(), unmappedProperty.Name,
(unmappedProperty.GetValueConverter()?.ProviderClrType ?? unmappedProperty.ClrType).ShortDisplayName()));
}

if (entityType.ClrType == Model.DefaultPropertyBagType)
Expand All @@ -175,45 +176,52 @@ public virtual void Validate(IModel model, IDiagnosticsLogger<DbLoggerCategory.M
.Where(pi => pi.IsCandidateProperty(needsWrite: false))
.Select(pi => pi.GetSimpleMemberName()));

clrProperties.ExceptWith(entityType.GetProperties().Select(p => p.Name));
clrProperties.ExceptWith(entityType.GetNavigations().Select(p => p.Name));
clrProperties.ExceptWith(entityType.GetSkipNavigations().Select(p => p.Name));
clrProperties.ExceptWith(entityType.GetServiceProperties().Select(p => p.Name));
clrProperties.ExceptWith(
((IEnumerable<IConventionPropertyBase>)entityType.GetProperties())
.Concat(entityType.GetNavigations())
.Concat(entityType.GetSkipNavigations())
.Concat(entityType.GetServiceProperties()).Select(p => p.Name));
if (entityType.IsPropertyBag)
{
clrProperties.ExceptWith(_dictionaryProperties);
}

clrProperties.RemoveWhere(p => entityType.FindIgnoredConfigurationSource(p) != null);

if (clrProperties.Count <= 0)
{
continue;
}

foreach (var clrProperty in clrProperties)
var configuration = ((Model)entityType.Model).Configuration;
foreach (var clrPropertyName in clrProperties)
{
var actualProperty = runtimeProperties[clrProperty];
var propertyType = actualProperty.PropertyType;
if (entityType.FindIgnoredConfigurationSource(clrPropertyName) != null)
{
continue;
}

var clrProperty = runtimeProperties[clrPropertyName];
var propertyType = clrProperty.PropertyType;
var targetSequenceType = propertyType.TryGetSequenceType();

if (conventionModel.FindIgnoredConfigurationSource(propertyType) != null
|| targetSequenceType != null
&& conventionModel.FindIgnoredConfigurationSource(targetSequenceType) != null)
|| conventionModel.IsIgnoredType(propertyType)
|| (targetSequenceType != null
&& (conventionModel.FindIgnoredConfigurationSource(targetSequenceType) != null
|| conventionModel.IsIgnoredType(targetSequenceType))))
{
continue;
}

var targetType = FindCandidateNavigationPropertyType(actualProperty);
Dependencies.MemberClassifier.GetNavigationCandidates(entityType).TryGetValue(clrProperty, out var targetType);
if (targetType == null
|| targetSequenceType == null)
|| targetSequenceType == null)
{
if (actualProperty.FindSetterProperty() == null)
if (clrProperty.FindSetterProperty() == null)
{
continue;
}

var sharedType = actualProperty.GetMemberType();
var sharedType = clrProperty.GetMemberType();
if (conventionModel.IsShared(sharedType))
{
targetType = sharedType;
Expand All @@ -234,7 +242,7 @@ public virtual void Validate(IModel model, IDiagnosticsLogger<DbLoggerCategory.M
if ((!entityType.IsKeyless
|| targetSequenceType == null)
&& entityType.GetDerivedTypes().All(
dt => dt.GetDeclaredNavigations().FirstOrDefault(n => n.Name == actualProperty.GetSimpleMemberName())
dt => dt.GetDeclaredNavigations().FirstOrDefault(n => n.Name == clrProperty.GetSimpleMemberName())
== null)
&& (!isTargetSharedOrOwned
|| (!targetType.Equals(entityType.ClrType)
Expand All @@ -247,18 +255,18 @@ public virtual void Validate(IModel model, IDiagnosticsLogger<DbLoggerCategory.M
{
throw new InvalidOperationException(
CoreStrings.AmbiguousOwnedNavigation(
entityType.DisplayName() + "." + actualProperty.Name, targetType.ShortDisplayName()));
entityType.DisplayName() + "." + clrProperty.Name, targetType.ShortDisplayName()));
}

if (model.IsShared(targetType))
{
throw new InvalidOperationException(
CoreStrings.NonConfiguredNavigationToSharedType(actualProperty.Name, entityType.DisplayName()));
CoreStrings.NonConfiguredNavigationToSharedType(clrProperty.Name, entityType.DisplayName()));
}

throw new InvalidOperationException(
CoreStrings.NavigationNotAdded(
entityType.DisplayName(), actualProperty.Name, propertyType.ShortDisplayName()));
entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()));
}

// ReSharper restore CheckForReferenceEqualityInstead.3
Expand All @@ -269,21 +277,18 @@ public virtual void Validate(IModel model, IDiagnosticsLogger<DbLoggerCategory.M
{
throw new InvalidOperationException(
CoreStrings.InterfacePropertyNotAdded(
entityType.DisplayName(), actualProperty.Name, propertyType.ShortDisplayName()));
entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()));
}
else
{
throw new InvalidOperationException(
CoreStrings.PropertyNotAdded(
entityType.DisplayName(), actualProperty.Name, propertyType.ShortDisplayName()));
entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()));
}
}
}
}

private Type? FindCandidateNavigationPropertyType(PropertyInfo propertyInfo)
=> Dependencies.MemberClassifier.FindCandidateNavigationPropertyType(propertyInfo);

/// <summary>
/// Validates that no attempt is made to ignore inherited properties.
/// </summary>
Expand Down
Loading

0 comments on commit 4a67481

Please sign in to comment.