diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index ecd60227ecb..5a1406e09f4 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -55,7 +55,6 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui var annotations = model.GetAnnotations().ToList(); - IgnoreAnnotationTypes(annotations, RelationalAnnotationNames.DbFunction); IgnoreAnnotations( annotations, ChangeDetector.SkipDetectChangesAnnotation, @@ -63,6 +62,7 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui CoreAnnotationNames.OwnedTypes, RelationalAnnotationNames.CheckConstraints, RelationalAnnotationNames.Sequences, + RelationalAnnotationNames.DbFunctions, RelationalAnnotationNames.Tables, RelationalAnnotationNames.Views); diff --git a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs index e525186dcdd..66273516c6e 100644 --- a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs @@ -243,6 +243,7 @@ private IEnumerable GetAnnotationNamespaces(IEnumerable it CoreAnnotationNames.QueryFilter, RelationalAnnotationNames.CheckConstraints, RelationalAnnotationNames.Sequences, + RelationalAnnotationNames.DbFunctions, RelationalAnnotationNames.Tables, RelationalAnnotationNames.TableMappings, RelationalAnnotationNames.TableColumnMappings, @@ -251,18 +252,12 @@ private IEnumerable GetAnnotationNamespaces(IEnumerable it RelationalAnnotationNames.ViewColumnMappings }; - var ignoredAnnotationTypes = new List - { - RelationalAnnotationNames.DbFunction - }; - return items.SelectMany( i => i.GetAnnotations().Select( a => new { Annotatable = i, Annotation = a }) .Where( a => a.Annotation.Value != null - && !ignoredAnnotations.Contains(a.Annotation.Name) - && !ignoredAnnotationTypes.Any(p => a.Annotation.Name.StartsWith(p, StringComparison.Ordinal))) + && !ignoredAnnotations.Contains(a.Annotation.Name)) .SelectMany(a => GetProviderType(a.Annotatable, a.Annotation.Value.GetType()).GetNamespaces())); } diff --git a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs index c32f12aefb2..f32badb8890 100644 --- a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs @@ -219,7 +219,7 @@ public static IConventionSequence AddSequence( /// public static IMutableSequence RemoveSequence( [NotNull] this IMutableModel model, [NotNull] string name, [CanBeNull] string schema = null) - => Sequence.RemoveSequence(model, name, schema); + => Sequence.RemoveSequence(Check.NotNull(model, nameof(model)), name, schema); /// /// Removes the with the given name. @@ -233,28 +233,28 @@ public static IMutableSequence RemoveSequence( /// public static IConventionSequence RemoveSequence( [NotNull] this IConventionModel model, [NotNull] string name, [CanBeNull] string schema = null) - => (IConventionSequence)((IMutableModel)model).RemoveSequence(name, schema); + => Sequence.RemoveSequence((IMutableModel)Check.NotNull(model, nameof(model)), name, schema); /// /// Returns all s contained in the model. /// /// The model to get the sequences in. public static IEnumerable GetSequences([NotNull] this IModel model) - => Sequence.GetSequences(model); + => Sequence.GetSequences(Check.NotNull(model, nameof(model))); /// /// Returns all s contained in the model. /// /// The model to get the sequences in. public static IEnumerable GetSequences([NotNull] this IMutableModel model) - => (IEnumerable)((IModel)model).GetSequences(); + => Sequence.GetSequences(Check.NotNull(model, nameof(model))); /// /// Returns all s contained in the model. /// /// The model to get the sequences in. public static IEnumerable GetSequences([NotNull] this IConventionModel model) - => (IEnumerable)((IModel)model).GetSequences(); + => Sequence.GetSequences(Check.NotNull(model, nameof(model))); /// /// Finds a that is mapped to the method represented by the given . @@ -263,14 +263,9 @@ public static IEnumerable GetSequences([NotNull] this IConv /// The for the method that is mapped to the function. /// The or null if the method is not mapped. public static IDbFunction FindDbFunction([NotNull] this IModel model, [NotNull] MethodInfo method) - { - Check.NotNull(model, nameof(model)); - Check.NotNull(method, nameof(method)); - - return DbFunction.FindDbFunction( + => DbFunction.FindDbFunction( Check.NotNull(model, nameof(model)), Check.NotNull(method, nameof(method))); - } /// /// Finds a that is mapped to the method represented by the given . @@ -290,6 +285,35 @@ public static IMutableDbFunction FindDbFunction([NotNull] this IMutableModel mod public static IConventionDbFunction FindDbFunction([NotNull] this IConventionModel model, [NotNull] MethodInfo method) => (IConventionDbFunction)((IModel)model).FindDbFunction(method); + /// + /// Finds a that is mapped to the method represented by the given . + /// + /// The model to find the function in. + /// The model name of the function. + /// The or null if the method is not mapped. + public static IDbFunction FindDbFunction([NotNull] this IModel model, [NotNull] string name) + => DbFunction.FindDbFunction( + Check.NotNull(model, nameof(model)), + Check.NotNull(name, nameof(name))); + + /// + /// Finds a that is mapped to the method represented by the given . + /// + /// The model to find the function in. + /// The model name of the function. + /// The or null if the method is not mapped. + public static IMutableDbFunction FindDbFunction([NotNull] this IMutableModel model, [NotNull] string name) + => (IMutableDbFunction)((IModel)model).FindDbFunction(name); + + /// + /// Finds a that is mapped to the method represented by the given . + /// + /// The model to find the function in. + /// The model name of the function. + /// The or null if the method is not mapped. + public static IConventionDbFunction FindDbFunction([NotNull] this IConventionModel model, [NotNull] string name) + => (IConventionDbFunction)((IModel)model).FindDbFunction(name); + /// /// Either returns the existing mapped to the given method /// or creates a new function mapped to the method. @@ -298,8 +322,8 @@ public static IConventionDbFunction FindDbFunction([NotNull] this IConventionMod /// The for the method that is mapped to the function. /// The . public static DbFunction AddDbFunction([NotNull] this IMutableModel model, [NotNull] MethodInfo methodInfo) - => new DbFunction( - Check.NotNull(methodInfo, nameof(methodInfo)), model, ConfigurationSource.Explicit); + => DbFunction.AddDbFunction( + model, Check.NotNull(methodInfo, nameof(methodInfo)), ConfigurationSource.Explicit); /// /// Either returns the existing mapped to the given method @@ -311,9 +335,34 @@ public static DbFunction AddDbFunction([NotNull] this IMutableModel model, [NotN /// The . public static IConventionDbFunction AddDbFunction( [NotNull] this IConventionModel model, [NotNull] MethodInfo methodInfo, bool fromDataAnnotation = false) - => new DbFunction( - Check.NotNull(methodInfo, nameof(methodInfo)), (IMutableModel)model, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + => DbFunction.AddDbFunction( + (IMutableModel)model, Check.NotNull(methodInfo, nameof(methodInfo)), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// Either returns the existing mapped to the given method + /// or creates a new function mapped to the method. + /// + /// The model to add the function to. + /// The model name of the function. + /// The . + public static DbFunction AddDbFunction([NotNull] this IMutableModel model, [NotNull] string name) + => DbFunction.AddDbFunction( + model, Check.NotNull(name, nameof(name)), ConfigurationSource.Explicit); + + /// + /// Either returns the existing mapped to the given method + /// or creates a new function mapped to the method. + /// + /// The model to add the function to. + /// The model name of the function. + /// Indicates whether the configuration was specified using a data annotation. + /// The . + public static IConventionDbFunction AddDbFunction( + [NotNull] this IConventionModel model, [NotNull] string name, bool fromDataAnnotation = false) + => DbFunction.AddDbFunction( + (IMutableModel)model, Check.NotNull(name, nameof(name)), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// Removes the that is mapped to the method represented by the given @@ -323,14 +372,9 @@ public static IConventionDbFunction AddDbFunction( /// The for the method that is mapped to the function. /// The removed or null if the method is not mapped. public static IMutableDbFunction RemoveDbFunction([NotNull] this IMutableModel model, [NotNull] MethodInfo method) - { - Check.NotNull(model, nameof(model)); - Check.NotNull(method, nameof(method)); - - return DbFunction.RemoveDbFunction( + => DbFunction.RemoveDbFunction( Check.NotNull(model, nameof(model)), Check.NotNull(method, nameof(method))); - } /// /// Removes the that is mapped to the method represented by the given @@ -342,25 +386,47 @@ public static IMutableDbFunction RemoveDbFunction([NotNull] this IMutableModel m public static IConventionDbFunction RemoveDbFunction([NotNull] this IConventionModel model, [NotNull] MethodInfo method) => (IConventionDbFunction)((IMutableModel)model).RemoveDbFunction(method); + /// + /// Removes the that is mapped to the method represented by the given + /// . + /// + /// The model to find the function in. + /// The model name of the function. + /// The removed or null if the method is not mapped. + public static IMutableDbFunction RemoveDbFunction([NotNull] this IMutableModel model, [NotNull] string name) + => DbFunction.RemoveDbFunction( + Check.NotNull(model, nameof(model)), + Check.NotNull(name, nameof(name))); + + /// + /// Removes the that is mapped to the method represented by the given + /// . + /// + /// The model to find the function in. + /// The model name of the function. + /// The removed or null if the method is not mapped. + public static IConventionDbFunction RemoveDbFunction([NotNull] this IConventionModel model, [NotNull] string name) + => (IConventionDbFunction)((IMutableModel)model).RemoveDbFunction(name); + /// /// Returns all s contained in the model. /// /// The model to get the functions in. public static IEnumerable GetDbFunctions([NotNull] this IModel model) - => DbFunction.GetDbFunctions(model.AsModel()); + => DbFunction.GetDbFunctions(Check.NotNull(model, nameof(model))); /// /// Returns all s contained in the model. /// /// The model to get the functions in. public static IEnumerable GetDbFunctions([NotNull] this IMutableModel model) - => DbFunction.GetDbFunctions((Model)model); + => DbFunction.GetDbFunctions((Model)Check.NotNull(model, nameof(model))); /// /// Returns all s contained in the model. /// /// The model to get the functions in. public static IEnumerable GetDbFunctions([NotNull] this IConventionModel model) - => DbFunction.GetDbFunctions((Model)model); + => DbFunction.GetDbFunctions((Model)Check.NotNull(model, nameof(model))); } } diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs index f651dc690d9..c4e41b7a835 100644 --- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs +++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs @@ -87,8 +87,8 @@ public override ConventionSet CreateConventionSet() var dbFunctionAttributeConvention = new RelationalDbFunctionAttributeConvention(Dependencies, RelationalDependencies); conventionSet.ModelInitializedConventions.Add(dbFunctionAttributeConvention); - conventionSet.ModelAnnotationChangedConventions.Add(dbFunctionAttributeConvention); + conventionSet.ModelFinalizingConventions.Add(dbFunctionAttributeConvention); conventionSet.ModelFinalizingConventions.Add(tableNameFromDbSetConvention); conventionSet.ModelFinalizingConventions.Add(storeGenerationConvention); conventionSet.ModelFinalizingConventions.Add(new SharedTableConvention(Dependencies, RelationalDependencies)); diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalDbFunctionAttributeConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalDbFunctionAttributeConvention.cs index e702e012e8f..b6ab6f2cb5d 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalDbFunctionAttributeConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalDbFunctionAttributeConvention.cs @@ -1,13 +1,11 @@ // 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.Linq; using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; -using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions { @@ -15,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions /// A convention that configures model function mappings based on public static methods on the context marked with /// . /// - public class RelationalDbFunctionAttributeConvention : IModelInitializedConvention, IModelAnnotationChangedConvention + public class RelationalDbFunctionAttributeConvention : IModelInitializedConvention, IModelFinalizingConvention { /// /// Creates a new instance of . @@ -63,29 +61,12 @@ public virtual void ProcessModelInitialized( } } - /// - /// Called after an annotation is changed on an model. - /// - /// The builder for the model. - /// The annotation name. - /// The new annotation. - /// The old annotation. - /// Additional information associated with convention execution. - public virtual void ProcessModelAnnotationChanged( - IConventionModelBuilder modelBuilder, - string name, - IConventionAnnotation annotation, - IConventionAnnotation oldAnnotation, - IConventionContext context) + /// + public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext context) { - Check.NotNull(modelBuilder, nameof(modelBuilder)); - Check.NotNull(name, nameof(name)); - - if (name.StartsWith(RelationalAnnotationNames.DbFunction, StringComparison.Ordinal) - && annotation?.Value != null - && oldAnnotation == null) + foreach (var function in modelBuilder.Metadata.GetDbFunctions()) { - ProcessDbFunctionAdded(new DbFunctionBuilder((IMutableDbFunction)annotation.Value), context); + ProcessDbFunctionAdded(function.Builder, context); } } @@ -98,10 +79,15 @@ protected virtual void ProcessDbFunctionAdded( [NotNull] IConventionDbFunctionBuilder dbFunctionBuilder, [NotNull] IConventionContext context) { var methodInfo = dbFunctionBuilder.Metadata.MethodInfo; - var dbFunctionAttribute = methodInfo.GetCustomAttributes().SingleOrDefault(); - - dbFunctionBuilder.HasName(dbFunctionAttribute?.Name ?? methodInfo.Name); - dbFunctionBuilder.HasSchema(dbFunctionAttribute?.Schema); + var dbFunctionAttribute = methodInfo?.GetCustomAttributes().SingleOrDefault(); + if (dbFunctionAttribute != null) + { + dbFunctionBuilder.HasName(dbFunctionAttribute.Name, fromDataAnnotation: true); + if (dbFunctionAttribute.Schema != null) + { + dbFunctionBuilder.HasSchema(dbFunctionAttribute.Schema, fromDataAnnotation: true); + } + } } } } diff --git a/src/EFCore.Relational/Metadata/IDbFunction.cs b/src/EFCore.Relational/Metadata/IDbFunction.cs index 8b129911289..201f5611cb5 100644 --- a/src/EFCore.Relational/Metadata/IDbFunction.cs +++ b/src/EFCore.Relational/Metadata/IDbFunction.cs @@ -24,6 +24,11 @@ public interface IDbFunction /// string Schema { get; } + /// + /// The name of the function in the model. + /// + string ModelName { get; } + /// /// The in which this function is defined. /// diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs index 9a503831b20..25e49352b8b 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs @@ -26,7 +26,6 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal public class DbFunction : IMutableDbFunction, IConventionDbFunction { private readonly IMutableModel _model; - private readonly string _annotationName; private readonly List _parameters; private string _schema; private string _name; @@ -34,6 +33,7 @@ public class DbFunction : IMutableDbFunction, IConventionDbFunction private RelationalTypeMapping _typeMapping; private Func, SqlExpression> _translation; + private ConfigurationSource _configurationSource; private ConfigurationSource? _schemaConfigurationSource; private ConfigurationSource? _nameConfigurationSource; private ConfigurationSource? _storeTypeConfigurationSource; @@ -51,9 +51,6 @@ public DbFunction( [NotNull] IMutableModel model, ConfigurationSource configurationSource) { - Check.NotNull(methodInfo, nameof(methodInfo)); - Check.NotNull(model, nameof(model)); - if (methodInfo.IsGenericMethod) { throw new ArgumentException(RelationalStrings.DbFunctionGenericMethodNotSupported(methodInfo.DisplayName())); @@ -91,30 +88,164 @@ public DbFunction( MethodInfo = methodInfo; - _model = model; + var parameters = methodInfo.GetParameters(); - _parameters = methodInfo.GetParameters() + _parameters = parameters .Select((pi, i) => new DbFunctionParameter(this, pi.Name, pi.ParameterType)) .ToList(); - _annotationName = BuildAnnotationName(methodInfo); - if (configurationSource == ConfigurationSource.Explicit) + ModelName = GetFunctionName(methodInfo, parameters); + + _model = model; + _configurationSource = configurationSource; + Builder = new DbFunctionBuilder(this); + } + + /// + /// 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. + /// + public DbFunction( + [NotNull] string name, + [NotNull] IMutableModel model, + ConfigurationSource configurationSource) + { + ModelName = name; + _parameters = new List(); + _model = model; + _configurationSource = configurationSource; + Builder = new DbFunctionBuilder(this); + } + + private static string GetFunctionName(MethodInfo methodInfo, ParameterInfo[] parameters) + => methodInfo.DeclaringType.FullName + "." + methodInfo.Name + + "(" + string.Join(",", parameters.Select(p => p.ParameterType.FullName)) + ")"; + + /// + /// The builder that can be used to configure this function. + /// + public virtual DbFunctionBuilder Builder { get; private set; } + + /// + /// 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. + /// + public static IEnumerable GetDbFunctions([NotNull] IModel model) + => ((SortedDictionary)model[RelationalAnnotationNames.DbFunctions]) + ?.Values ?? Enumerable.Empty(); + + /// + /// 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. + /// + public static DbFunction FindDbFunction( + [NotNull] IModel model, + [NotNull] MethodInfo methodInfo) + { + var functions = (SortedDictionary)model[RelationalAnnotationNames.DbFunctions]; + if (functions == null + || !functions.TryGetValue(GetFunctionName(methodInfo, methodInfo.GetParameters()), out var dbFunction)) { - _model.AddAnnotation(_annotationName, this); + return null; } - else + + return dbFunction; + } + + /// + /// 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. + /// + public static DbFunction FindDbFunction( + [NotNull] IModel model, + [NotNull] string name) + { + var functions = (SortedDictionary)model[RelationalAnnotationNames.DbFunctions]; + return functions == null + || !functions.TryGetValue(name, out var dbFunction) + ? null + : dbFunction; + } + + /// + /// 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. + /// + public static DbFunction AddDbFunction( + [NotNull] IMutableModel model, [NotNull] MethodInfo methodInfo, ConfigurationSource configurationSource) + { + var function = new DbFunction(methodInfo, model, configurationSource); + + GetOrCreateFunctions(model).Add(function.ModelName, function); + return function; + } + + /// + /// 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. + /// + public static DbFunction AddDbFunction( + [NotNull] IMutableModel model, + [NotNull] string name, + ConfigurationSource configurationSource) + { + var function = new DbFunction(name, model, configurationSource); + + GetOrCreateFunctions(model).Add(name, function); + return function; + } + + private static SortedDictionary GetOrCreateFunctions(IMutableModel model) + { + var functions = (SortedDictionary)model[RelationalAnnotationNames.DbFunctions]; + if (functions == null) { - ((IConventionModel)_model).AddAnnotation( - _annotationName, - this, - configurationSource == ConfigurationSource.DataAnnotation); + functions = new SortedDictionary(); + model[RelationalAnnotationNames.DbFunctions] = functions; } + + return functions; } /// - /// The builder that can be used to configure this function. + /// 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. /// - public virtual IConventionDbFunctionBuilder Builder => new DbFunctionBuilder(this); + public static DbFunction RemoveDbFunction( + [NotNull] IMutableModel model, + [NotNull] MethodInfo methodInfo) + { + var functions = (SortedDictionary)model[RelationalAnnotationNames.DbFunctions]; + if (functions == null) + { + return null; + } + + var name = GetFunctionName(methodInfo, methodInfo.GetParameters()); + if (!functions.TryGetValue(name, out var function)) + { + return null; + } + + functions.Remove(name); + function.Builder = null; + + return function; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -122,21 +253,38 @@ public DbFunction( /// 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. /// - public static IEnumerable GetDbFunctions([NotNull] Model model) + public static DbFunction RemoveDbFunction( + [NotNull] IMutableModel model, + [NotNull] string name) { - Check.NotNull(model, nameof(model)); + var functions = (SortedDictionary)model[RelationalAnnotationNames.DbFunctions]; + if (functions == null + || !functions.TryGetValue(name, out var function)) + { + return null; + } - return model.GetAnnotations() - .Where(a => a.Name.StartsWith(RelationalAnnotationNames.DbFunction, StringComparison.Ordinal)) - .Select(a => a.Value) - .Cast(); + functions.Remove(name); + function.Builder = null; + + return function; } - private static string BuildAnnotationName(MethodBase methodBase) - => - // ReSharper disable once AssignNullToNotNullAttribute - // ReSharper disable once PossibleNullReferenceException - $"{RelationalAnnotationNames.DbFunction}{methodBase.DeclaringType.FullName}{methodBase.Name}({string.Join(",", methodBase.GetParameters().Select(p => p.ParameterType.FullName))})"; + /// + /// 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. + /// + public virtual string ModelName { get; private set; } + + /// + /// 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. + /// + public virtual MethodInfo MethodInfo { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -145,7 +293,7 @@ private static string BuildAnnotationName(MethodBase methodBase) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ConfigurationSource GetConfigurationSource() - => ((IConventionModel)_model).FindAnnotation(_annotationName).GetConfigurationSource(); + => _configurationSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -154,7 +302,7 @@ public virtual ConfigurationSource GetConfigurationSource() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual void UpdateConfigurationSource(ConfigurationSource configurationSource) - => ((Model)_model).FindAnnotation(_annotationName).UpdateConfigurationSource(configurationSource); + => _configurationSource = configurationSource.Max(_configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -178,12 +326,11 @@ public virtual void SetSchema([CanBeNull] string schema, ConfigurationSource con { _schema = schema; - UpdateSchemaConfigurationSource(configurationSource); + _schemaConfigurationSource = schema == null + ? (ConfigurationSource?)null + : configurationSource.Max(_schemaConfigurationSource); } - private void UpdateSchemaConfigurationSource(ConfigurationSource configurationSource) - => _schemaConfigurationSource = configurationSource.Max(_schemaConfigurationSource); - /// /// 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 @@ -200,7 +347,7 @@ private void UpdateSchemaConfigurationSource(ConfigurationSource configurationSo /// public virtual string Name { - get => _name ?? MethodInfo.Name; + get => _name ?? MethodInfo?.Name ?? ModelName; set => SetName(value, ConfigurationSource.Explicit); } @@ -210,18 +357,15 @@ public virtual string Name /// 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. /// - public virtual void SetName([NotNull] string name, ConfigurationSource configurationSource) + public virtual void SetName([CanBeNull] string name, ConfigurationSource configurationSource) { - Check.NotNull(name, nameof(name)); - _name = name; - UpdateNameConfigurationSource(configurationSource); + _nameConfigurationSource = name == null + ? (ConfigurationSource?)null + : configurationSource.Max(_nameConfigurationSource); } - private void UpdateNameConfigurationSource(ConfigurationSource configurationSource) - => _nameConfigurationSource = configurationSource.Max(_nameConfigurationSource); - /// /// 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 @@ -230,14 +374,6 @@ private void UpdateNameConfigurationSource(ConfigurationSource configurationSour /// public virtual ConfigurationSource? GetNameConfigurationSource() => _nameConfigurationSource; - /// - /// 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. - /// - public virtual MethodInfo MethodInfo { get; } - /// /// 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 @@ -256,18 +392,15 @@ public virtual string StoreType /// 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. /// - public virtual void SetStoreType([NotNull] string storeType, ConfigurationSource configurationSource) + public virtual void SetStoreType([CanBeNull] string storeType, ConfigurationSource configurationSource) { - Check.NotNull(storeType, nameof(storeType)); - _storeType = storeType; - UpdateStoreTypeConfigurationSource(configurationSource); + _storeTypeConfigurationSource = storeType == null + ? (ConfigurationSource?)null + : configurationSource.Max(_storeTypeConfigurationSource); } - private void UpdateStoreTypeConfigurationSource(ConfigurationSource configurationSource) - => _storeTypeConfigurationSource = configurationSource.Max(_storeTypeConfigurationSource); - /// /// 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 @@ -298,12 +431,11 @@ public virtual void SetTypeMapping([CanBeNull] RelationalTypeMapping typeMapping { _typeMapping = typeMapping; - UpdateTypeMappingConfigurationSource(configurationSource); + _typeMappingConfigurationSource = typeMapping == null + ? (ConfigurationSource?)null + : configurationSource.Max(_typeMappingConfigurationSource); } - private void UpdateTypeMappingConfigurationSource(ConfigurationSource configurationSource) - => _typeMappingConfigurationSource = configurationSource.Max(_typeMappingConfigurationSource); - /// /// 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 @@ -324,14 +456,6 @@ public virtual Func, SqlExpression> Translati set => SetTranslation(value, ConfigurationSource.Explicit); } - /// - /// 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. - /// - public virtual bool IsIQueryable { get; } - /// /// 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 @@ -344,12 +468,11 @@ public virtual void SetTranslation( { _translation = translation; - UpdateTranslationConfigurationSource(configurationSource); + _translationConfigurationSource = translation == null + ? (ConfigurationSource?)null + : configurationSource.Max(_translationConfigurationSource); } - private void UpdateTranslationConfigurationSource(ConfigurationSource configurationSource) - => _translationConfigurationSource = configurationSource.Max(_translationConfigurationSource); - /// /// 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 @@ -364,30 +487,7 @@ private void UpdateTranslationConfigurationSource(ConfigurationSource configurat /// 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. /// - public static DbFunction FindDbFunction( - [NotNull] IModel model, - [NotNull] MethodInfo methodInfo) - { - var dbFunction = model[BuildAnnotationName(methodInfo)] as DbFunction; - - if (dbFunction == null - && methodInfo.GetParameters().Any(p => p.ParameterType.IsGenericType && p.ParameterType.GetGenericTypeDefinition() == typeof(Expression<>))) - { - var parameters = methodInfo.GetParameters().Select(p => p.ParameterType.IsGenericType - && p.ParameterType.GetGenericTypeDefinition() == typeof(Expression<>) - && p.ParameterType.GetGenericArguments()[0].GetGenericTypeDefinition() == typeof(Func<>) - ? p.ParameterType.GetGenericArguments()[0].GetGenericArguments()[0] - : p.ParameterType).ToArray(); - - var nonExpressionMethod = methodInfo.DeclaringType.GetMethod(methodInfo.Name, parameters); - - dbFunction = nonExpressionMethod != null - ? model[BuildAnnotationName(nonExpressionMethod)] as DbFunction - : null; - } - - return dbFunction; - } + public virtual bool IsIQueryable { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -395,10 +495,7 @@ public static DbFunction FindDbFunction( /// 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. /// - public static DbFunction RemoveDbFunction( - [NotNull] IMutableModel model, - [NotNull] MethodInfo methodInfo) - => model.RemoveAnnotation(BuildAnnotationName(methodInfo))?.Value as DbFunction; + IConventionDbFunctionBuilder IConventionDbFunction.Builder => Builder; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs index b308570bd54..b84102dff3c 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs @@ -41,6 +41,7 @@ public DbFunctionParameter([NotNull] IMutableDbFunction function, [NotNull] stri _name = name; _function = function; _clrType = clrType; + Builder = new DbFunctionParameterBuilder(this); } /// @@ -49,7 +50,7 @@ public DbFunctionParameter([NotNull] IMutableDbFunction function, [NotNull] stri /// 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. /// - public virtual IConventionDbFunctionParameterBuilder Builder => new DbFunctionParameterBuilder(this); + public virtual IConventionDbFunctionParameterBuilder Builder { get; private set; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/Sequence.cs b/src/EFCore.Relational/Metadata/Internal/Sequence.cs index f88dde18f3b..1511924b375 100644 --- a/src/EFCore.Relational/Metadata/Internal/Sequence.cs +++ b/src/EFCore.Relational/Metadata/Internal/Sequence.cs @@ -23,14 +23,14 @@ public class Sequence : IMutableSequence, IConventionSequence { private readonly IModel _model; - private string _name { get; set; } - private string _schema { get; set; } - private long? _startValue { get; set; } - private int? _incrementBy { get; set; } - private long? _minValue { get; set; } - private long? _maxValue { get; set; } - private Type _clrType { get; set; } - private bool? _isCyclic { get; set; } + private readonly string _name; + private readonly string _schema; + private long? _startValue; + private int? _incrementBy; + private long? _minValue; + private long? _maxValue; + private Type _clrType; + private bool? _isCyclic; private ConfigurationSource _configurationSource; private ConfigurationSource? _startValueConfigurationSource; @@ -95,9 +95,9 @@ public class Sequence : IMutableSequence, IConventionSequence /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public Sequence( - [NotNull] IMutableModel model, [NotNull] string name, [CanBeNull] string schema, + [NotNull] IModel model, ConfigurationSource configurationSource) { Check.NotEmpty(name, nameof(name)); @@ -107,6 +107,7 @@ public Sequence( _name = name; _schema = schema; _configurationSource = configurationSource; + Builder = new SequenceBuilder(this); } /// @@ -133,6 +134,7 @@ public Sequence([NotNull] IModel model, [NotNull] string annotationName) _maxValue = data.MaxValue; _clrType = data.ClrType; _isCyclic = data.IsCyclic; + Builder = new SequenceBuilder(this); } /// @@ -172,7 +174,7 @@ public static Sequence FindSequence([NotNull] IModel model, [NotNull] string nam public static Sequence AddSequence( [NotNull] IMutableModel model, [NotNull] string name, [CanBeNull] string schema, ConfigurationSource configurationSource) { - var sequence = new Sequence(model, name, schema, configurationSource); + var sequence = new Sequence(name, schema, model, configurationSource); var sequences = (SortedDictionary<(string, string), Sequence>)model[RelationalAnnotationNames.Sequences]; if (sequences == null) { @@ -190,7 +192,7 @@ public static Sequence AddSequence( /// 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. /// - public static IMutableSequence RemoveSequence([NotNull] IMutableModel model, [NotNull] string name, [CanBeNull] string schema) + public static Sequence RemoveSequence([NotNull] IMutableModel model, [NotNull] string name, [CanBeNull] string schema) { var sequences = (SortedDictionary<(string, string), Sequence>)model[RelationalAnnotationNames.Sequences]; if (sequences == null @@ -200,6 +202,7 @@ public static IMutableSequence RemoveSequence([NotNull] IMutableModel model, [No } sequences.Remove((name, schema)); + sequence.Builder = null; return sequence; } @@ -210,7 +213,7 @@ public static IMutableSequence RemoveSequence([NotNull] IMutableModel model, [No /// 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. /// - public virtual IMutableModel Model => (IMutableModel)_model; + public virtual SequenceBuilder Builder { get; private set; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -218,7 +221,7 @@ public static IMutableSequence RemoveSequence([NotNull] IMutableModel model, [No /// 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. /// - IConventionSequenceBuilder IConventionSequence.Builder => new SequenceBuilder(this); + public virtual IModel Model => _model; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -491,7 +494,7 @@ public virtual void UpdateConfigurationSource(ConfigurationSource configurationS /// 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. /// - IConventionModel IConventionSequence.Model => (IConventionModel)Model; + IConventionSequenceBuilder IConventionSequence.Builder => Builder; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -499,7 +502,15 @@ public virtual void UpdateConfigurationSource(ConfigurationSource configurationS /// 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. /// - IModel ISequence.Model => _model; + IMutableModel IMutableSequence.Model => (IMutableModel)Model; + + /// + /// 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. + /// + IConventionModel IConventionSequence.Model => (IConventionModel)Model; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs index 0dd7869292a..432e16386ac 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs @@ -105,8 +105,14 @@ public static class RelationalAnnotationNames /// /// The name for DbFunction annotation. /// + [Obsolete("Use DbFunctions")] public const string DbFunction = Prefix + "DbFunction"; + /// + /// The name for functions annotation. + /// + public const string DbFunctions = Prefix + "DbFunctions"; + /// /// The name for the annotation containing the maximum length for database identifiers. /// diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index af7ac45e953..f94aedd94ec 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -71,7 +71,7 @@ public void Test_new_annotations_handled_for_entity_types() RelationalAnnotationNames.CheckConstraints, RelationalAnnotationNames.DefaultSchema, RelationalAnnotationNames.Filter, - RelationalAnnotationNames.DbFunction, + RelationalAnnotationNames.DbFunctions, RelationalAnnotationNames.MaxIdentifierLength, RelationalAnnotationNames.IsFixedLength }; @@ -157,7 +157,7 @@ public void Test_new_annotations_handled_for_properties() RelationalAnnotationNames.Sequences, RelationalAnnotationNames.CheckConstraints, RelationalAnnotationNames.Filter, - RelationalAnnotationNames.DbFunction, + RelationalAnnotationNames.DbFunctions, RelationalAnnotationNames.MaxIdentifierLength }; @@ -449,7 +449,7 @@ protected override void Down(MigrationBuilder migrationBuilder) typeof(MyContext), "MyMigration", "20150511161616_MyMigration", - new Model { ["Some:EnumValue"] = RegexOptions.Multiline, ["Relational:DbFunction:MyFunc"] = new object() }); + new Model { ["Some:EnumValue"] = RegexOptions.Multiline, [RelationalAnnotationNames.DbFunctions] = new object() }); ; Assert.Equal( @"// using System.Text.RegularExpressions; diff --git a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs index cce30eb9a0b..4ce53f385c1 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs @@ -196,18 +196,13 @@ public class MultProductOrders public int CustomerId { get; set; } public DateTime OrderDate { get; set; } - } + } public IQueryable GetCustomerOrderCountByYear(int customerId) { return CreateQuery(() => GetCustomerOrderCountByYear(customerId)); } - public IQueryable GetCustomerOrderCountByYear(Expression> customerId2) - { - return CreateQuery(() => GetCustomerOrderCountByYear(customerId2)); - } - public class TopSellingProduct { public Product Product { get; set; } @@ -336,7 +331,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) //Table modelBuilder.HasDbFunction(typeof(UDFSqlContext).GetMethod(nameof(GetCustomerOrderCountByYear), new[] { typeof(int) })); - modelBuilder.HasDbFunction(typeof(UDFSqlContext).GetMethod(nameof(GetCustomerOrderCountByYear), new[] { typeof(Expression>) })); modelBuilder.HasDbFunction(typeof(UDFSqlContext).GetMethod(nameof(GetTopTwoSellingProducts))); modelBuilder.HasDbFunction(typeof(UDFSqlContext).GetMethod(nameof(GetTopSellingProductsForCustomer))); modelBuilder.HasDbFunction(typeof(UDFSqlContext).GetMethod(nameof(GetCreditCards))); @@ -1495,23 +1489,6 @@ orderby c.Count descending } } - [ConditionalFact] - public virtual void QF_Stand_Alone_Nested() - { - using (var context = CreateContext()) - { - var orders = (from r in context.GetCustomerOrderCountByYear(() => context.AddValues(-2, 3)) - orderby r.Count descending - select r).ToList(); - - Assert.Equal(2, orders.Count); - Assert.Equal(2, orders[0].Count); - Assert.Equal(2000, orders[0].Year); - Assert.Equal(1, orders[1].Count); - Assert.Equal(2001, orders[1].Year); - } - } - [ConditionalFact] public virtual void QF_CrossApply_Correlated_Select_QF_Type() { diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 6e0f7dea40a..4e8f9001968 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -1013,7 +1013,7 @@ public virtual void Detects_ToTable_on_derived_entity_types() } [ConditionalFact] - public void Detects_function_with_invalid_return_type_throws() + public void Detects_function_with_invalid_return_type() { var modelBuilder = CreateConventionalModelBuilder(); diff --git a/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs b/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs index 98ed0c600a4..d1f0a33bd94 100644 --- a/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs +++ b/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs @@ -279,6 +279,8 @@ var dup2methodInfo var dbFunc1 = modelBuilder.HasDbFunction(dup1methodInfo).HasName("Dup1").Metadata; var dbFunc2 = modelBuilder.HasDbFunction(dup2methodInfo).HasName("Dup2").Metadata; + modelBuilder.FinalizeModel(); + Assert.Equal("Dup1", dbFunc1.Name); Assert.Equal("Dup2", dbFunc2.Name); } @@ -289,6 +291,8 @@ public virtual void Finds_dbFunctions_on_dbContext() var context = new MyDerivedContext(); var modelBuilder = GetModelBuilder(context); + modelBuilder.FinalizeModel(); + foreach (var function in MyBaseContext.FunctionNames) { Assert.NotNull( @@ -317,6 +321,8 @@ var methodInfo var dbFunc = modelBuilder.HasDbFunction(methodInfo).Metadata; + modelBuilder.FinalizeModel(); + Assert.Equal("InstancePublicBase", dbFunc.Name); Assert.Equal(typeof(int), dbFunc.MethodInfo.ReturnType); } @@ -355,6 +361,8 @@ public void Adding_method_fluent_only_convention_defaults() var dbFuncBuilder = modelBuilder.HasDbFunction(MethodAmi); var dbFunc = dbFuncBuilder.Metadata; + modelBuilder.FinalizeModel(); + Assert.Equal("MethodA", dbFunc.Name); Assert.Null(dbFunc.Schema); Assert.Equal(typeof(int), dbFunc.MethodInfo.ReturnType); @@ -368,6 +376,8 @@ public void Adding_method_fluent_only_convention_defaults_fluent_method_info() var dbFuncBuilder = modelBuilder.HasDbFunction(() => TestMethods.MethodA(null, default)); var dbFunc = dbFuncBuilder.Metadata; + modelBuilder.FinalizeModel(); + Assert.Equal("MethodA", dbFunc.Name); Assert.Null(dbFunc.Schema); Assert.Equal(typeof(int), dbFunc.MethodInfo.ReturnType); @@ -408,6 +418,8 @@ public void Adding_method_fluent_only_with_name_schema() var dbFunc = dbFuncBuilder.Metadata; + modelBuilder.FinalizeModel(); + Assert.Equal("foo", dbFunc.Name); Assert.Equal("bar", dbFunc.Schema); Assert.Equal(typeof(int), dbFunc.MethodInfo.ReturnType); @@ -422,6 +434,8 @@ public void Adding_method_fluent_only_with_builder() var dbFunc = modelBuilder.HasDbFunction(MethodAmi).Metadata; + modelBuilder.FinalizeModel(); + Assert.Equal("foo", dbFunc.Name); Assert.Equal("bar", dbFunc.Schema); Assert.Equal(typeof(int), dbFunc.MethodInfo.ReturnType); @@ -435,6 +449,8 @@ public void Adding_method_with_attribute_only() var dbFuncBuilder = modelBuilder.HasDbFunction(MethodBmi); var dbFunc = dbFuncBuilder.Metadata; + modelBuilder.FinalizeModel(); + Assert.Equal("MethodFoo", dbFunc.Name); Assert.Equal("bar", dbFunc.Schema); Assert.Equal(typeof(int), dbFunc.MethodInfo.ReturnType); @@ -451,6 +467,8 @@ public void Adding_method_with_attribute_and_fluent_api_configuration_source() var dbFunc = dbFuncBuilder.Metadata; + modelBuilder.FinalizeModel(); + Assert.Equal("foo", dbFunc.Name); Assert.Equal("bar", dbFunc.Schema); Assert.Equal(typeof(int), dbFunc.MethodInfo.ReturnType); @@ -465,6 +483,8 @@ public void Adding_method_with_attribute_and_fluent_configuration_source() var dbFunc = modelBuilder.HasDbFunction(MethodBmi).Metadata; + modelBuilder.FinalizeModel(); + Assert.Equal("foo", dbFunc.Name); Assert.Equal("bar", dbFunc.Schema); Assert.Equal(typeof(int), dbFunc.MethodInfo.ReturnType); @@ -479,6 +499,8 @@ public void Adding_method_with_relational_schema() var dbFuncBuilder = modelBuilder.HasDbFunction(MethodAmi); + modelBuilder.FinalizeModel(); + Assert.Equal("dbo", dbFuncBuilder.Metadata.Schema); } @@ -489,6 +511,8 @@ public void Adding_method_with_store_type() var dbFuncBuilder = modelBuilder.HasDbFunction(MethodAmi).HasStoreType("int(8)"); + modelBuilder.FinalizeModel(); + Assert.Equal("int(8)", dbFuncBuilder.Metadata.StoreType); } @@ -501,6 +525,8 @@ public void Adding_method_with_relational_schema_fluent_overrides() var dbFuncBuilder = modelBuilder.HasDbFunction(MethodAmi).HasSchema("bar"); + modelBuilder.FinalizeModel(); + Assert.Equal("bar", dbFuncBuilder.Metadata.Schema); } @@ -513,6 +539,8 @@ public void Adding_method_with_relational_schema_attribute_overrides() var dbFuncBuilder = modelBuilder.HasDbFunction(MethodBmi); + modelBuilder.FinalizeModel(); + Assert.Equal("bar", dbFuncBuilder.Metadata.Schema); } @@ -529,6 +557,8 @@ public void Changing_default_schema_is_detected_by_dbfunction() modelBuilder.HasDefaultSchema("xyz"); + modelBuilder.FinalizeModel(); + Assert.Equal("xyz", dbFuncBuilder.Metadata.Schema); } @@ -561,6 +591,8 @@ public void DbParameters_load_no_parameters() var dbFuncBuilder = modelBuilder.HasDbFunction(MethodImi); var dbFunc = dbFuncBuilder.Metadata; + modelBuilder.FinalizeModel(); + Assert.Equal(0, dbFunc.Parameters.Count); } @@ -584,6 +616,8 @@ public void DbParameters_load_with_parameters() var dbFuncBuilder = modelBuilder.HasDbFunction(MethodBmi); var dbFunc = dbFuncBuilder.Metadata; + modelBuilder.FinalizeModel(); + Assert.Equal(2, dbFunc.Parameters.Count); Assert.Equal("c", dbFunc.Parameters[0].Name); @@ -603,6 +637,8 @@ public void DbParameters_dbfunctionType() dbFuncBuilder.HasParameter("c"); + modelBuilder.FinalizeModel(); + Assert.Equal(2, dbFunc.Parameters.Count); Assert.Equal("c", dbFunc.Parameters[0].Name); @@ -622,6 +658,8 @@ public void DbParameters_name() dbFuncBuilder.HasParameter("c"); + modelBuilder.FinalizeModel(); + Assert.Equal(2, dbFunc.Parameters.Count); Assert.Equal("c", dbFunc.Parameters[0].Name); @@ -641,6 +679,8 @@ public void DbParameters_StoreType() dbFuncBuilder.HasParameter("c").HasStoreType("varchar(max)"); + modelBuilder.FinalizeModel(); + Assert.Equal(2, dbFunc.Parameters.Count); Assert.Equal("c", dbFunc.Parameters[0].Name); @@ -664,42 +704,13 @@ public void DbFunction_Annotation_FullName() funcA.HasName("MinA"); + modelBuilder.FinalizeModel(); + Assert.Equal("MinA", funcA.Metadata.Name); Assert.Equal("Min", funcB.Metadata.Name); Assert.NotEqual(funcA.Metadata.Name, funcB.Metadata.Name); } - [ConditionalFact] - public void Find_Queryable_Single_Expression_Overload() - { - var modelBuilder = GetModelBuilder(); - - var funcA = modelBuilder.HasDbFunction(typeof(MyDerivedContext).GetMethod(nameof(MyDerivedContext.QueryableSingleParam), new Type[] { typeof(int) })); - var funcB = modelBuilder.HasDbFunction(typeof(MyDerivedContext).GetMethod(nameof(MyDerivedContext.QueryableSingleParam), new Type[] { typeof(Expression>) })); - - Assert.Equal("QueryableSingleParam", funcA.Metadata.Name); - Assert.Equal("QueryableSingleParam", funcB.Metadata.Name); - Assert.Equal(funcA.Metadata, funcB.Metadata); - } - - [ConditionalFact] - public void Find_Queryable_Multiple_Expression_Overload() - { - var modelBuilder = GetModelBuilder(); - - var funcA = modelBuilder.HasDbFunction(typeof(MyDerivedContext).GetMethod(nameof(MyDerivedContext.QueryableMultiParam), new Type[] { typeof(int), typeof(double) })); - var funcB = modelBuilder.HasDbFunction(typeof(MyDerivedContext).GetMethod(nameof(MyDerivedContext.QueryableMultiParam), new Type[] { typeof(Expression>), typeof(double) })); - var funcC = modelBuilder.HasDbFunction(typeof(MyDerivedContext).GetMethod(nameof(MyDerivedContext.QueryableMultiParam), new Type[] { typeof(Expression>), typeof(Expression>) })); - - Assert.Equal("QueryableMultiParam", funcA.Metadata.Name); - Assert.Equal("QueryableMultiParam", funcB.Metadata.Name); - Assert.Equal("QueryableMultiParam", funcC.Metadata.Name); - - Assert.Equal(funcA.Metadata, funcB.Metadata); - Assert.Equal(funcA.Metadata, funcC.Metadata); - Assert.Equal(funcB.Metadata, funcC.Metadata); - } - private ModelBuilder GetModelBuilder(DbContext dbContext = null) { var conventionSet = new ConventionSet(); @@ -709,7 +720,7 @@ private ModelBuilder GetModelBuilder(DbContext dbContext = null) var relationalDependencies = CreateRelationalDependencies(); var dbFunctionAttributeConvention = new RelationalDbFunctionAttributeConvention(dependencies, relationalDependencies); conventionSet.ModelInitializedConventions.Add(dbFunctionAttributeConvention); - conventionSet.ModelAnnotationChangedConventions.Add(dbFunctionAttributeConvention); + conventionSet.ModelFinalizingConventions.Add(dbFunctionAttributeConvention); conventionSet.ModelFinalizingConventions.Add(new DbFunctionTypeMappingConvention(dependencies, relationalDependencies)); return new ModelBuilder(conventionSet); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs index 2c0a3698ba8..8534ad255f0 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs @@ -411,6 +411,11 @@ public void Can_get_and_set_dbfunction() Assert.NotNull(dbFunc); Assert.NotNull(dbFunc.Name); Assert.Null(dbFunc.Schema); + Assert.NotNull(((IConventionDbFunction)dbFunc).Builder); + + Assert.Same(dbFunc, model.RemoveDbFunction(testMethod)); + + Assert.Null(((IConventionDbFunction)dbFunc).Builder); } [ConditionalFact] @@ -432,8 +437,10 @@ public void Can_get_and_set_sequence() Assert.Null(sequence.MinValue); Assert.Null(sequence.MaxValue); Assert.Same(typeof(long), sequence.ClrType); + Assert.False(sequence.IsCyclic); + Assert.NotNull(((IConventionSequence)sequence).Builder); - var sequence2 = model.FindSequence("Foo"); + Assert.Same(sequence, model.FindSequence("Foo")); sequence.StartValue = 1729; sequence.IncrementBy = 11; @@ -449,13 +456,9 @@ public void Can_get_and_set_sequence() Assert.Equal(2010, sequence.MaxValue); Assert.Same(typeof(int), sequence.ClrType); - Assert.Equal(sequence2.Name, sequence.Name); - Assert.Equal(sequence2.Schema, sequence.Schema); - Assert.Equal(sequence2.IncrementBy, sequence.IncrementBy); - Assert.Equal(sequence2.StartValue, sequence.StartValue); - Assert.Equal(sequence2.MinValue, sequence.MinValue); - Assert.Equal(sequence2.MaxValue, sequence.MaxValue); - Assert.Same(sequence2.ClrType, sequence.ClrType); + Assert.Same(sequence, model.RemoveSequence("Foo")); + + Assert.Null(((IConventionSequence)sequence).Builder); } [ConditionalFact] @@ -479,7 +482,7 @@ public void Can_get_and_set_sequence_with_schema_name() Assert.Null(sequence.MaxValue); Assert.Same(typeof(long), sequence.ClrType); - var sequence2 = model.FindSequence("Foo", "Smoo"); + Assert.Same(sequence, model.FindSequence("Foo", "Smoo")); sequence.StartValue = 1729; sequence.IncrementBy = 11; @@ -494,14 +497,6 @@ public void Can_get_and_set_sequence_with_schema_name() Assert.Equal(2001, sequence.MinValue); Assert.Equal(2010, sequence.MaxValue); Assert.Same(typeof(int), sequence.ClrType); - - Assert.Equal(sequence2.Name, sequence.Name); - Assert.Equal(sequence2.Schema, sequence.Schema); - Assert.Equal(sequence2.IncrementBy, sequence.IncrementBy); - Assert.Equal(sequence2.StartValue, sequence.StartValue); - Assert.Equal(sequence2.MinValue, sequence.MinValue); - Assert.Equal(sequence2.MaxValue, sequence.MaxValue); - Assert.Same(sequence2.ClrType, sequence.ClrType); } [ConditionalFact] @@ -515,7 +510,7 @@ public void Sequence_is_in_model_schema_if_schema_not_specified() var sequence = model.AddSequence("Foo"); - Assert.Equal("Foo", model.FindSequence("Foo").Name); + Assert.Same(sequence, model.FindSequence("Foo")); Assert.Equal("Foo", sequence.Name); Assert.Equal("Smoo", sequence.Schema); @@ -536,7 +531,7 @@ public void Returns_same_sequence_if_schema_not_specified_explicitly() var sequence = model.AddSequence("Foo"); - Assert.Equal("Foo", model.FindSequence("Foo").Name); + Assert.Same(sequence, model.FindSequence("Foo")); Assert.Equal("Foo", sequence.Name); Assert.Null(sequence.Schema); @@ -548,7 +543,7 @@ public void Returns_same_sequence_if_schema_not_specified_explicitly() model.SetDefaultSchema("Smoo"); - var sequence2 = model.FindSequence("Foo"); + Assert.Same(sequence, model.FindSequence("Foo")); sequence.StartValue = 1729; sequence.IncrementBy = 11; @@ -563,14 +558,6 @@ public void Returns_same_sequence_if_schema_not_specified_explicitly() Assert.Equal(2001, sequence.MinValue); Assert.Equal(2010, sequence.MaxValue); Assert.Same(typeof(int), sequence.ClrType); - - Assert.Equal(sequence2.Name, sequence.Name); - Assert.Equal(sequence2.Schema, sequence.Schema); - Assert.Equal(sequence2.IncrementBy, sequence.IncrementBy); - Assert.Equal(sequence2.StartValue, sequence.StartValue); - Assert.Equal(sequence2.MinValue, sequence.MinValue); - Assert.Equal(sequence2.MaxValue, sequence.MaxValue); - Assert.Same(sequence2.ClrType, sequence.ClrType); } [ConditionalFact] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs index 0677830c9fb..6bdbeca535f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs @@ -475,15 +475,6 @@ FROM [dbo].[GetCustomerOrderCountByYear](@__customerId_0) AS [o] ORDER BY [o].[Count] DESC"); } - public override void QF_Stand_Alone_Nested() - { - base.QF_Stand_Alone_Nested(); - - AssertSql(@"SELECT [o].[Count], [o].[CustomerId], [o].[Year] -FROM [dbo].[GetCustomerOrderCountByYear]([dbo].[AddValues](-2, 3)) AS [o] -ORDER BY [o].[Count] DESC"); - } - public override void QF_CrossApply_Correlated_Select_Anonymous() { base.QF_CrossApply_Correlated_Select_Anonymous();