Skip to content

Commit 34fa402

Browse files
ajcvickersbricelam
andauthored
Stop appending facets to SQL Server type aliases (#24517)
Stop appending facets to SQL Server type aliases Fixes #24365 Co-authored-by: Brice Lambson <[email protected]>
1 parent b830d74 commit 34fa402

6 files changed

+249
-14
lines changed

src/EFCore.SqlServer/Storage/Internal/SqlServerDateTimeOffsetTypeMapping.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@ public class SqlServerDateTimeOffsetTypeMapping : DateTimeOffsetTypeMapping
3838
/// </summary>
3939
public SqlServerDateTimeOffsetTypeMapping(
4040
string storeType,
41-
DbType? dbType = System.Data.DbType.DateTimeOffset)
41+
DbType? dbType = System.Data.DbType.DateTimeOffset,
42+
StoreTypePostfix storeTypePostfix = StoreTypePostfix.Precision)
4243
: base(
4344
new RelationalTypeMappingParameters(
4445
new CoreTypeMappingParameters(typeof(DateTimeOffset)),
4546
storeType,
46-
StoreTypePostfix.Precision,
47+
storeTypePostfix,
4748
dbType))
4849
{
4950
}

src/EFCore.SqlServer/Storage/Internal/SqlServerDateTimeTypeMapping.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@ public class SqlServerDateTimeTypeMapping : DateTimeTypeMapping
4343
/// </summary>
4444
public SqlServerDateTimeTypeMapping(
4545
string storeType,
46-
DbType? dbType = null)
46+
DbType? dbType = null,
47+
StoreTypePostfix storeTypePostfix = StoreTypePostfix.Precision)
4748
: base(
4849
new RelationalTypeMappingParameters(
4950
new CoreTypeMappingParameters(typeof(DateTime)),
5051
storeType,
51-
StoreTypePostfix.Precision,
52+
storeTypePostfix,
5253
dbType))
5354
{
5455
}

src/EFCore.SqlServer/Storage/Internal/SqlServerDoubleTypeMapping.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@ public class SqlServerDoubleTypeMapping : DoubleTypeMapping
2424
/// </summary>
2525
public SqlServerDoubleTypeMapping(
2626
string storeType,
27-
DbType? dbType = null)
27+
DbType? dbType = null,
28+
StoreTypePostfix storeTypePostfix = StoreTypePostfix.Precision)
2829
: base(
2930
new RelationalTypeMappingParameters(
3031
new CoreTypeMappingParameters(typeof(double)),
3132
storeType,
32-
StoreTypePostfix.Precision,
33+
storeTypePostfix,
3334
dbType))
3435
{
3536
}

src/EFCore.SqlServer/Storage/Internal/SqlServerFloatTypeMapping.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@ public class SqlServerFloatTypeMapping : FloatTypeMapping
2323
/// </summary>
2424
public SqlServerFloatTypeMapping(
2525
string storeType,
26-
DbType? dbType = null)
26+
DbType? dbType = null,
27+
StoreTypePostfix storeTypePostfix = StoreTypePostfix.Precision)
2728
: base(
2829
new RelationalTypeMappingParameters(
2930
new CoreTypeMappingParameters(typeof(float)),
3031
storeType,
31-
StoreTypePostfix.Precision,
32+
storeTypePostfix,
3233
dbType))
3334
{
3435
}

src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs

+42-6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ private readonly RelationalTypeMapping _sqlVariant
3333
private readonly FloatTypeMapping _real
3434
= new SqlServerFloatTypeMapping("real");
3535

36+
private readonly FloatTypeMapping _realAlias
37+
= new SqlServerFloatTypeMapping("placeholder", storeTypePostfix: StoreTypePostfix.None);
38+
3639
private readonly ByteTypeMapping _byte
3740
= new SqlServerByteTypeMapping("tinyint", DbType.Byte);
3841

@@ -104,20 +107,30 @@ private readonly SqlServerDateTimeTypeMapping _datetime
104107
private readonly SqlServerDateTimeTypeMapping _datetime2
105108
= new("datetime2", DbType.DateTime2);
106109

110+
private readonly SqlServerDateTimeTypeMapping _datetime2Alias
111+
= new("placeholder", DbType.DateTime2, StoreTypePostfix.None);
112+
107113
private readonly DoubleTypeMapping _double
108114
= new SqlServerDoubleTypeMapping("float");
109115

