Skip to content

Commit 87d777c

Browse files
committed
Add SQL Server spatial type tests
And improve the type tests a bit
1 parent c020d82 commit 87d777c

File tree

8 files changed

+674
-57
lines changed

8 files changed

+674
-57
lines changed

test/EFCore.Relational.Specification.Tests/RelationalTypeTestBase.cs

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ public abstract class RelationalTypeTestBase<T, TFixture>(TFixture fixture) : Ty
77
where TFixture : RelationalTypeTestBase<T, TFixture>.RelationalTypeTestFixture
88
where T : notnull
99
{
10+
public RelationalTypeTestBase(TFixture fixture, ITestOutputHelper testOutputHelper)
11+
: this(fixture)
12+
{
13+
Fixture.TestSqlLoggerFactory.Clear();
14+
Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
15+
}
16+
1017
[ConditionalFact]
1118
public virtual async Task ExecuteUpdate_within_json_to_parameter()
1219
=> await TestHelpers.ExecuteWithStrategyInTransactionAsync(
@@ -15,8 +22,12 @@ public virtual async Task ExecuteUpdate_within_json_to_parameter()
1522
async context =>
1623
{
1724
await context.Set<JsonTypeEntity>().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, e => Fixture.OtherValue));
18-
var result = await context.Set<JsonTypeEntity>().Where(e => e.Id == 1).SingleAsync();
19-
Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer);
25+
26+
using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents())
27+
{
28+
var result = await context.Set<JsonTypeEntity>().Where(e => e.Id == 1).SingleAsync();
29+
Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer);
30+
}
2031
});
2132

2233
[ConditionalFact]
@@ -33,8 +44,12 @@ public virtual async Task ExecuteUpdate_within_json_to_constant()
3344
parameter);
3445

3546
await context.Set<JsonTypeEntity>().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, valueExpression));
36-
var result = await context.Set<JsonTypeEntity>().Where(e => e.Id == 1).SingleAsync();
37-
Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer);
47+
48+
using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents())
49+
{
50+
var result = await context.Set<JsonTypeEntity>().Where(e => e.Id == 1).SingleAsync();
51+
Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer);
52+
}
3853
});
3954

4055
[ConditionalFact]
@@ -45,8 +60,12 @@ public virtual async Task ExecuteUpdate_within_json_to_another_json_property()
4560
async context =>
4661
{
4762
await context.Set<JsonTypeEntity>().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, e => e.JsonContainer.OtherValue));
48-
var result = await context.Set<JsonTypeEntity>().Where(e => e.Id == 1).SingleAsync();
49-
Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer);
63+
64+
using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents())
65+
{
66+
var result = await context.Set<JsonTypeEntity>().Where(e => e.Id == 1).SingleAsync();
67+
Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer);
68+
}
5069
});
5170

5271
[ConditionalFact]
@@ -57,8 +76,12 @@ public virtual async Task ExecuteUpdate_within_json_to_nonjson_column()
5776
async context =>
5877
{
5978
await context.Set<JsonTypeEntity>().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, e => e.OtherValue));
60-
var result = await context.Set<JsonTypeEntity>().Where(e => e.Id == 1).SingleAsync();
61-
Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer);
79+
80+
using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents())
81+
{
82+
var result = await context.Set<JsonTypeEntity>().Where(e => e.Id == 1).SingleAsync();
83+
Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer);
84+
}
6285
});
6386

6487
protected class JsonTypeEntity
@@ -77,17 +100,37 @@ public class JsonContainer
77100
public required T OtherValue { get; set; }
78101
}
79102

80-
public abstract class RelationalTypeTestFixture(T value, T otherValue)
81-
: TypeTestFixture(value, otherValue)
103+
protected void AssertSql(params string[] expected)
104+
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
105+
106+
protected void AssertExecuteUpdateSql(params string[] expected)
107+
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
108+
109+
public abstract class RelationalTypeTestFixture : TypeTestFixture, ITestSqlLoggerFactory
82110
{
111+
public virtual string? StoreType => null;
112+
83113
protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
84114
{
85115
base.OnModelCreating(modelBuilder, context);
86116

117+
modelBuilder.Entity<TypeEntity>(b =>
118+
{
119+
b.Property(e => e.Value).HasColumnType(StoreType);
120+
b.Property(e => e.OtherValue).HasColumnType(StoreType);
121+
});
122+
87123
modelBuilder.Entity<JsonTypeEntity>(b =>
88124
{
89125
modelBuilder.Entity<JsonTypeEntity>().Property(e => e.Id).ValueGeneratedNever();
90-
b.ComplexProperty(e => e.JsonContainer, cb => cb.ToJson());
126+
127+
b.ComplexProperty(e => e.JsonContainer, jc =>
128+
{
129+
jc.ToJson();
130+
131+
jc.Property(e => e.Value).HasColumnType(StoreType);
132+
jc.Property(e => e.OtherValue).HasColumnType(StoreType);
133+
});
91134
});
92135
}
93136

@@ -122,6 +165,9 @@ protected override async Task SeedAsync(DbContext context)
122165
await context.SaveChangesAsync();
123166
}
124167

