diff --git a/src/EFCore.Relational/EFCore.Relational.baseline.json b/src/EFCore.Relational/EFCore.Relational.baseline.json index 6e01c2538f5..82dbb2fb97e 100644 --- a/src/EFCore.Relational/EFCore.Relational.baseline.json +++ b/src/EFCore.Relational/EFCore.Relational.baseline.json @@ -3174,7 +3174,7 @@ ] }, { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IColumnMapping : Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable", + "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IColumnMapping : Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable, System.IComparable", "Methods": [ { "Member": "string ToDebugString(Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions options = Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions.ShortDefault, int indent = 0);" @@ -3190,7 +3190,7 @@ ] }, { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase : Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable", + "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase : Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable, System.IComparable", "Properties": [ { "Member": "Microsoft.EntityFrameworkCore.Metadata.IColumnBase Column { get; }" @@ -4251,7 +4251,7 @@ ] }, { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IFunctionColumnMapping : Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable", + "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IFunctionColumnMapping : Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable, System.IComparable", "Methods": [ { "Member": "string ToDebugString(Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions options = Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions.ShortDefault, int indent = 0);" @@ -4267,7 +4267,7 @@ ] }, { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IFunctionMapping : Microsoft.EntityFrameworkCore.Metadata.ITableMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable", + "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IFunctionMapping : Microsoft.EntityFrameworkCore.Metadata.ITableMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable, System.IComparable", "Properties": [ { "Member": "System.Collections.Generic.IEnumerable ColumnMappings { get; }" @@ -6305,7 +6305,7 @@ ] }, { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.ISqlQueryColumnMapping : Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable", + "Type": "interface Microsoft.EntityFrameworkCore.Metadata.ISqlQueryColumnMapping : Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable, System.IComparable", "Methods": [ { "Member": "string ToDebugString(Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions options = Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions.ShortDefault, int indent = 0);" @@ -6321,7 +6321,7 @@ ] }, { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.ISqlQueryMapping : Microsoft.EntityFrameworkCore.Metadata.ITableMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable", + "Type": "interface Microsoft.EntityFrameworkCore.Metadata.ISqlQueryMapping : Microsoft.EntityFrameworkCore.Metadata.ITableMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable, System.IComparable", "Properties": [ { "Member": "System.Collections.Generic.IEnumerable ColumnMappings { get; }" @@ -6375,7 +6375,7 @@ ] }, { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IStoredProcedureMapping : Microsoft.EntityFrameworkCore.Metadata.ITableMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable", + "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IStoredProcedureMapping : Microsoft.EntityFrameworkCore.Metadata.ITableMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable, System.IComparable", "Properties": [ { "Member": "System.Collections.Generic.IEnumerable ParameterMappings { get; }" @@ -6409,7 +6409,7 @@ ] }, { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IStoredProcedureParameterMapping : Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable", + "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IStoredProcedureParameterMapping : Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable, System.IComparable", "Methods": [ { "Member": "string ToDebugString(Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions options = Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions.ShortDefault, int indent = 0);" @@ -6439,7 +6439,7 @@ ] }, { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IStoredProcedureResultColumnMapping : Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable", + "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IStoredProcedureResultColumnMapping : Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable, System.IComparable", "Methods": [ { "Member": "string ToDebugString(Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions options = Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions.ShortDefault, int indent = 0);" @@ -6728,7 +6728,7 @@ ] }, { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.ITableMapping : Microsoft.EntityFrameworkCore.Metadata.ITableMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable", + "Type": "interface Microsoft.EntityFrameworkCore.Metadata.ITableMapping : Microsoft.EntityFrameworkCore.Metadata.ITableMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable, System.IComparable", "Properties": [ { "Member": "System.Collections.Generic.IEnumerable ColumnMappings { get; }" @@ -6748,7 +6748,7 @@ ] }, { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.ITableMappingBase : Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable", + "Type": "interface Microsoft.EntityFrameworkCore.Metadata.ITableMappingBase : Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable, System.IComparable", "Methods": [ { "Member": "string ToDebugString(Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions options = Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions.ShortDefault, int indent = 0);" @@ -6897,7 +6897,7 @@ ] }, { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IViewColumnMapping : Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable", + "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IViewColumnMapping : Microsoft.EntityFrameworkCore.Metadata.IColumnMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable, System.IComparable", "Methods": [ { "Member": "string ToDebugString(Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions options = Microsoft.EntityFrameworkCore.Infrastructure.MetadataDebugStringOptions.ShortDefault, int indent = 0);" @@ -6913,7 +6913,7 @@ ] }, { - "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IViewMapping : Microsoft.EntityFrameworkCore.Metadata.ITableMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable", + "Type": "interface Microsoft.EntityFrameworkCore.Metadata.IViewMapping : Microsoft.EntityFrameworkCore.Metadata.ITableMappingBase, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable, Microsoft.EntityFrameworkCore.Infrastructure.IReadOnlyAnnotatable, System.IComparable", "Properties": [ { "Member": "System.Collections.Generic.IEnumerable ColumnMappings { get; }" diff --git a/src/EFCore.Relational/Metadata/IColumnMappingBase.cs b/src/EFCore.Relational/Metadata/IColumnMappingBase.cs index 2dfd5558bb9..307213d2db7 100644 --- a/src/EFCore.Relational/Metadata/IColumnMappingBase.cs +++ b/src/EFCore.Relational/Metadata/IColumnMappingBase.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public interface IColumnMappingBase : IAnnotatable +public interface IColumnMappingBase : IAnnotatable, IComparable { /// /// Gets the mapped property. @@ -30,4 +30,10 @@ public interface IColumnMappingBase : IAnnotatable /// Gets the containing table mapping. /// ITableMappingBase TableMapping { get; } + + /// + /// Compares the current instance with another object of the same type. + /// + int IComparable.CompareTo(IColumnMappingBase? other) + => other is null ? 1 : 0; } diff --git a/src/EFCore.Relational/Metadata/ITableMappingBase.cs b/src/EFCore.Relational/Metadata/ITableMappingBase.cs index 1b4e50ca8b0..2f882d63cc8 100644 --- a/src/EFCore.Relational/Metadata/ITableMappingBase.cs +++ b/src/EFCore.Relational/Metadata/ITableMappingBase.cs @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public interface ITableMappingBase : IAnnotatable +public interface ITableMappingBase : IAnnotatable, IComparable { /// /// Gets the mapped type base. @@ -135,4 +135,10 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt return builder.ToString(); } + + /// + /// Compares the current instance with another object of the same type. + /// + int IComparable.CompareTo(ITableMappingBase? other) + => other is null ? 1 : 0; } diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnMappingBase.cs b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBase.cs index a74933e1eba..d8b9c825f90 100644 --- a/src/EFCore.Relational/Metadata/Internal/ColumnMappingBase.cs +++ b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBase.cs @@ -74,4 +74,33 @@ ITableMappingBase IColumnMappingBase.TableMapping [DebuggerStepThrough] get => TableMapping; } + + /// + public virtual int CompareTo(IColumnMappingBase? other) + { + if (ReferenceEquals(this, other)) + { + return 0; + } + + if (other is null) + { + return 1; + } + + var result = other.Property.IsPrimaryKey().CompareTo(Property.IsPrimaryKey()); + if (result != 0) + { + return result; + } + + result = TableMapping.CompareTo(other.TableMapping); + if (result != 0) + { + return result; + } + + result = StringComparer.Ordinal.Compare(Property.Name, other.Property.Name); + return result != 0 ? result : StringComparer.Ordinal.Compare(Column.Name, other.Column.Name); + } } diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs index 57646e13623..8c76aeb1f7e 100644 --- a/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs +++ b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs @@ -30,37 +30,9 @@ private ColumnMappingBaseComparer() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public int Compare(IColumnMappingBase? x, IColumnMappingBase? y) - { - if (ReferenceEquals(x, y)) - { - return 0; - } - - if (x is null) - { - return -1; - } - - if (y is null) - { - return 1; - } - - var result = y.Property.IsPrimaryKey().CompareTo(x.Property.IsPrimaryKey()); - if (result != 0) - { - return result; - } - - result = TableMappingBaseComparer.Instance.Compare(x.TableMapping, y.TableMapping); - if (result != 0) - { - return result; - } - - result = StringComparer.Ordinal.Compare(x.Property.Name, y.Property.Name); - return result != 0 ? result : StringComparer.Ordinal.Compare(x.Column.Name, y.Column.Name); - } + => x is null + ? y is null ? 0 : -1 + : y is null ? 1 : x.CompareTo(y); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/FunctionMapping.cs b/src/EFCore.Relational/Metadata/Internal/FunctionMapping.cs index d6282e8f9c5..e96b4c77404 100644 --- a/src/EFCore.Relational/Metadata/Internal/FunctionMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/FunctionMapping.cs @@ -46,4 +46,71 @@ IEnumerable IFunctionMapping.ColumnMappings [DebuggerStepThrough] get => ColumnMappings; } + + /// + protected override int CompareTableDetails(ITableMappingBase other) + { + if (other is not IFunctionMapping otherFunctionMapping) + { + return 1; + } + + if (Table is StoreFunction storeFunction + && otherFunctionMapping.Table is StoreFunction otherStoreFunction) + { + var xParams = storeFunction.Parameters; + var yParams = otherStoreFunction.Parameters; + var result = xParams.Length.CompareTo(yParams.Length); + if (result != 0) + { + return result; + } + + for (var i = 0; i < xParams.Length; i++) + { + var xp = xParams[i]; + var yp = yParams[i]; + result = StringComparer.Ordinal.Compare(xp.StoreType, yp.StoreType); + if (result != 0) + { + return result; + } + + result = StringComparer.Ordinal.Compare(xp.Name, yp.Name); + if (result != 0) + { + return result; + } + } + + return 0; + } + + var xList = StoreFunction.Parameters.ToList(); + var yList = otherFunctionMapping.StoreFunction.Parameters.ToList(); + var countResult = xList.Count.CompareTo(yList.Count); + if (countResult != 0) + { + return countResult; + } + + for (var i = 0; i < xList.Count; i++) + { + var xp = xList[i]; + var yp = yList[i]; + var result = StringComparer.Ordinal.Compare(xp.StoreType, yp.StoreType); + if (result != 0) + { + return result; + } + + result = StringComparer.Ordinal.Compare(xp.Name, yp.Name); + if (result != 0) + { + return result; + } + } + + return 0; + } } diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedureMapping.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedureMapping.cs index 4224b4ab73a..b1eec11f497 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedureMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedureMapping.cs @@ -104,4 +104,45 @@ IEnumerable IStoredProcedureMapping.ParameterM [DebuggerStepThrough] get => ParameterMappings; } + + /// + protected override int CompareTableDetails(ITableMappingBase other) + { + if (other is IFunctionMapping) + { + return -1; + } + + if (other is not IStoredProcedureMapping otherSprocMapping) + { + return 1; + } + + var xSproc = StoreStoredProcedure; + var ySproc = otherSprocMapping.StoreStoredProcedure; + var result = xSproc.Parameters.Count.CompareTo(ySproc.Parameters.Count); + if (result != 0) + { + return result; + } + + for (var i = 0; i < xSproc.Parameters.Count; i++) + { + var xp = xSproc.Parameters[i]; + var yp = ySproc.Parameters[i]; + result = StringComparer.Ordinal.Compare(xp.StoreType, yp.StoreType); + if (result != 0) + { + return result; + } + + result = StringComparer.Ordinal.Compare(xp.Name, yp.Name); + if (result != 0) + { + return result; + } + } + + return 0; + } } diff --git a/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs b/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs index 68c7e859838..6e60b9b5423 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs @@ -97,6 +97,128 @@ public virtual bool AddColumnMapping(TColumnMapping columnMapping) public virtual void SetIsSharedTablePrincipal(bool isSharedTablePrincipal) => throw new NotImplementedException(); + /// + public virtual int CompareTo(ITableMappingBase? other) + { + if (ReferenceEquals(this, other)) + { + return 0; + } + + if (other is null) + { + return 1; + } + + var result = 0; + if (other.IsSharedTablePrincipal == null) + { + if (IsSharedTablePrincipal != null) + { + return 1; + } + } + else + { + if (IsSharedTablePrincipal == null) + { + return -1; + } + + result = other.IsSharedTablePrincipal.Value.CompareTo(IsSharedTablePrincipal.Value); + if (result != 0) + { + return result; + } + } + + if (other.IncludesDerivedTypes == null) + { + if (IncludesDerivedTypes != null) + { + return -1; + } + } + else + { + if (IncludesDerivedTypes == null) + { + return 1; + } + + result = other.IncludesDerivedTypes.Value.CompareTo(IncludesDerivedTypes.Value); + if (result != 0) + { + return result; + } + } + + if (other.IsSplitEntityTypePrincipal == null) + { + if (IsSplitEntityTypePrincipal != null) + { + return -1; + } + } + else + { + if (IsSplitEntityTypePrincipal == null) + { + return 1; + } + + result = other.IsSplitEntityTypePrincipal.Value.CompareTo(IsSplitEntityTypePrincipal.Value); + if (result != 0) + { + return result; + } + } + + result = TypeBaseNameComparer.Instance.Compare(TypeBase, other.TypeBase); + if (result != 0) + { + return result; + } + + result = StringComparer.Ordinal.Compare(Table.Name, other.Table.Name); + if (result != 0) + { + return result; + } + + result = StringComparer.Ordinal.Compare(Table.Schema, other.Table.Schema); + if (result != 0) + { + return result; + } + + result = CompareTableDetails(other); + if (result != 0) + { + return result; + } + + result = ColumnMappings.Count.CompareTo(other.ColumnMappings.Count()); + return result != 0 + ? result + : ColumnMappings.Zip( + other.ColumnMappings, (xc, yc) => + { + var columnResult = StringComparer.Ordinal.Compare(xc.Property.Name, yc.Property.Name); + return columnResult != 0 ? columnResult : StringComparer.Ordinal.Compare(xc.Column.Name, yc.Column.Name); + }) + .FirstOrDefault(r => r != 0); + } + + /// + /// 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. + /// + protected virtual int CompareTableDetails(ITableMappingBase other) + => other is IFunctionMapping or IStoredProcedureMapping ? -1 : 0; + /// /// 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 diff --git a/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs b/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs index a2e3851d7da..99c33fba10c 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs @@ -31,115 +31,9 @@ private TableMappingBaseComparer() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public int Compare(ITableMappingBase? x, ITableMappingBase? y) - { - if (ReferenceEquals(x, y)) - { - return 0; - } - - if (x is null) - { - return -1; - } - - if (y is null) - { - return 1; - } - - var result = 0; - if (y.IsSharedTablePrincipal == null) - { - if (x.IsSharedTablePrincipal != null) - { - return 1; - } - } - else - { - if (x.IsSharedTablePrincipal == null) - { - return -1; - } - - result = y.IsSharedTablePrincipal.Value.CompareTo(x.IsSharedTablePrincipal.Value); - if (result != 0) - { - return result; - } - } - - if (y.IncludesDerivedTypes == null) - { - if (x.IncludesDerivedTypes != null) - { - return -1; - } - } - else - { - if (x.IncludesDerivedTypes == null) - { - return 1; - } - - result = y.IncludesDerivedTypes.Value.CompareTo(x.IncludesDerivedTypes.Value); - if (result != 0) - { - return result; - } - } - - if (y.IsSplitEntityTypePrincipal == null) - { - if (x.IsSplitEntityTypePrincipal != null) - { - return -1; - } - } - else - { - if (x.IsSplitEntityTypePrincipal == null) - { - return 1; - } - - result = y.IsSplitEntityTypePrincipal.Value.CompareTo(x.IsSplitEntityTypePrincipal.Value); - if (result != 0) - { - return result; - } - } - - result = TypeBaseNameComparer.Instance.Compare(x.TypeBase, y.TypeBase); - if (result != 0) - { - return result; - } - - result = StringComparer.Ordinal.Compare(x.Table.Name, y.Table.Name); - if (result != 0) - { - return result; - } - - result = StringComparer.Ordinal.Compare(x.Table.Schema, y.Table.Schema); - if (result != 0) - { - return result; - } - - result = x.ColumnMappings.Count().CompareTo(y.ColumnMappings.Count()); - return result != 0 - ? result - : x.ColumnMappings.Zip( - y.ColumnMappings, (xc, yc) => - { - var columnResult = StringComparer.Ordinal.Compare(xc.Property.Name, yc.Property.Name); - return columnResult != 0 ? columnResult : StringComparer.Ordinal.Compare(xc.Column.Name, yc.Column.Name); - }) - .FirstOrDefault(r => r != 0); - } + => x is null + ? y is null ? 0 : -1 + : y is null ? 1 : x.CompareTo(y); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -168,6 +62,23 @@ public int GetHashCode(ITableMappingBase obj) hashCode.Add(obj.TypeBase, TypeBaseNameComparer.Instance); hashCode.Add(obj.Table.Name); hashCode.Add(obj.Table.Schema); + if (obj.Table is IStoreFunction function) + { + foreach (var parameter in function.Parameters) + { + hashCode.Add(parameter.StoreType); + hashCode.Add(parameter.Name); + } + } + else if (obj.Table is IStoreStoredProcedure sproc) + { + foreach (var parameter in sproc.Parameters) + { + hashCode.Add(parameter.StoreType); + hashCode.Add(parameter.Name); + } + } + foreach (var columnMapping in obj.ColumnMappings) { hashCode.Add(columnMapping.Property.Name); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index 6f497af875c..0e7b7c9d604 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -2978,6 +2978,9 @@ private static IQueryable GetOrdersForCustomer(int id) private static IQueryable GetOrdersForCustomer(string name) => throw new NotImplementedException(); + private static IQueryable GetOrdersForCustomer(int id, string name) + => throw new NotImplementedException(); + [Fact] public void Complex_property_container_column_type_is_used_in_relational_model() { @@ -3094,32 +3097,41 @@ public void Can_use_relational_model_with_functions() typeof(RelationalModelTest).GetMethod( nameof(GetOrdersForCustomer), BindingFlags.NonPublic | BindingFlags.Static, [typeof(string)])!); + modelBuilder.HasDbFunction( + typeof(RelationalModelTest).GetMethod( + nameof(GetOrdersForCustomer), BindingFlags.NonPublic | BindingFlags.Static, [typeof(int), typeof(string)])!); + var model = Finalize(modelBuilder); Assert.Single(model.Model.GetEntityTypes()); - Assert.Equal(3, model.Functions.Count()); + Assert.Equal(4, model.Functions.Count()); Assert.Empty(model.Views); Assert.Empty(model.Tables); var orderType = model.Model.FindEntityType(typeof(Order))!; Assert.Null(orderType.FindPrimaryKey()); - Assert.Equal(3, orderType.GetFunctionMappings().Count()); + Assert.Equal(4, orderType.GetFunctionMappings().Count()); var orderMapping = orderType.GetFunctionMappings().First(); Assert.Null(orderMapping.IsSharedTablePrincipal); Assert.Null(orderMapping.IsSplitEntityTypePrincipal); Assert.True(orderMapping.IsDefaultFunctionMapping); - var tvfMapping = orderType.GetFunctionMappings().ElementAt(1); + var tvfMapping = orderType.GetFunctionMappings().ElementAt(2); Assert.Null(tvfMapping.IsSharedTablePrincipal); Assert.Null(tvfMapping.IsSplitEntityTypePrincipal); Assert.False(tvfMapping.IsDefaultFunctionMapping); - var tvfMapping2 = orderType.GetFunctionMappings().Last(); + var tvfMapping2 = orderType.GetFunctionMappings().ElementAt(3); Assert.Null(tvfMapping2.IsSharedTablePrincipal); Assert.Null(tvfMapping2.IsSplitEntityTypePrincipal); Assert.False(tvfMapping2.IsDefaultFunctionMapping); + var tvfMapping3 = orderType.GetFunctionMappings().ElementAt(1); + Assert.Null(tvfMapping3.IsSharedTablePrincipal); + Assert.Null(tvfMapping3.IsSplitEntityTypePrincipal); + Assert.False(tvfMapping3.IsDefaultFunctionMapping); + Assert.Null(orderMapping.IncludesDerivedTypes); Assert.Equal( [nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.Id), nameof(Order.OrderDate)], @@ -3140,7 +3152,7 @@ public void Can_use_relational_model_with_functions() Assert.Null(ordersFunction.ReturnType); var orderDate = orderType.FindProperty(nameof(Order.OrderDate))!; - Assert.Equal(2, orderDate.GetFunctionColumnMappings().Count()); + Assert.Equal(4, orderDate.GetFunctionColumnMappings().Count()); var orderDateMapping = orderMapping.ColumnMappings.Single(m => m.Property == orderDate); Assert.NotNull(orderDateMapping.TypeMapping); Assert.Equal("default_datetime_mapping", orderDateMapping.TypeMapping.StoreType); @@ -3172,6 +3184,7 @@ public void Can_use_relational_model_with_functions() Assert.Same(model.Model.GetDbFunctions().First(f => f.Parameters.Count() == 1), tvfDbFunction); Assert.Same(tvfFunction.Parameters.Single(), tvfDbFunction.Parameters.Single().StoreFunctionParameter); Assert.Equal(tvfDbFunction.Parameters.Single().Name, tvfFunction.Parameters.Single().DbFunctionParameters.Single().Name); + Assert.NotNull(tvfFunction.FindColumn(orderDate)); var tvfFunction2 = tvfMapping2.StoreFunction; Assert.Same(tvfMapping2, tvfFunction2.EntityTypeMappings.Single()); @@ -3184,9 +3197,17 @@ public void Can_use_relational_model_with_functions() var tvfDbFunction2 = tvfFunction2.DbFunctions.Single(); Assert.Same(tvfFunction2, tvfDbFunction2.StoreFunction); - Assert.Same(model.Model.GetDbFunctions().Last(f => f.Parameters.Count() == 1), tvfDbFunction2); + Assert.Same(model.Model.GetDbFunctions().First(f => f.Parameters.Count() == 1 && f.Parameters.Single().ClrType == typeof(string)), tvfDbFunction2); Assert.Same(tvfFunction2.Parameters.Single(), tvfDbFunction2.Parameters.Single().StoreFunctionParameter); Assert.Equal(tvfDbFunction2.Parameters.Single().Name, tvfFunction2.Parameters.Single().DbFunctionParameters.Single().Name); + Assert.NotNull(tvfFunction2.FindColumn(orderDate)); + + var tvfFunction3 = tvfMapping3.StoreFunction; + Assert.Same(tvfMapping3, tvfFunction3.EntityTypeMappings.Single()); + Assert.Same(tvfFunction3, model.FindFunction(tvfFunction3.Name, tvfFunction3.Schema, ["default_int_mapping", "just_string(max)"])); + Assert.Equal(nameof(GetOrdersForCustomer), tvfFunction3.Name); + Assert.Equal(2, tvfFunction3.Parameters.Count()); + Assert.NotNull(tvfFunction3.FindColumn(orderDate)); } [Fact]