116+
private readonly DoubleTypeMapping _doubleAlias
117+
= new SqlServerDoubleTypeMapping("placeholder", storeTypePostfix: StoreTypePostfix.None);
118+
110119
private readonly SqlServerDateTimeOffsetTypeMapping _datetimeoffset
111-
#pragma warning disable CS8620 // TODO: Follow up. Possibly dotnet/roslyn#50311
112-
= new("datetimeoffset");
113-
#pragma warning restore CS8620
120+
= new("datetimeoffset", DbType.DateTimeOffset);
121+
122+
private readonly SqlServerDateTimeOffsetTypeMapping _datetimeoffsetAlias
123+
= new("placeholder", DbType.DateTimeOffset, StoreTypePostfix.None);
114124

115125
private readonly GuidTypeMapping _uniqueidentifier
116126
= new("uniqueidentifier", DbType.Guid);
117127

118128
private readonly DecimalTypeMapping _decimal
119129
= new SqlServerDecimalTypeMapping("decimal");
120130

131+
private readonly DecimalTypeMapping _decimalAlias
132+
= new SqlServerDecimalTypeMapping("placeholder", precision: 18, scale: 2, storeTypePostfix: StoreTypePostfix.None);
133+
121134
private readonly DecimalTypeMapping _decimal182
122135
= new SqlServerDecimalTypeMapping("decimal(18, 2)", precision: 18, scale: 2);
123136

@@ -132,6 +145,8 @@ private readonly SqlServerStringTypeMapping _xml
132145

133146
private readonly Dictionary<Type, RelationalTypeMapping> _clrTypeMappings;
134147

148+
private readonly Dictionary<Type, RelationalTypeMapping> _clrNoFacetTypeMappings;
149+
135150
private readonly Dictionary<string, RelationalTypeMapping> _storeTypeMappings;
136151

137152
/// <summary>
@@ -162,6 +177,16 @@ public SqlServerTypeMappingSource(
162177
{ typeof(TimeSpan), _time }
163178
};
164179

180+
_clrNoFacetTypeMappings
181+
= new Dictionary<Type, RelationalTypeMapping>
182+
{
183+
{ typeof(DateTime), _datetime2Alias },
184+
{ typeof(double), _doubleAlias },
185+
{ typeof(DateTimeOffset), _datetimeoffsetAlias },
186+
{ typeof(float), _realAlias },
187+
{ typeof(decimal), _decimalAlias }
188+
};
189+
165190
_storeTypeMappings
166191
= new Dictionary<string, RelationalTypeMapping>(StringComparer.OrdinalIgnoreCase)
167192
{
@@ -256,6 +281,12 @@ public SqlServerTypeMappingSource(
256281
? mapping
257282
: null;
258283
}
284+
285+
if (clrType != null
286+
&& _clrNoFacetTypeMappings.TryGetValue(clrType, out mapping))
287+
{
288+
return mapping;
289+
}
259290
}
260291

261292
if (clrType != null)
@@ -277,7 +308,8 @@ public SqlServerTypeMappingSource(
277308
size = isFixedLength ? maxSize : (int?)null;
278309
}
279310

280-
if (size == null)
311+
if (size == null
312+
&& storeTypeName == null)
281313
{
282314
return isAnsi
283315
? isFixedLength
@@ -291,7 +323,8 @@ public SqlServerTypeMappingSource(
291323
return new SqlServerStringTypeMapping(
292324
unicode: !isAnsi,
293325
size: size,
294-
fixedLength: isFixedLength);
326+
fixedLength: isFixedLength,
327+
storeTypePostfix: storeTypeName == null ? StoreTypePostfix.Size : StoreTypePostfix.None);
295328
}
296329