168+
public TestSqlLoggerFactory TestSqlLoggerFactory
169+
=> (TestSqlLoggerFactory)ListLoggerFactory;
170+
125171
public virtual void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
126172
=> facade.UseTransaction(transaction.GetDbTransaction());
127173
}

test/EFCore.Specification.Tests/SharedStoreFixtureBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ public virtual async Task InitializeAsync()
7676
_serviceProvider = services.BuildServiceProvider(validateScopes: true);
7777

7878
await TestStore.InitializeAsync(ServiceProvider, CreateContext, c => SeedAsync((TContext)c), CleanAsync);
79+
80+
ListLoggerFactory.Clear();
7981
}
8082

8183
public virtual TContext CreateContext()

test/EFCore.Specification.Tests/TypeTestBase.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public abstract class TypeTestBase<T, TFixture>(TFixture fixture) : IClassFixtur
99
where T : notnull
1010
{
1111
[ConditionalFact]
12-
public async Task Equality_in_query()
12+
public async virtual Task Equality_in_query()
1313
{
1414
await using var context = Fixture.CreateContext();
1515

@@ -28,13 +28,19 @@ protected class TypeEntity
2828

2929
protected TFixture Fixture { get; } = fixture;
3030

31-
public abstract class TypeTestFixture(T value, T otherValue)
32-
: SharedStoreFixtureBase<DbContext>
31+
public abstract class TypeTestFixture : SharedStoreFixtureBase<DbContext>
3332
{
34-
protected override string StoreName => "TypeTest";
33+
/// <summary>
34+
/// The main value used in the tests.
35+
/// </summary>
36+
public abstract T Value { get; }
37+
38+
/// <summary>
39+
/// An additional value that is different from <see cref="Value" />.
40+
/// </summary>
41+
public abstract T OtherValue { get; }
3542

36-
public T Value { get; } = value;
37-
public T OtherValue { get; } = otherValue;
43+
protected override string StoreName => "TypeTest";
3844

3945
public virtual Func<T, T, bool> Comparer { get; } = EqualityComparer<T>.Default.Equals;
4046

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using NetTopologySuite.Geometries;
5+
6+
namespace Microsoft.EntityFrameworkCore.Types.Geography;
7+
8+
public abstract class GeographyTypeTestBase<T, TFixture>(TFixture fixture) : RelationalTypeTestBase<T, TFixture>(fixture)
9+
where T : NetTopologySuite.Geometries.Geometry
10+
where TFixture : GeographyTypeTestBase<T, TFixture>.GeographyTypeFixture
11+
{
12+
// SQL Server doesn't support the equality operator on geography, override to use EqualsTopologically
13+
public override async Task Equality_in_query()
14+
{
15+
await using var context = Fixture.CreateContext();
16+
17+
var result = await context.Set<TypeEntity>().Where(e => e.Value.EqualsTopologically(Fixture.Value)).SingleAsync();
18+
19+
Assert.Equal(Fixture.Value, result.Value, Fixture.Comparer);
20+
}
21+
22+
public override async Task ExecuteUpdate_within_json_to_nonjson_column()
23+
{
24+
// See #36688 for supporting this for SQL Server types other than string/numeric/bool
25+
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => base.ExecuteUpdate_within_json_to_nonjson_column());
26+
Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message);
27+
}
28+
29+
public abstract class GeographyTypeFixture() : RelationalTypeTestFixture
30+
{
31+
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
32+
=> base.AddOptions(builder).UseSqlServer(o => o.UseNetTopologySuite());
33+
34+
protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance;
35+
}
36+
}
37+
38+
public class PointTypeTest(PointTypeTest.PointTypeFixture fixture)
39+
: GeographyTypeTestBase<Point, PointTypeTest.PointTypeFixture>(fixture)
40+
{
41+
public class PointTypeFixture() : GeographyTypeFixture
42+
{
43+
public override Point Value { get; } = new(-122.34877, 47.6233355) { SRID = 4326 };
44+
public override Point OtherValue { get; } = new(-121.7500, 46.2500) { SRID = 4326 };
45+
}
46+
}
47+
48+
public class LineStringTypeTest(LineStringTypeTest.LineStringTypeFixture fixture)
49+
: GeographyTypeTestBase<LineString, LineStringTypeTest.LineStringTypeFixture>(fixture)
50+
{
51+
public class LineStringTypeFixture() : GeographyTypeFixture
52+
{
53+
public override LineString Value { get; } = new(
54+
[
55+
new Coordinate(-122.34877, 47.6233355),
56+
new Coordinate(-122.3308366, 47.5978429)
57+
]) { SRID = 4326 };
58+
59+
public override LineString OtherValue { get; } = new(
60+
[
61+
new Coordinate(-121.5000, 46.9000),
62+
new Coordinate(-121.2000, 46.6500),
63+
new Coordinate(-121.0000, 46.4000)
64+
]) { SRID = 4326 };
65+
}
66+
}
67+
68+
public class PolygonTypeTest(PolygonTypeTest.PolygonTypeFixture fixture)
69+
: GeographyTypeTestBase<Polygon, PolygonTypeTest.PolygonTypeFixture>(fixture)
70+
{
71+
public class PolygonTypeFixture() : GeographyTypeFixture
72+
{
73+
// Simple rectangle
74+
public override Polygon Value { get; } = new(
75+
new LinearRing([
76+
new Coordinate(-122.3500, 47.6200), // NW
77+
new Coordinate(-122.3500, 47.6100), // SW
78+
new Coordinate(-122.3400, 47.6100), // SE
79+
new Coordinate(-122.3400, 47.6200), // NE
80+
new Coordinate(-122.3500, 47.6200) // Close
81+
])) { SRID = 4326 };
82+
83+
// Shifted rectangle; different area so not topologically equal
84+
public override Polygon OtherValue { get; } = new(
85+
new LinearRing([
86+
new Coordinate(-121.3000, 46.6000), // NW
87+
new Coordinate(-121.3000, 46.5900), // SW
88+
new Coordinate(-121.2800, 46.5900), // SE
89+
new Coordinate(-121.2800, 46.6000), // NE
90+
new Coordinate(-121.3000, 46.6000)
91+
])) { SRID = 4326 };
92+
}
93+
}
94+
95+
public class MultiPointTypeTest(MultiPointTypeTest.MultiPointTypeFixture fixture)
96+
: GeographyTypeTestBase<MultiPoint, MultiPointTypeTest.MultiPointTypeFixture>(fixture)
97+
{
98+
public class MultiPointTypeFixture() : GeographyTypeFixture
99+
{
100+
public override MultiPoint Value { get; } = new([
101+
new Point(-122.3500, 47.6200) { SRID = 4326 },
102+
new Point(-122.3450, 47.6150) { SRID = 4326 }
103+
]) { SRID = 4326 };
104+
105+
public override MultiPoint OtherValue { get; } = new([
106+
new Point(-121.9000, 46.9500) { SRID = 4326 },
107+
new Point(-121.5000, 46.6000) { SRID = 4326 },
108+
new Point(-121.2000, 46.3000) { SRID = 4326 }
109+
]) { SRID = 4326 };
110+
}
111+
}
112+
113+
public class MultiLineStringTypeTest(MultiLineStringTypeTest.MultiLineStringTypeFixture fixture)
114+
: GeographyTypeTestBase<MultiLineString, MultiLineStringTypeTest.MultiLineStringTypeFixture>(fixture)
115+
{
116+
public class MultiLineStringTypeFixture() : GeographyTypeFixture
117+
{
118+
public override MultiLineString Value { get; } = new([
119+
new LineString([
120+
new Coordinate(-122.3500, 47.6200),
121+
new Coordinate(-122.3450, 47.6150)
122+
]) { SRID = 4326 },
123+
new LineString([
124+
new Coordinate(-122.3480, 47.6180),
125+
new Coordinate(-122.3420, 47.6130)
126+
]) { SRID = 4326 }
127+
]) { SRID = 4326 };
128+
129+
public override MultiLineString OtherValue { get; } = new([
130+
new LineString([
131+
new Coordinate(-121.9000, 46.9500),
132+
new Coordinate(-121.6000, 46.8200)
133+
]) { SRID = 4326 },
134+
new LineString([
135+
new Coordinate(-121.7000, 46.7800),
136+
new Coordinate(-121.4000, 46.5500)
137+
]) { SRID = 4326 }
138+
]) { SRID = 4326 };
139+
}
140+
}
141+
142+
public class MultiPolygonTypeTest(MultiPolygonTypeTest.MultiPolygonTypeFixture fixture)
143+
: GeographyTypeTestBase<MultiPolygon, MultiPolygonTypeTest.MultiPolygonTypeFixture>(fixture)
144+
{
145+
public class MultiPolygonTypeFixture() : GeographyTypeFixture
146+
{
147+
public override MultiPolygon Value { get; } = new(
148+
[
149+
new Polygon(new LinearRing([
150+
new Coordinate(-122.3500, 47.6200), // NW
151+
new Coordinate(-122.3500, 47.6150), // SW
152+
new Coordinate(-122.3450, 47.6150), // SE
153+
new Coordinate(-122.3450, 47.6200), // NE
154+
new Coordinate(-122.3500, 47.6200)
155+
])) { SRID = 4326 },
156+
new Polygon(new LinearRing([
157+
new Coordinate(-122.3525, 47.6230), // NW
158+
new Coordinate(-122.3525, 47.6215), // SW
159+
new Coordinate(-122.3510, 47.6215), // SE
160+
new Coordinate(-122.3510, 47.6230), // NE
161+
new Coordinate(-122.3525, 47.6230)
162+
])) { SRID = 4326 }
163+
]) { SRID = 4326 };
164+
165+
public override MultiPolygon OtherValue { get; } = new(
166+
[
167+
new Polygon(new LinearRing([
168+
new Coordinate(-121.3600, 46.6250), // NW
169+
new Coordinate(-121.3600, 46.6200), // SW
170+
new Coordinate(-121.3550, 46.6200), // SE
171+
new Coordinate(-121.3550, 46.6250), // NE
172+
new Coordinate(-121.3600, 46.6250)
173+
])) { SRID = 4326 },
174+
new Polygon(new LinearRing([
175+
new Coordinate(-121.3540, 46.6240), // NW
176+
new Coordinate(-121.3540, 46.6220), // SW
177+
new Coordinate(-121.3525, 46.6220), // SE
178+
new Coordinate(-121.3525, 46.6240), // NE
179+
new Coordinate(-121.3540, 46.6240)
180+
])) { SRID = 4326 }
181+
]) { SRID = 4326 };
182+
}
183+
}
184+
185+
public class GeometryCollectionTypeTest(GeometryCollectionTypeTest.GeometryCollectionTypeFixture fixture)
186+
: GeographyTypeTestBase<GeometryCollection, GeometryCollectionTypeTest.GeometryCollectionTypeFixture>(fixture)
187+
{
188+
public class GeometryCollectionTypeFixture() : GeographyTypeFixture
189+
{
190+
public override GeometryCollection Value { get; } = new(
191+
[
192+
new Point(-122.3500, 47.6200) { SRID = 4326 },
193+
new LineString([
194+
new Coordinate(-122.3500, 47.6200),
195+
new Coordinate(-122.3450, 47.6150)
196+
]) { SRID = 4326 },
197+
new Polygon(new LinearRing([
198+
new Coordinate(-122.3480, 47.6190), // NW
199+
new Coordinate(-122.3480, 47.6170), // SW
200+
new Coordinate(-122.3460, 47.6170), // SE
201+
new Coordinate(-122.3460, 47.6190), // NE
202+
new Coordinate(-122.3480, 47.6190)
203+
])) { SRID = 4326 }
204+
]) { SRID = 4326 };
205+
206+
public override GeometryCollection OtherValue { get; } = new(
207+
[
208+
new Point(-121.9000, 46.9500) { SRID = 4326 },
209+
new LineString([
210+
new Coordinate(-121.9000, 46.9500),
211+
new Coordinate(-121.6000, 46.8200)
212+
]) { SRID = 4326 },
213+
new Polygon(new LinearRing([
214+
new Coordinate(-121.8800, 46.9400), // NW
215+
new Coordinate(-121.8800, 46.9200), // SW
216+
new Coordinate(-121.8600, 46.9200), // SE
217+
new Coordinate(-121.8600, 46.9400), // NE
218+
new Coordinate(-121.8800, 46.9400)
219+
])) { SRID = 4326 }
220+
]) { SRID = 4326 };
221+
}
222+
}

0 commit comments

Comments
 (0)