From 498a452e77dfb1e9ea64d73230e0780d0e99fc35 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 27 Dec 2019 21:01:04 -0800 Subject: [PATCH] Fix propagation of fixed length facet Fixes #18961 Fixes #14163 --- .../Internal/CSharpDbContextGenerator.cs | 2 +- .../Internal/ScaffoldingTypeMapper.cs | 3 +- .../RelationalPropertyExtensions.cs | 14 +- .../Migrations/MigrationsSqlGenerator.cs | 2 +- .../Internal/SqlServerTypeMappingSource.cs | 21 +- .../Migrations/ModelSnapshotSqlServerTest.cs | 7 +- .../MigrationSqlGeneratorTestBase.cs | 8 +- .../RelationalMetadataExtensionsTest.cs | 6 +- .../Storage/RelationalTypeMapperTest.cs | 20 +- .../Storage/RelationalTypeMapperTestBase.cs | 4 +- .../TestRelationalTypeMappingSource.cs | 3 +- .../SqlServerTypeMapperTest.cs | 601 ++++++++++++++---- 12 files changed, 509 insertions(+), 182 deletions(-) diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index 924c5e76d08..1748df0deb7 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -664,7 +664,7 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) $"({(property.IsUnicode() == false ? "false" : "")})"); } - if (property.IsFixedLength()) + if (property.IsFixedLength() != null) { lines.Add( $".{nameof(RelationalPropertyBuilderExtensions.IsFixedLength)}()"); diff --git a/src/EFCore.Design/Scaffolding/Internal/ScaffoldingTypeMapper.cs b/src/EFCore.Design/Scaffolding/Internal/ScaffoldingTypeMapper.cs index ec7518babff..9a4e9619140 100644 --- a/src/EFCore.Design/Scaffolding/Internal/ScaffoldingTypeMapper.cs +++ b/src/EFCore.Design/Scaffolding/Internal/ScaffoldingTypeMapper.cs @@ -134,13 +134,12 @@ public virtual TypeScaffoldingInfo FindMapping( ? (bool?)stringMapping.IsFixedLength : null; - // Check for size var sizedMapping = _typeMappingSource.FindMapping( typeof(string), null, keyOrIndex, unicode: mapping.IsUnicode, - fixedLength: mapping.IsFixedLength); + fixedLength: false); // Fixed length with no size is not valid scaffoldMaxLength = sizedMapping.Size != stringMapping.Size ? stringMapping.Size : null; } diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index b1eb1a66d63..3f90ff0f311 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -360,14 +360,8 @@ private static object ConvertDefaultValue([NotNull] IProperty property, [CanBeNu /// /// The property. /// A flag indicating if the property as capable of storing only fixed-length data, such as strings. - public static bool IsFixedLength([NotNull] this IProperty property) - => (bool?)property[RelationalAnnotationNames.IsFixedLength] ?? GetDefaultIsFixedLength(property); - - private static bool GetDefaultIsFixedLength(IProperty property) - { - var sharedTablePrincipalPrimaryKeyProperty = property.FindSharedTableRootPrimaryKeyProperty(); - return sharedTablePrincipalPrimaryKeyProperty != null && IsFixedLength(sharedTablePrincipalPrimaryKeyProperty); - } + public static bool? IsFixedLength([NotNull] this IProperty property) + => (bool?)property[RelationalAnnotationNames.IsFixedLength]; /// /// Sets a flag indicating whether the property as capable of storing only fixed-length data, such as strings. @@ -375,9 +369,7 @@ private static bool GetDefaultIsFixedLength(IProperty property) /// The property. /// A value indicating whether the property is constrained to fixed length values. public static void SetIsFixedLength([NotNull] this IMutableProperty property, bool? fixedLength) - => property.SetOrRemoveAnnotation( - RelationalAnnotationNames.IsFixedLength, - fixedLength); + => property.SetOrRemoveAnnotation(RelationalAnnotationNames.IsFixedLength, fixedLength); /// /// Sets a flag indicating whether the property as capable of storing only fixed-length data, such as strings. diff --git a/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs b/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs index 64f7d951721..fc45e8aa7b0 100644 --- a/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs +++ b/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs @@ -1200,7 +1200,7 @@ protected virtual string GetColumnType( { if (operation.IsUnicode == property.IsUnicode() && operation.MaxLength == property.GetMaxLength() - && (operation.IsFixedLength ?? false) == property.IsFixedLength() + && operation.IsFixedLength == property.IsFixedLength() && operation.IsRowVersion == (property.IsConcurrencyToken && property.ValueGenerated == ValueGenerated.OnAddOrUpdate)) { return Dependencies.TypeMappingSource.FindMapping(property).StoreType; diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs index b4587783242..db01c6a4cbe 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs @@ -319,12 +319,21 @@ private RelationalTypeMapping FindRawMapping(RelationalTypeMappingInfo mappingIn size = isFixedLength ? maxSize : (int?)null; } - return size == null - ? isAnsi ? _variableLengthMaxAnsiString : _variableLengthMaxUnicodeString - : new SqlServerStringTypeMapping( - unicode: !isAnsi, - size: size, - fixedLength: isFixedLength); + if (size == null) + { + return isAnsi + ? isFixedLength + ? _fixedLengthAnsiString + : _variableLengthMaxAnsiString + : isFixedLength + ? _fixedLengthUnicodeString + : _variableLengthMaxUnicodeString; + } + + return new SqlServerStringTypeMapping( + unicode: !isAnsi, + size: size, + fixedLength: isFixedLength); } if (clrType == typeof(byte[])) diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 7dfe91fb6bf..23a7b9ba2c7 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -1651,7 +1651,7 @@ public virtual void Property_unicodeness_is_stored_in_snapshot() public virtual void Property_fixedlengthness_is_stored_in_snapshot() { Test( - builder => builder.Entity().Property("Name").IsFixedLength(), + builder => builder.Entity().Property("Name").IsFixedLength().HasMaxLength(100), AddBoilerPlate( GetHeading() + @" @@ -1663,8 +1663,9 @@ public virtual void Property_fixedlengthness_is_stored_in_snapshot() .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); b.Property(""Name"") - .HasColumnType(""nvarchar(max)"") - .IsFixedLength(true); + .HasColumnType(""nchar(100)"") + .IsFixedLength(true) + .HasMaxLength(100); b.HasKey(""Id""); diff --git a/test/EFCore.Relational.Specification.Tests/MigrationSqlGeneratorTestBase.cs b/test/EFCore.Relational.Specification.Tests/MigrationSqlGeneratorTestBase.cs index bf2b6926647..d700103e8b2 100644 --- a/test/EFCore.Relational.Specification.Tests/MigrationSqlGeneratorTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/MigrationSqlGeneratorTestBase.cs @@ -123,7 +123,7 @@ public virtual void AddColumnOperation_with_unicode_no_model() [ConditionalFact] public virtual void AddColumnOperation_with_fixed_length() => Generate( - modelBuilder => modelBuilder.Entity("Person").Property("Name").IsFixedLength(), + modelBuilder => modelBuilder.Entity("Person").Property("Name").HasMaxLength(100).IsFixedLength(), new AddColumnOperation { Table = "Person", @@ -131,7 +131,8 @@ public virtual void AddColumnOperation_with_fixed_length() ClrType = typeof(string), IsUnicode = true, IsNullable = true, - IsFixedLength = true + IsFixedLength = true, + MaxLength = 100 }); [ConditionalFact] @@ -144,7 +145,8 @@ public virtual void AddColumnOperation_with_fixed_length_no_model() ClrType = typeof(string), IsUnicode = false, IsNullable = true, - IsFixedLength = true + IsFixedLength = true, + MaxLength = 100 }); [ConditionalFact] diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs index db10824b33e..6b1407cfa75 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs @@ -21,7 +21,7 @@ public void Can_get_and_set_fixed_length() .Property(e => e.Name) .Metadata; - Assert.False(property.IsFixedLength()); + Assert.Null(property.IsFixedLength()); property.SetIsFixedLength(true); @@ -30,6 +30,10 @@ public void Can_get_and_set_fixed_length() property.SetIsFixedLength(false); Assert.False(property.IsFixedLength()); + + property.SetIsFixedLength(null); + + Assert.Null(property.IsFixedLength()); } [ConditionalFact] diff --git a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs index 6014000f902..7be1719f597 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs @@ -159,32 +159,32 @@ public static RelationalTypeMapping GetMapping( => CreateTestTypeMapper().FindMapping(property); [ConditionalFact] - public void String_key_with_max_length_is_picked_up_by_FK() + public void String_key_with_max_fixed_length_is_picked_up_by_FK() { var model = CreateModel(); var mapper = CreateTestTypeMapper(); Assert.Equal( - "just_string(200)", + "just_string_fixed(200)", GetMapping(mapper, model.FindEntityType(typeof(MyRelatedType1)).FindProperty("Id")).StoreType); Assert.Equal( - "just_string(200)", + "just_string_fixed(200)", GetMapping(mapper, model.FindEntityType(typeof(MyRelatedType2)).FindProperty("Relationship1Id")).StoreType); } [ConditionalFact] - public void Binary_key_with_max_length_is_picked_up_by_FK() + public void Binary_key_with_max_fixed_length_is_picked_up_by_FK() { var model = CreateModel(); var mapper = CreateTestTypeMapper(); Assert.Equal( - "just_binary(100)", + "just_binary_fixed(100)", GetMapping(mapper, model.FindEntityType(typeof(MyRelatedType2)).FindProperty("Id")).StoreType); Assert.Equal( - "just_binary(100)", + "just_binary_fixed(100)", GetMapping(mapper, model.FindEntityType(typeof(MyRelatedType3)).FindProperty("Relationship1Id")).StoreType); } @@ -225,11 +225,11 @@ public void String_FK_max_length_is_preferred_if_specified() var mapper = CreateTestTypeMapper(); Assert.Equal( - "just_string(200)", + "just_string_fixed(200)", GetMapping(mapper, model.FindEntityType(typeof(MyRelatedType1)).FindProperty("Id")).StoreType); Assert.Equal( - "just_string(787)", + "just_string_fixed(787)", GetMapping(mapper, model.FindEntityType(typeof(MyRelatedType2)).FindProperty("Relationship2Id")).StoreType); } @@ -240,11 +240,11 @@ public void Binary_FK_max_length_is_preferred_if_specified() var mapper = CreateTestTypeMapper(); Assert.Equal( - "just_binary(100)", + "just_binary_fixed(100)", GetMapping(mapper, model.FindEntityType(typeof(MyRelatedType2)).FindProperty("Id")).StoreType); Assert.Equal( - "just_binary(767)", + "just_binary_fixed(767)", GetMapping(mapper, model.FindEntityType(typeof(MyRelatedType3)).FindProperty("Relationship2Id")).StoreType); } diff --git a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs index d594ae89cd1..13827eaee7e 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs @@ -15,9 +15,9 @@ protected IMutableModel CreateModel() var builder = CreateModelBuilder(); builder.Entity().Property(e => e.Id).HasColumnType("money"); - builder.Entity().Property(e => e.Id).HasMaxLength(200); + builder.Entity().Property(e => e.Id).HasMaxLength(200).IsFixedLength(); builder.Entity().Property(e => e.Relationship2Id).HasColumnType("dec(6,1)"); - builder.Entity().Property(e => e.Id).HasMaxLength(100); + builder.Entity().Property(e => e.Id).HasMaxLength(100).IsFixedLength(); builder.Entity().Property(e => e.Relationship2Id).HasMaxLength(787); builder.Entity().Property(e => e.Id).IsUnicode(false); builder.Entity().Property(e => e.Relationship2Id).HasMaxLength(767); diff --git a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs index d27a5ea8e49..ae0e1bc7de4 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs @@ -188,9 +188,10 @@ protected override RelationalTypeMapping FindMapping(in RelationalTypeMappingInf } var size = mappingInfo.Size ?? (mappingInfo.IsKeyOrIndex ? (int?)900 : null); + var isFixedLength = mappingInfo.IsFixedLength == true; return new ByteArrayTypeMapping( - storeTypeName ?? "just_binary(" + (size == null ? "max" : size.ToString()) + ")", + storeTypeName ?? (isFixedLength ? "just_binary_fixed(" : "just_binary(") + (size == null ? "max" : size.ToString()) + ")", DbType.Binary, size); } diff --git a/test/EFCore.SqlServer.Tests/SqlServerTypeMapperTest.cs b/test/EFCore.SqlServer.Tests/SqlServerTypeMapperTest.cs index c6c0ed45ae0..e59c7750e39 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerTypeMapperTest.cs +++ b/test/EFCore.SqlServer.Tests/SqlServerTypeMapperTest.cs @@ -127,83 +127,172 @@ public void Does_decimal_mapping_for_nullable_CLR_types() } [ConditionalTheory] - [InlineData(true)] - [InlineData(null)] - public void Does_non_key_SQL_Server_string_mapping(bool? unicode) + [InlineData(true, false)] + [InlineData(null, false)] + [InlineData(true, null)] + [InlineData(null, null)] + public void Does_non_key_SQL_Server_string_mapping(bool? unicode, bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(string), unicode: unicode); + var typeMapping = GetTypeMapping(typeof(string), unicode: unicode, fixedLength: fixedLength); Assert.Null(typeMapping.DbType); Assert.Equal("nvarchar(max)", typeMapping.StoreType); Assert.Null(typeMapping.Size); Assert.True(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(4000, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } [ConditionalTheory] - [InlineData(true)] - [InlineData(null)] - public void Does_non_key_SQL_Server_string_mapping_with_max_length(bool? unicode) + [InlineData(true, false)] + [InlineData(null, false)] + [InlineData(true, null)] + [InlineData(null, null)] + public void Does_non_key_SQL_Server_string_mapping_with_value_that_fits_max_length(bool? unicode, bool? fixedLength) + { + var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode, fixedLength); + + Assert.Null(typeMapping.DbType); + Assert.Equal("nvarchar(3)", typeMapping.StoreType); + Assert.Equal(3, typeMapping.Size); + Assert.True(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); + Assert.Equal(3, typeMapping.CreateParameter(new TestCommand(), "Name", "Va").Size); + } + + [ConditionalTheory] + [InlineData(true, false)] + [InlineData(null, false)] + [InlineData(true, null)] + [InlineData(null, null)] + public void Does_non_key_SQL_Server_string_mapping_with_max_length(bool? unicode, bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode: unicode); + var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode, fixedLength); Assert.Null(typeMapping.DbType); Assert.Equal("nvarchar(3)", typeMapping.StoreType); Assert.Equal(3, typeMapping.Size); Assert.True(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(-1, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } [ConditionalTheory] [InlineData(true)] [InlineData(null)] - public void Does_non_key_SQL_Server_string_mapping_with_long_string(bool? unicode) + public void Does_non_key_SQL_Server_fixed_string_mapping_with_max_length_large_value(bool? unicode) + { + var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode, fixedLength: true); + + Assert.Equal(DbType.String, typeMapping.DbType); + Assert.Equal("nchar(3)", typeMapping.StoreType); + Assert.Equal(3, typeMapping.Size); + Assert.True(typeMapping.IsUnicode); + Assert.True(typeMapping.IsFixedLength); + + var parameter = typeMapping.CreateParameter(new TestCommand(), "Name", "Value"); + Assert.Equal(DbType.String, parameter.DbType); + Assert.Equal(-1, parameter.Size); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(null)] + public void Does_non_key_SQL_Server_fixed_string_mapping_with_max_length_small_value(bool? unicode) { - var typeMapping = GetTypeMapping(typeof(string), unicode: unicode); + var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode, fixedLength: true); + + Assert.Equal(DbType.String, typeMapping.DbType); + Assert.Equal("nchar(3)", typeMapping.StoreType); + Assert.Equal(3, typeMapping.Size); + Assert.True(typeMapping.IsUnicode); + Assert.True(typeMapping.IsFixedLength); + + var parameter = typeMapping.CreateParameter(new TestCommand(), "Name", "Va"); + Assert.Equal(DbType.String, parameter.DbType); + Assert.Equal(3, parameter.Size); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(null)] + public void Does_non_key_SQL_Server_fixed_string_mapping_with_max_length_exact_value(bool? unicode) + { + var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode, fixedLength: true); + + Assert.Equal(DbType.String, typeMapping.DbType); + Assert.Equal("nchar(3)", typeMapping.StoreType); + Assert.Equal(3, typeMapping.Size); + Assert.True(typeMapping.IsUnicode); + Assert.True(typeMapping.IsFixedLength); + + var parameter = typeMapping.CreateParameter(new TestCommand(), "Name", "Val"); + Assert.Equal(DbType.String, parameter.DbType); + Assert.Equal(3, parameter.Size); + } + + [ConditionalTheory] + [InlineData(true, false)] + [InlineData(null, false)] + [InlineData(true, null)] + [InlineData(null, null)] + public void Does_non_key_SQL_Server_string_mapping_with_long_string(bool? unicode, bool? fixedLength) + { + var typeMapping = GetTypeMapping(typeof(string), unicode: unicode, fixedLength: fixedLength); Assert.Null(typeMapping.DbType); Assert.Equal("nvarchar(max)", typeMapping.StoreType); Assert.Null(typeMapping.Size); Assert.True(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(-1, typeMapping.CreateParameter(new TestCommand(), "Name", new string('X', 4001)).Size); } [ConditionalTheory] - [InlineData(true)] - [InlineData(null)] - public void Does_non_key_SQL_Server_string_mapping_with_max_length_with_long_string(bool? unicode) + [InlineData(true, false)] + [InlineData(null, false)] + [InlineData(true, null)] + [InlineData(null, null)] + public void Does_non_key_SQL_Server_string_mapping_with_max_length_with_long_string(bool? unicode, bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode: unicode); + var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode, fixedLength); Assert.Null(typeMapping.DbType); Assert.Equal("nvarchar(3)", typeMapping.StoreType); Assert.Equal(3, typeMapping.Size); Assert.True(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(-1, typeMapping.CreateParameter(new TestCommand(), "Name", new string('X', 4001)).Size); } [ConditionalTheory] - [InlineData(true)] - [InlineData(null)] - public void Does_non_key_SQL_Server_required_string_mapping(bool? unicode) + [InlineData(true, false)] + [InlineData(null, false)] + [InlineData(true, null)] + [InlineData(null, null)] + public void Does_non_key_SQL_Server_required_string_mapping(bool? unicode, bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(string), nullable: false, unicode: unicode); + var typeMapping = GetTypeMapping(typeof(string), nullable: false, unicode: unicode, fixedLength: fixedLength); Assert.Null(typeMapping.DbType); Assert.Equal("nvarchar(max)", typeMapping.StoreType); Assert.Null(typeMapping.Size); Assert.True(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(4000, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } [ConditionalTheory] - [InlineData(true)] - [InlineData(null)] - public void Does_key_SQL_Server_string_mapping(bool? unicode) + [InlineData(true, false)] + [InlineData(null, false)] + [InlineData(true, null)] + [InlineData(null, null)] + public void Does_key_SQL_Server_string_mapping(bool? unicode, bool? fixedLength) { var property = CreateEntityType().AddProperty("MyProp", typeof(string)); property.IsNullable = false; property.SetIsUnicode(unicode); + property.SetIsFixedLength(fixedLength); property.DeclaringEntityType.SetPrimaryKey(property); var typeMapping = CreateTypeMapper().GetMapping(property); @@ -212,6 +301,7 @@ public void Does_key_SQL_Server_string_mapping(bool? unicode) Assert.Equal("nvarchar(450)", typeMapping.StoreType); Assert.Equal(450, typeMapping.Size); Assert.True(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(450, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } @@ -221,13 +311,16 @@ private static IRelationalTypeMappingSource CreateTypeMapper() TestServiceFactory.Instance.Create()); [ConditionalTheory] - [InlineData(true)] - [InlineData(null)] - public void Does_foreign_key_SQL_Server_string_mapping(bool? unicode) + [InlineData(true, false)] + [InlineData(null, false)] + [InlineData(true, null)] + [InlineData(null, null)] + public void Does_foreign_key_SQL_Server_string_mapping(bool? unicode, bool? fixedLength) { var property = CreateEntityType().AddProperty("MyProp", typeof(string)); property.IsNullable = false; property.SetIsUnicode(unicode); + property.SetIsFixedLength(fixedLength); var fkProperty = property.DeclaringEntityType.AddProperty("FK", typeof(string)); var pk = property.DeclaringEntityType.SetPrimaryKey(property); property.DeclaringEntityType.AddForeignKey(fkProperty, pk, property.DeclaringEntityType); @@ -238,17 +331,21 @@ public void Does_foreign_key_SQL_Server_string_mapping(bool? unicode) Assert.Equal("nvarchar(450)", typeMapping.StoreType); Assert.Equal(450, typeMapping.Size); Assert.True(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(450, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } [ConditionalTheory] - [InlineData(true)] - [InlineData(null)] - public void Does_required_foreign_key_SQL_Server_string_mapping(bool? unicode) + [InlineData(true, false)] + [InlineData(null, false)] + [InlineData(true, null)] + [InlineData(null, null)] + public void Does_required_foreign_key_SQL_Server_string_mapping(bool? unicode, bool? fixedLength) { var property = CreateEntityType().AddProperty("MyProp", typeof(string)); property.IsNullable = false; property.SetIsUnicode(unicode); + property.SetIsFixedLength(fixedLength); var fkProperty = property.DeclaringEntityType.AddProperty("FK", typeof(string)); var pk = property.DeclaringEntityType.SetPrimaryKey(property); property.DeclaringEntityType.AddForeignKey(fkProperty, pk, property.DeclaringEntityType); @@ -260,17 +357,21 @@ public void Does_required_foreign_key_SQL_Server_string_mapping(bool? unicode) Assert.Equal("nvarchar(450)", typeMapping.StoreType); Assert.Equal(450, typeMapping.Size); Assert.True(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(450, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } [ConditionalTheory] - [InlineData(true)] - [InlineData(null)] - public void Does_indexed_column_SQL_Server_string_mapping(bool? unicode) + [InlineData(true, false)] + [InlineData(null, false)] + [InlineData(true, null)] + [InlineData(null, null)] + public void Does_indexed_column_SQL_Server_string_mapping(bool? unicode, bool? fixedLength) { var entityType = CreateEntityType(); var property = entityType.AddProperty("MyProp", typeof(string)); property.SetIsUnicode(unicode); + property.SetIsFixedLength(fixedLength); entityType.AddIndex(property); var typeMapping = CreateTypeMapper().GetMapping(property); @@ -279,75 +380,157 @@ public void Does_indexed_column_SQL_Server_string_mapping(bool? unicode) Assert.Equal("nvarchar(450)", typeMapping.StoreType); Assert.Equal(450, typeMapping.Size); Assert.True(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(450, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } - [ConditionalFact] - public void Does_non_key_SQL_Server_string_mapping_ansi() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_non_key_SQL_Server_string_mapping_ansi(bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(string), unicode: false); + var typeMapping = GetTypeMapping(typeof(string), unicode: false, fixedLength: fixedLength); Assert.Equal(DbType.AnsiString, typeMapping.DbType); Assert.Equal("varchar(max)", typeMapping.StoreType); Assert.Null(typeMapping.Size); Assert.False(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(8000, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } - [ConditionalFact] - public void Does_non_key_SQL_Server_string_mapping_with_max_length_ansi() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_non_key_SQL_Server_string_mapping_for_value_that_fits_with_max_length_ansi(bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode: false); + var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode: false, fixedLength); Assert.Equal(DbType.AnsiString, typeMapping.DbType); Assert.Equal("varchar(3)", typeMapping.StoreType); Assert.Equal(3, typeMapping.Size); Assert.False(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); + Assert.Equal(3, typeMapping.CreateParameter(new TestCommand(), "Name", "Val").Size); + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_non_key_SQL_Server_string_mapping_with_max_length_ansi(bool? fixedLength) + { + var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode: false, fixedLength: fixedLength); + + Assert.Equal(DbType.AnsiString, typeMapping.DbType); + Assert.Equal("varchar(3)", typeMapping.StoreType); + Assert.Equal(3, typeMapping.Size); + Assert.False(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(-1, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } [ConditionalFact] - public void Does_non_key_SQL_Server_string_mapping_with_long_string_ansi() + public void Does_non_key_SQL_Server_fixed_string_mapping_with_max_length_ansi_large_value() + { + var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode: false, fixedLength: true); + + Assert.Equal(DbType.AnsiString, typeMapping.DbType); + Assert.Equal("char(3)", typeMapping.StoreType); + Assert.Equal(3, typeMapping.Size); + Assert.False(typeMapping.IsUnicode); + Assert.True(typeMapping.IsFixedLength); + + var parameter = typeMapping.CreateParameter(new TestCommand(), "Name", "Value"); + Assert.Equal(DbType.AnsiString, parameter.DbType); + Assert.Equal(-1, parameter.Size); + } + + [ConditionalFact] + public void Does_non_key_SQL_Server_fixed_string_mapping_with_max_length_ansi_small_value() + { + var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode: false, fixedLength: true); + + Assert.Equal(DbType.AnsiString, typeMapping.DbType); + Assert.Equal("char(3)", typeMapping.StoreType); + Assert.Equal(3, typeMapping.Size); + Assert.False(typeMapping.IsUnicode); + Assert.True(typeMapping.IsFixedLength); + + var parameter = typeMapping.CreateParameter(new TestCommand(), "Name", "Va"); + Assert.Equal(DbType.AnsiString, parameter.DbType); + Assert.Equal(3, parameter.Size); + } + + [ConditionalFact] + public void Does_non_key_SQL_Server_fixed_string_mapping_with_max_length_ansi_exact_value() + { + var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode: false, fixedLength: true); + + Assert.Equal(DbType.AnsiString, typeMapping.DbType); + Assert.Equal("char(3)", typeMapping.StoreType); + Assert.Equal(3, typeMapping.Size); + Assert.False(typeMapping.IsUnicode); + Assert.True(typeMapping.IsFixedLength); + + var parameter = typeMapping.CreateParameter(new TestCommand(), "Name", "Val"); + Assert.Equal(DbType.AnsiString, parameter.DbType); + Assert.Equal(3, parameter.Size); + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_non_key_SQL_Server_string_mapping_with_long_string_ansi(bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(string), unicode: false); + var typeMapping = GetTypeMapping(typeof(string), unicode: false, fixedLength: fixedLength); Assert.Equal(DbType.AnsiString, typeMapping.DbType); Assert.Equal("varchar(max)", typeMapping.StoreType); Assert.Null(typeMapping.Size); Assert.False(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(-1, typeMapping.CreateParameter(new TestCommand(), "Name", new string('X', 8001)).Size); } - [ConditionalFact] - public void Does_non_key_SQL_Server_string_mapping_with_max_length_with_long_string_ansi() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_non_key_SQL_Server_string_mapping_with_max_length_with_long_string_ansi(bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode: false); + var typeMapping = GetTypeMapping(typeof(string), null, 3, unicode: false, fixedLength); Assert.Equal(DbType.AnsiString, typeMapping.DbType); Assert.Equal("varchar(3)", typeMapping.StoreType); Assert.Equal(3, typeMapping.Size); Assert.False(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(-1, typeMapping.CreateParameter(new TestCommand(), "Name", new string('X', 8001)).Size); } - [ConditionalFact] - public void Does_non_key_SQL_Server_required_string_mapping_ansi() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_non_key_SQL_Server_required_string_mapping_ansi(bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(string), nullable: false, unicode: false); + var typeMapping = GetTypeMapping(typeof(string), nullable: false, unicode: false, fixedLength: fixedLength); Assert.Equal(DbType.AnsiString, typeMapping.DbType); Assert.Equal("varchar(max)", typeMapping.StoreType); Assert.Null(typeMapping.Size); Assert.False(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(8000, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } - [ConditionalFact] - public void Does_key_SQL_Server_string_mapping_ansi() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_key_SQL_Server_string_mapping_ansi(bool? fixedLength) { var property = CreateEntityType().AddProperty("MyProp", typeof(string)); property.IsNullable = false; property.SetIsUnicode(false); + property.SetIsFixedLength(fixedLength); property.DeclaringEntityType.SetPrimaryKey(property); var typeMapping = CreateTypeMapper().GetMapping(property); @@ -356,14 +539,18 @@ public void Does_key_SQL_Server_string_mapping_ansi() Assert.Equal("varchar(900)", typeMapping.StoreType); Assert.Equal(900, typeMapping.Size); Assert.False(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(900, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } - [ConditionalFact] - public void Does_foreign_key_SQL_Server_string_mapping_ansi() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_foreign_key_SQL_Server_string_mapping_ansi(bool? fixedLength) { var property = CreateEntityType().AddProperty("MyProp", typeof(string)); property.SetIsUnicode(false); + property.SetIsFixedLength(fixedLength); property.IsNullable = false; var fkProperty = property.DeclaringEntityType.AddProperty("FK", typeof(string)); var pk = property.DeclaringEntityType.SetPrimaryKey(property); @@ -375,14 +562,18 @@ public void Does_foreign_key_SQL_Server_string_mapping_ansi() Assert.Equal("varchar(900)", typeMapping.StoreType); Assert.Equal(900, typeMapping.Size); Assert.False(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(900, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } - [ConditionalFact] - public void Does_required_foreign_key_SQL_Server_string_mapping_ansi() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_required_foreign_key_SQL_Server_string_mapping_ansi(bool? fixedLength) { var property = CreateEntityType().AddProperty("MyProp", typeof(string)); property.SetIsUnicode(false); + property.SetIsFixedLength(fixedLength); property.IsNullable = false; var fkProperty = property.DeclaringEntityType.AddProperty("FK", typeof(string)); var pk = property.DeclaringEntityType.SetPrimaryKey(property); @@ -395,15 +586,19 @@ public void Does_required_foreign_key_SQL_Server_string_mapping_ansi() Assert.Equal("varchar(900)", typeMapping.StoreType); Assert.Equal(900, typeMapping.Size); Assert.False(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(900, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } - [ConditionalFact] - public void Does_indexed_column_SQL_Server_string_mapping_ansi() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_indexed_column_SQL_Server_string_mapping_ansi(bool? fixedLength) { var entityType = CreateEntityType(); var property = entityType.AddProperty("MyProp", typeof(string)); property.SetIsUnicode(false); + property.SetIsFixedLength(fixedLength); entityType.AddIndex(property); var typeMapping = CreateTypeMapper().GetMapping(property); @@ -412,131 +607,244 @@ public void Does_indexed_column_SQL_Server_string_mapping_ansi() Assert.Equal("varchar(900)", typeMapping.StoreType); Assert.Equal(900, typeMapping.Size); Assert.False(typeMapping.IsUnicode); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(900, typeMapping.CreateParameter(new TestCommand(), "Name", "Value").Size); } - [ConditionalFact] - public void Does_non_key_SQL_Server_binary_mapping() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_non_key_SQL_Server_binary_mapping(bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(byte[])); + var typeMapping = GetTypeMapping(typeof(byte[]), fixedLength: fixedLength); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("varbinary(max)", typeMapping.StoreType); Assert.Null(typeMapping.Size); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(8000, typeMapping.CreateParameter(new TestCommand(), "Name", new byte[3]).Size); } - [ConditionalFact] - public void Does_non_key_SQL_Server_binary_mapping_with_max_length() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_non_key_SQL_Server_binary_mapping_with_max_length(bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(byte[]), null, 3); + var typeMapping = GetTypeMapping(typeof(byte[]), null, 3, fixedLength: fixedLength); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("varbinary(3)", typeMapping.StoreType); Assert.Equal(3, typeMapping.Size); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(3, typeMapping.CreateParameter(new TestCommand(), "Name", new byte[3]).Size); } - [ConditionalFact] - public void Does_non_key_SQL_Server_binary_mapping_with_long_array() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_non_key_SQL_Server_binary_mapping_with_long_array(bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(byte[])); + var typeMapping = GetTypeMapping(typeof(byte[]), fixedLength: fixedLength); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("varbinary(max)", typeMapping.StoreType); Assert.Null(typeMapping.Size); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(-1, typeMapping.CreateParameter(new TestCommand(), "Name", new byte[8001]).Size); } - [ConditionalFact] - public void Does_non_key_SQL_Server_binary_mapping_with_max_length_with_long_array() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_non_key_SQL_Server_binary_mapping_with_max_length_with_long_array(bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(byte[]), null, 3); + var typeMapping = GetTypeMapping(typeof(byte[]), null, 3, fixedLength: fixedLength); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("varbinary(3)", typeMapping.StoreType); Assert.Equal(3, typeMapping.Size); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(-1, typeMapping.CreateParameter(new TestCommand(), "Name", new byte[8001]).Size); } - [ConditionalFact] - public void Does_non_key_SQL_Server_required_binary_mapping() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_non_key_SQL_Server_required_binary_mapping(bool? fixedLength) { - var typeMapping = GetTypeMapping(typeof(byte[]), nullable: false); + var typeMapping = GetTypeMapping(typeof(byte[]), nullable: false, fixedLength: fixedLength); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("varbinary(max)", typeMapping.StoreType); Assert.Null(typeMapping.Size); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(8000, typeMapping.CreateParameter(new TestCommand(), "Name", new byte[3]).Size); } - [ConditionalFact] - public void Does_non_key_SQL_Server_fixed_length_binary_mapping() + [ConditionalTheory] + [InlineData("binary(100)", null)] + [InlineData("binary(100)", 100)] + [InlineData("binary", 100)] + [InlineData(null, 100)] + public void Does_non_key_SQL_Server_fixed_length_binary_mapping_with_small_value(string typeName, int? maxLength) { - var property = CreateEntityType().AddProperty("MyBinaryProp", typeof(byte[])); - property.SetColumnType("binary(100)"); + var typeMapping = CreateBinaryMapping(typeName, maxLength); - var typeMapping = CreateTypeMapper().GetMapping(property); + Assert.True(typeMapping.IsFixedLength); + Assert.Equal(DbType.Binary, typeMapping.DbType); + Assert.Equal("binary(100)", typeMapping.StoreType); + + var parameter = typeMapping.CreateParameter(new TestCommand(), "Name", new byte[10]); + Assert.Equal(DbType.Binary, parameter.DbType); + Assert.Equal(100, parameter.Size); + } + + [ConditionalTheory] + [InlineData("binary(100)", null)] + [InlineData("binary(100)", 100)] + [InlineData("binary", 100)] + [InlineData(null, 100)] + public void Does_non_key_SQL_Server_fixed_length_binary_mapping_with_exact_value(string typeName, int? maxLength) + { + var typeMapping = CreateBinaryMapping(typeName, maxLength); + Assert.True(typeMapping.IsFixedLength); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("binary(100)", typeMapping.StoreType); + + var parameter = typeMapping.CreateParameter(new TestCommand(), "Name", new byte[100]); + Assert.Equal(DbType.Binary, parameter.DbType); + Assert.Equal(100, parameter.Size); } - [ConditionalFact] - public void Does_key_SQL_Server_binary_mapping() + [ConditionalTheory] + [InlineData("binary(100)", null)] + [InlineData("binary(100)", 100)] + [InlineData("binary", 100)] + [InlineData(null, 100)] + public void Does_non_key_SQL_Server_fixed_length_binary_mapping_with_large_value(string typeName, int? maxLength) + { + var typeMapping = CreateBinaryMapping(typeName, maxLength); + + Assert.True(typeMapping.IsFixedLength); + Assert.Equal(DbType.Binary, typeMapping.DbType); + Assert.Equal("binary(100)", typeMapping.StoreType); + + var parameter = typeMapping.CreateParameter(new TestCommand(), "Name", new byte[101]); + Assert.Equal(DbType.Binary, parameter.DbType); + Assert.Equal(-1, parameter.Size); + } + + [ConditionalTheory] + [InlineData("binary(100)", null)] + [InlineData("binary(100)", 100)] + [InlineData("binary", 100)] + [InlineData(null, 100)] + public void Does_non_key_SQL_Server_fixed_length_binary_mapping_with_extreme_value(string typeName, int? maxLength) + { + var typeMapping = CreateBinaryMapping(typeName, maxLength); + + Assert.True(typeMapping.IsFixedLength); + Assert.Equal(DbType.Binary, typeMapping.DbType); + Assert.Equal("binary(100)", typeMapping.StoreType); + + var parameter = typeMapping.CreateParameter(new TestCommand(), "Name", new byte[8001]); + Assert.Equal(DbType.Binary, parameter.DbType); + Assert.Equal(-1, parameter.Size); + } + + private RelationalTypeMapping CreateBinaryMapping(string typeName, int? maxLength) + { + var property = CreateEntityType().AddProperty("MyBinaryProp", typeof(byte[])); + + if (typeName != null) + { + property.SetColumnType("binary(100)"); + } + else + { + property.SetIsFixedLength(true); + } + + if (maxLength != null) + { + property.SetMaxLength(maxLength); + } + + return CreateTypeMapper().GetMapping(property); + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_key_SQL_Server_binary_mapping(bool? fixedLength) { var property = CreateEntityType().AddProperty("MyProp", typeof(byte[])); property.IsNullable = false; + property.SetIsFixedLength(fixedLength); property.DeclaringEntityType.SetPrimaryKey(property); var typeMapping = CreateTypeMapper().GetMapping(property); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("varbinary(900)", typeMapping.StoreType); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(900, typeMapping.CreateParameter(new TestCommand(), "Name", new byte[3]).Size); } - [ConditionalFact] - public void Does_foreign_key_SQL_Server_binary_mapping() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_foreign_key_SQL_Server_binary_mapping(bool? fixedLength) { var property = CreateEntityType().AddProperty("MyProp", typeof(byte[])); property.IsNullable = false; + property.SetIsFixedLength(fixedLength); var fkProperty = property.DeclaringEntityType.AddProperty("FK", typeof(byte[])); var pk = property.DeclaringEntityType.SetPrimaryKey(property); property.DeclaringEntityType.AddForeignKey(fkProperty, pk, property.DeclaringEntityType); var typeMapping = CreateTypeMapper().GetMapping(fkProperty); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("varbinary(900)", typeMapping.StoreType); Assert.Equal(900, typeMapping.CreateParameter(new TestCommand(), "Name", new byte[3]).Size); } - [ConditionalFact] - public void Does_required_foreign_key_SQL_Server_binary_mapping() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_required_foreign_key_SQL_Server_binary_mapping(bool? fixedLength) { var property = CreateEntityType().AddProperty("MyProp", typeof(byte[])); property.IsNullable = false; + property.SetIsFixedLength(fixedLength); var fkProperty = property.DeclaringEntityType.AddProperty("FK", typeof(byte[])); var pk = property.DeclaringEntityType.SetPrimaryKey(property); property.DeclaringEntityType.AddForeignKey(fkProperty, pk, property.DeclaringEntityType); fkProperty.IsNullable = false; var typeMapping = CreateTypeMapper().GetMapping(fkProperty); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("varbinary(900)", typeMapping.StoreType); Assert.Equal(900, typeMapping.CreateParameter(new TestCommand(), "Name", new byte[3]).Size); } - [ConditionalFact] - public void Does_indexed_column_SQL_Server_binary_mapping() + [ConditionalTheory] + [InlineData(false)] + [InlineData(null)] + public void Does_indexed_column_SQL_Server_binary_mapping(bool? fixedLength) { var entityType = CreateEntityType(); var property = entityType.AddProperty("MyProp", typeof(byte[])); + property.SetIsFixedLength(fixedLength); entityType.AddIndex(property); var typeMapping = CreateTypeMapper().GetMapping(property); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("varbinary(900)", typeMapping.StoreType); @@ -555,6 +863,7 @@ public void Does_non_key_SQL_Server_rowversion_mapping() Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("rowversion", typeMapping.StoreType); Assert.Equal(8, typeMapping.Size); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(8, typeMapping.CreateParameter(new TestCommand(), "Name", new byte[8]).Size); } @@ -571,6 +880,7 @@ public void Does_non_key_SQL_Server_required_rowversion_mapping() Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("rowversion", typeMapping.StoreType); Assert.Equal(8, typeMapping.Size); + Assert.False(typeMapping.IsFixedLength); Assert.Equal(8, typeMapping.CreateParameter(new TestCommand(), "Name", new byte[8]).Size); } @@ -583,6 +893,7 @@ public void Does_not_do_rowversion_mapping_for_non_computed_concurrency_tokens() var typeMapping = CreateTypeMapper().GetMapping(property); Assert.Equal(DbType.Binary, typeMapping.DbType); + Assert.False(typeMapping.IsFixedLength); Assert.Equal("varbinary(max)", typeMapping.StoreType); } @@ -590,7 +901,8 @@ private RelationalTypeMapping GetTypeMapping( Type propertyType, bool? nullable = null, int? maxLength = null, - bool? unicode = null) + bool? unicode = null, + bool? fixedLength = null) { var property = CreateEntityType().AddProperty("MyProp", propertyType); @@ -609,6 +921,11 @@ private RelationalTypeMapping GetTypeMapping( property.SetIsUnicode(unicode); } + if (fixedLength.HasValue) + { + property.SetIsFixedLength(fixedLength); + } + return CreateTypeMapper().GetMapping(property); } @@ -653,62 +970,62 @@ public void Throws_for_unrecognized_property_types() } [ConditionalTheory] - [InlineData("bigint", typeof(long), null, false)] - [InlineData("binary varying(333)", typeof(byte[]), 333, false)] - [InlineData("binary varying(max)", typeof(byte[]), null, false)] - [InlineData("binary(333)", typeof(byte[]), 333, false)] - [InlineData("bit", typeof(bool), null, false)] - [InlineData("char varying(333)", typeof(string), 333, false)] - [InlineData("char varying(max)", typeof(string), null, false)] - [InlineData("char(333)", typeof(string), 333, false)] - [InlineData("character varying(333)", typeof(string), 333, false)] - [InlineData("character varying(max)", typeof(string), null, false)] - [InlineData("character(333)", typeof(string), 333, false)] - [InlineData("date", typeof(DateTime), null, false)] - [InlineData("datetime", typeof(DateTime), null, false)] - [InlineData("datetime2", typeof(DateTime), null, false)] - [InlineData("datetimeoffset", typeof(DateTimeOffset), null, false)] - [InlineData("dec", typeof(decimal), null, false, "dec(18,2)")] - [InlineData("decimal", typeof(decimal), null, false, "decimal(18,2)")] - [InlineData("float", typeof(double), null, false)] // This is correct. SQL Server 'float' type maps to C# double - [InlineData("float(10,8)", typeof(double), null, false)] - [InlineData("image", typeof(byte[]), null, false)] - [InlineData("int", typeof(int), null, false)] - [InlineData("money", typeof(decimal), null, false)] - [InlineData("national char varying(333)", typeof(string), 333, true)] - [InlineData("national char varying(max)", typeof(string), null, true)] - [InlineData("national character varying(333)", typeof(string), 333, true)] - [InlineData("national character varying(max)", typeof(string), null, true)] - [InlineData("national character(333)", typeof(string), 333, true)] - [InlineData("nchar(333)", typeof(string), 333, true)] - [InlineData("ntext", typeof(string), null, true)] - [InlineData("numeric", typeof(decimal), null, false, "numeric(18,2)")] - [InlineData("nvarchar(333)", typeof(string), 333, true)] - [InlineData("nvarchar(max)", typeof(string), null, true)] - [InlineData("real", typeof(float), null, false)] - [InlineData("rowversion", typeof(byte[]), 8, false)] - [InlineData("smalldatetime", typeof(DateTime), null, false)] - [InlineData("smallint", typeof(short), null, false)] - [InlineData("smallmoney", typeof(decimal), null, false)] - [InlineData("text", typeof(string), null, false)] - [InlineData("time", typeof(TimeSpan), null, false)] - [InlineData( - "timestamp", typeof(byte[]), 8, false)] // note: rowversion is a synonym but SQL Server stores the data type as 'timestamp' - [InlineData("tinyint", typeof(byte), null, false)] - [InlineData("uniqueidentifier", typeof(Guid), null, false)] - [InlineData("varbinary(333)", typeof(byte[]), 333, false)] - [InlineData("varbinary(max)", typeof(byte[]), null, false)] - [InlineData("VarCHaR(333)", typeof(string), 333, false)] // case-insensitive - [InlineData("varchar(333)", typeof(string), 333, false)] - [InlineData("varchar(max)", typeof(string), null, false)] - [InlineData("VARCHAR(max)", typeof(string), null, false, "VARCHAR(max)")] - public void Can_map_by_type_name(string typeName, Type clrType, int? size, bool unicode, string expectedType = null) + [InlineData("bigint", typeof(long), null, false, false)] + [InlineData("binary varying(333)", typeof(byte[]), 333, false, false)] + [InlineData("binary varying(max)", typeof(byte[]), null, false, false)] + [InlineData("binary(333)", typeof(byte[]), 333, false, true)] + [InlineData("bit", typeof(bool), null, false, false)] + [InlineData("char varying(333)", typeof(string), 333, false, false)] + [InlineData("char varying(max)", typeof(string), null, false, false)] + [InlineData("char(333)", typeof(string), 333, false, true)] + [InlineData("character varying(333)", typeof(string), 333, false, false)] + [InlineData("character varying(max)", typeof(string), null, false, false)] + [InlineData("character(333)", typeof(string), 333, false, true)] + [InlineData("date", typeof(DateTime), null, false, false)] + [InlineData("datetime", typeof(DateTime), null, false, false)] + [InlineData("datetime2", typeof(DateTime), null, false, false)] + [InlineData("datetimeoffset", typeof(DateTimeOffset), null, false, false)] + [InlineData("dec", typeof(decimal), null, false, false, "dec(18,2)")] + [InlineData("decimal", typeof(decimal), null, false, false, "decimal(18,2)")] + [InlineData("float", typeof(double), null, false, false)] // This is correct. SQL Server 'float' type maps to C# double + [InlineData("float(10,8)", typeof(double), null, false, false)] + [InlineData("image", typeof(byte[]), null, false, false)] + [InlineData("int", typeof(int), null, false, false)] + [InlineData("money", typeof(decimal), null, false, false)] + [InlineData("national char varying(333)", typeof(string), 333, true, false)] + [InlineData("national char varying(max)", typeof(string), null, true, false)] + [InlineData("national character varying(333)", typeof(string), 333, true, false)] + [InlineData("national character varying(max)", typeof(string), null, true, false)] + [InlineData("national character(333)", typeof(string), 333, true, true)] + [InlineData("nchar(333)", typeof(string), 333, true, true)] + [InlineData("ntext", typeof(string), null, true, false)] + [InlineData("numeric", typeof(decimal), null, false, false, "numeric(18,2)")] + [InlineData("nvarchar(333)", typeof(string), 333, true, false)] + [InlineData("nvarchar(max)", typeof(string), null, true, false)] + [InlineData("real", typeof(float), null, false, false)] + [InlineData("rowversion", typeof(byte[]), 8, false, false)] + [InlineData("smalldatetime", typeof(DateTime), null, false, false)] + [InlineData("smallint", typeof(short), null, false, false)] + [InlineData("smallmoney", typeof(decimal), null, false, false)] + [InlineData("text", typeof(string), null, false, false)] + [InlineData("time", typeof(TimeSpan), null, false, false)] + [InlineData("timestamp", typeof(byte[]), 8, false, false)] // note: rowversion is a synonym stored the data type as 'timestamp' + [InlineData("tinyint", typeof(byte), null, false, false)] + [InlineData("uniqueidentifier", typeof(Guid), null, false, false)] + [InlineData("varbinary(333)", typeof(byte[]), 333, false, false)] + [InlineData("varbinary(max)", typeof(byte[]), null, false, false)] + [InlineData("VarCHaR(333)", typeof(string), 333, false, false)] // case-insensitive + [InlineData("varchar(333)", typeof(string), 333, false, false)] + [InlineData("varchar(max)", typeof(string), null, false, false)] + [InlineData("VARCHAR(max)", typeof(string), null, false, false, "VARCHAR(max)")] + public void Can_map_by_type_name(string typeName, Type clrType, int? size, bool unicode, bool fixedLength, string expectedType = null) { var mapping = CreateTypeMapper().FindMapping(typeName); Assert.Equal(clrType, mapping.ClrType); Assert.Equal(size, mapping.Size); Assert.Equal(unicode, mapping.IsUnicode); + Assert.Equal(fixedLength, mapping.IsFixedLength); Assert.Equal(expectedType ?? typeName, mapping.StoreType); } @@ -797,6 +1114,7 @@ public void Can_map_string_base_type_name_and_size(string typeName) Assert.Same(typeof(string), mapping.ClrType); Assert.Equal(2018, mapping.Size); Assert.Equal(typeName.StartsWith("n", StringComparison.OrdinalIgnoreCase), mapping.IsUnicode); + Assert.Equal(typeName.Contains("var", StringComparison.OrdinalIgnoreCase), !mapping.IsFixedLength); Assert.Equal(typeName + "(2018)", mapping.StoreType); } @@ -818,6 +1136,7 @@ public void Can_map_binary_base_type_name_and_size(string typeName) Assert.Same(typeof(byte[]), mapping.ClrType); Assert.Equal(2018, mapping.Size); + Assert.Equal(typeName.Contains("var", StringComparison.OrdinalIgnoreCase), !mapping.IsFixedLength); Assert.Equal(typeName + "(2018)", mapping.StoreType); } @@ -844,32 +1163,32 @@ public void Key_with_store_type_is_picked_up_by_FK() } [ConditionalFact] - public void String_key_with_max_length_is_picked_up_by_FK() + public void String_key_with_max_fixed_length_is_picked_up_by_FK() { var model = CreateModel(); var mapper = CreateTypeMapper(); Assert.Equal( - "nvarchar(200)", + "nchar(200)", mapper.GetMapping(model.FindEntityType(typeof(MyRelatedType1)).FindProperty("Id")).StoreType); Assert.Equal( - "nvarchar(200)", + "nchar(200)", mapper.GetMapping(model.FindEntityType(typeof(MyRelatedType2)).FindProperty("Relationship1Id")).StoreType); } [ConditionalFact] - public void Binary_key_with_max_length_is_picked_up_by_FK() + public void Binary_key_with_max_fixed_length_is_picked_up_by_FK() { var model = CreateModel(); var mapper = CreateTypeMapper(); Assert.Equal( - "varbinary(100)", + "binary(100)", mapper.GetMapping(model.FindEntityType(typeof(MyRelatedType2)).FindProperty("Id")).StoreType); Assert.Equal( - "varbinary(100)", + "binary(100)", mapper.GetMapping(model.FindEntityType(typeof(MyRelatedType3)).FindProperty("Relationship1Id")).StoreType); } @@ -910,11 +1229,11 @@ public void String_FK_max_length_is_preferred_if_specified() var mapper = CreateTypeMapper(); Assert.Equal( - "nvarchar(200)", + "nchar(200)", mapper.GetMapping(model.FindEntityType(typeof(MyRelatedType1)).FindProperty("Id")).StoreType); Assert.Equal( - "nvarchar(787)", + "nchar(787)", mapper.GetMapping(model.FindEntityType(typeof(MyRelatedType2)).FindProperty("Relationship2Id")).StoreType); } @@ -925,11 +1244,11 @@ public void Binary_FK_max_length_is_preferred_if_specified() var mapper = CreateTypeMapper(); Assert.Equal( - "varbinary(100)", + "binary(100)", mapper.GetMapping(model.FindEntityType(typeof(MyRelatedType2)).FindProperty("Id")).StoreType); Assert.Equal( - "varbinary(767)", + "binary(767)", mapper.GetMapping(model.FindEntityType(typeof(MyRelatedType3)).FindProperty("Relationship2Id")).StoreType); }