297330
if (clrType == typeof(byte[]))
@@ -311,7 +344,10 @@ public SqlServerTypeMappingSource(
311344

312345
return size == null
313346
? _variableLengthMaxBinary
314-
: new SqlServerByteArrayTypeMapping(size: size, fixedLength: isFixedLength);
347+
: new SqlServerByteArrayTypeMapping(
348+
size: size,
349+
fixedLength: isFixedLength,
350+
storeTypePostfix: storeTypeName == null ? StoreTypePostfix.Size : StoreTypePostfix.None);
315351
}
316352
}
317353

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
#nullable enable
5+
6+
using System;
7+
using System.ComponentModel.DataAnnotations.Schema;
8+
using System.Linq;
9+
using Microsoft.EntityFrameworkCore.Metadata;
10+
using Microsoft.EntityFrameworkCore.TestUtilities;
11+
using Xunit;
12+
13+
namespace Microsoft.EntityFrameworkCore
14+
{
15+
public class SqlServerTypeAliasTest : IClassFixture<SqlServerFixture>
16+
{
17+
private const string DatabaseName = "SqlServerTypeAliasTest";
18+
19+
protected SqlServerFixture Fixture { get; }
20+
21+
public SqlServerTypeAliasTest(SqlServerFixture fixture)
22+
{
23+
Fixture = fixture;
24+
}
25+
26+
[ConditionalFact]
27+
public void Can_create_database_with_alias_columns()
28+
{
29+
using var testDatabase = SqlServerTestStore.CreateInitialized(DatabaseName);
30+
var options = Fixture.CreateOptions(testDatabase);
31+
32+
using (var context = new TypeAliasContext(options))
33+
{
34+
context.Database.ExecuteSqlRaw(
35+
@"
36+
CREATE TYPE datetimeAlias FROM datetime2(6);
37+
CREATE TYPE datetimeoffsetAlias FROM datetimeoffset(6);
38+
CREATE TYPE decimalAlias FROM decimal(10, 6);
39+
CREATE TYPE doubleAlias FROM float(26);
40+
CREATE TYPE floatAlias FROM real;
41+
CREATE TYPE binaryAlias FROM varbinary(50);
42+
CREATE TYPE stringAlias FROM nvarchar(50);");
43+
44+
var model = context.Model;
45+
46+
var aliasEntityType = model.FindEntityType(typeof(TypeAliasEntity));
47+
Assert.Equal("datetimeAlias", GetColumnType(aliasEntityType!, nameof(TypeAliasEntity.DateTimeAlias)));
48+
Assert.Equal("datetimeoffsetAlias", GetColumnType(aliasEntityType!, nameof(TypeAliasEntity.DateTimeOffsetAlias)));
49+
Assert.Equal("decimalAlias", GetColumnType(aliasEntityType!, nameof(TypeAliasEntity.DecimalAlias)));
50+
Assert.Equal("doubleAlias", GetColumnType(aliasEntityType!, nameof(TypeAliasEntity.DoubleAlias)));
51+
Assert.Equal("floatAlias", GetColumnType(aliasEntityType!, nameof(TypeAliasEntity.FloatAlias)));
52+
Assert.Equal("binaryAlias", GetColumnType(aliasEntityType!, nameof(TypeAliasEntity.BinaryAlias)));
53+
Assert.Equal("stringAlias", GetColumnType(aliasEntityType!, nameof(TypeAliasEntity.StringAlias)));
54+
55+
var facetedAliasEntityType = model.FindEntityType(typeof(TypeAliasEntityWithFacets));
56+
Assert.Equal("datetimeAlias", GetColumnType(facetedAliasEntityType!, nameof(TypeAliasEntityWithFacets.DateTimeAlias)));
57+
Assert.Equal("datetimeoffsetAlias", GetColumnType(facetedAliasEntityType!, nameof(TypeAliasEntityWithFacets.DateTimeOffsetAlias)));
58+
Assert.Equal("decimalAlias", GetColumnType(facetedAliasEntityType!, nameof(TypeAliasEntityWithFacets.DecimalAlias)));
59+
Assert.Equal("doubleAlias", GetColumnType(facetedAliasEntityType!, nameof(TypeAliasEntityWithFacets.DoubleAlias)));
60+
Assert.Equal("floatAlias", GetColumnType(facetedAliasEntityType!, nameof(TypeAliasEntityWithFacets.FloatAlias)));
61+
Assert.Equal("binaryAlias", GetColumnType(facetedAliasEntityType!, nameof(TypeAliasEntityWithFacets.BinaryAlias)));
62+
Assert.Equal("stringAlias", GetColumnType(facetedAliasEntityType!, nameof(TypeAliasEntityWithFacets.StringAlias)));
63+
64+
context.Database.EnsureCreatedResiliently();
65+
66+
context.AddRange(
67+
new TypeAliasEntity
68+
{
69+
DateTimeAlias = new DateTime(),
70+
DateTimeOffsetAlias = new DateTimeOffset(),
71+
DecimalAlias = 3.14159m,
72+
DoubleAlias = 3.14159,
73+
FloatAlias = 3.14159f,
74+
BinaryAlias = new byte[] { 0, 1, 2, 3 },
75+
StringAlias = "Rodrigo y Gabriela"
76+
},
77+
new TypeAliasEntityWithFacets
78+
{
79+
DateTimeAlias = new DateTime(),
80+
DateTimeOffsetAlias = new DateTimeOffset(),
81+
DecimalAlias = 3.14159m,
82+
DoubleAlias = 3.14159,
83+
FloatAlias = 3.14159f,
84+
BinaryAlias = new byte[] { 0, 1, 2, 3 },
85+
StringAlias = "Mettavolution"
86+
});
87+
88+
context.SaveChanges();
89+
}
90+
91+
using (var context = new TypeAliasContext(options))
92+
{
93+
var entity = context.Set<TypeAliasEntity>().OrderByDescending(e => e.Id).First();
94+
95+
Assert.Equal(new DateTime(), entity.DateTimeAlias);
96+
Assert.Equal(new DateTimeOffset(), entity.DateTimeOffsetAlias);
97+
Assert.Equal(3.14m, entity.DecimalAlias);
98+
Assert.Equal(3.14159, entity.DoubleAlias);
99+
Assert.Equal(3.14159f, entity.FloatAlias);
100+
Assert.Equal(new byte[] { 0, 1, 2, 3 }, entity.BinaryAlias);
101+
Assert.Equal("Rodrigo y Gabriela", entity.StringAlias);
102+
103+
var entityWithFacets = context.Set<TypeAliasEntityWithFacets>().OrderByDescending(e => e.Id).First();
104+
105+
Assert.Equal(new DateTime(), entityWithFacets.DateTimeAlias);
106+
Assert.Equal(new DateTimeOffset(), entityWithFacets.DateTimeOffsetAlias);
107+
Assert.Equal(3.14159m, entityWithFacets.DecimalAlias);
108+
Assert.Equal(3.14159, entityWithFacets.DoubleAlias);
109+
Assert.Equal(3.14159f, entityWithFacets.FloatAlias);
110+
Assert.Equal(new byte[] { 0, 1, 2, 3 }, entityWithFacets.BinaryAlias);
111+
Assert.Equal("Mettavolution", entityWithFacets.StringAlias);
112+
}
113+
114+
string GetColumnType(IEntityType entityType, string propertyName)
115+
=> entityType!.FindProperty(propertyName)!.GetColumnType(new StoreObjectIdentifier());
116+
}
117+
118+
private class TypeAliasContext : DbContext
119+
{
120+
public TypeAliasContext(DbContextOptions options)
121+
: base(options)
122+
{
123+
}
124+
125+
protected override void OnModelCreating(ModelBuilder modelBuilder)
126+
{
127+
modelBuilder.Entity<TypeAliasEntity>();
128+
modelBuilder.Entity<TypeAliasEntityWithFacets>(
129+
b =>
130+
{
131+
b.Property(e => e.DateTimeAlias).HasPrecision(6);
132+
b.Property(e => e.DateTimeOffsetAlias).HasPrecision(6);
133+
b.Property(e => e.DecimalAlias).HasPrecision(10, 6);
134+
b.Property(e => e.DoubleAlias).HasPrecision(26);
135+
b.Property(e => e.BinaryAlias).HasMaxLength(50);
136+
b.Property(e => e.StringAlias).HasMaxLength(50);
137+
});
138+
}
139+
}
140+
141+
private class TypeAliasEntity
142+
{
143+
public int Id { get; set; }
144+
145+
[Column(TypeName = "datetimeAlias")]
146+
public DateTime DateTimeAlias { get; set; }
147+
148+
[Column(TypeName = "datetimeoffsetAlias")]
149+
public DateTimeOffset DateTimeOffsetAlias { get; set; }
150+
151+
[Column(TypeName = "decimalAlias")]
152+
public decimal DecimalAlias { get; set; }
153+
154+
[Column(TypeName = "doubleAlias")]
155+
public double DoubleAlias { get; set; }
156+
157+
[Column(TypeName = "floatAlias")]
158+
public float FloatAlias { get; set; }
159+
160+
[Column(TypeName = "binaryAlias")]
161+
public byte[]? BinaryAlias { get; set; }
162+
163+
[Column(TypeName = "stringAlias")]
164+
public string? StringAlias { get; set; }
165+
}
166+
167+
private class TypeAliasEntityWithFacets
168+
{
169+
public int Id { get; set; }
170+
171+
[Column(TypeName = "datetimeAlias")]
172+
public DateTime DateTimeAlias { get; set; }
173+
174+
[Column(TypeName = "datetimeoffsetAlias")]
175+
public DateTimeOffset DateTimeOffsetAlias { get; set; }
176+
177+
[Column(TypeName = "decimalAlias")]
178+
public decimal DecimalAlias { get; set; }
179+
180+
[Column(TypeName = "doubleAlias")]
181+
public double DoubleAlias { get; set; }
182+
183+
[Column(TypeName = "floatAlias")]
184+
public float FloatAlias { get; set; }
185+
186+
[Column(TypeName = "binaryAlias")]
187+
public byte[]? BinaryAlias { get; set; }
188+
189+
[Column(TypeName = "stringAlias")]
190+
public string? StringAlias { get; set; }
191+
}
192+
}
193+
}
194+
195+
#nullable restore

0 commit comments

Comments
 (0)