Sqlite: Support spatial data via NTS and SpatiaLite

Part of #1100
This commit is contained in:
Brice Lambson 2018-06-14 15:25:39 -07:00
Родитель de44d56423
Коммит dc0f1b01d3
55 изменённых файлов: 2679 добавлений и 81 удалений

Просмотреть файл

@ -64,6 +64,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Analyzers", "src\EFC
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.SqlServer.NTS", "src\EFCore.SqlServer.NTS\EFCore.SqlServer.NTS.csproj", "{F53EB45F-84D7-4520-B813-8916C0C756BB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Sqlite.NTS", "src\EFCore.Sqlite.NTS\EFCore.Sqlite.NTS.csproj", "{1747A095-A464-4445-9F39-A80531FB24FD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -158,6 +160,10 @@ Global
{F53EB45F-84D7-4520-B813-8916C0C756BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F53EB45F-84D7-4520-B813-8916C0C756BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F53EB45F-84D7-4520-B813-8916C0C756BB}.Release|Any CPU.Build.0 = Release|Any CPU
{1747A095-A464-4445-9F39-A80531FB24FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1747A095-A464-4445-9F39-A80531FB24FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1747A095-A464-4445-9F39-A80531FB24FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1747A095-A464-4445-9F39-A80531FB24FD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -185,6 +191,7 @@ Global
{313F46FE-9962-4A15-805F-FCBDF5A6181E} = {258D5057-81B9-40EC-A872-D21E27452749}
{948D3EDA-ECF0-4367-B157-BF770F752A8B} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{F53EB45F-84D7-4520-B813-8916C0C756BB} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{1747A095-A464-4445-9F39-A80531FB24FD} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EC8BCF1F-A206-4420-A292-3E3F2A4CDC54}

Просмотреть файл

@ -99,6 +99,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Cosmos.Sql.Tests", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.SqlServer.NTS", "src\EFCore.SqlServer.NTS\EFCore.SqlServer.NTS.csproj", "{F53EB45F-84D7-4520-B813-8916C0C756BB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Sqlite.NTS", "src\EFCore.Sqlite.NTS\EFCore.Sqlite.NTS.csproj", "{1747A095-A464-4445-9F39-A80531FB24FD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -245,6 +247,10 @@ Global
{F53EB45F-84D7-4520-B813-8916C0C756BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F53EB45F-84D7-4520-B813-8916C0C756BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F53EB45F-84D7-4520-B813-8916C0C756BB}.Release|Any CPU.Build.0 = Release|Any CPU
{1747A095-A464-4445-9F39-A80531FB24FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1747A095-A464-4445-9F39-A80531FB24FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1747A095-A464-4445-9F39-A80531FB24FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1747A095-A464-4445-9F39-A80531FB24FD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -288,6 +294,7 @@ Global
{76EB1FAD-00F9-4B92-B954-073AF288D3DD} = {258D5057-81B9-40EC-A872-D21E27452749}
{B4E155E5-C0B8-4680-92A0-A0DE745486B1} = {258D5057-81B9-40EC-A872-D21E27452749}
{F53EB45F-84D7-4520-B813-8916C0C756BB} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{1747A095-A464-4445-9F39-A80531FB24FD} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {285A5EB4-BCF4-40EB-B9E1-DF6DBCB5E705}

Просмотреть файл

@ -39,6 +39,8 @@
<XunitAssertPackageVersion>2.3.1</XunitAssertPackageVersion>
<XunitCorePackageVersion>2.3.1</XunitCorePackageVersion>
<XunitRunnerVisualStudioPackageVersion>2.4.0</XunitRunnerVisualStudioPackageVersion>
<NetTopologySuiteIOSpatiaLitePackageVersion>1.15.0</NetTopologySuiteIOSpatiaLitePackageVersion>
<mod_spatialitePackageVersion>4.3.0.1</mod_spatialitePackageVersion>
</PropertyGroup>
<Import Project="$(DotNetPackageVersionPropsPath)" Condition=" '$(DotNetPackageVersionPropsPath)' != '' " />
<PropertyGroup Label="Package Versions: Pinned" />

Просмотреть файл

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.TestModels.SpatialModel;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.EntityFrameworkCore.TestUtilities.Xunit;
using NetTopologySuite.Geometries;
using Xunit;
@ -20,14 +21,14 @@ namespace Microsoft.EntityFrameworkCore.Query
{
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Area(bool isAsync)
{
return AssertQuery<PolygonEntity>(isAsync, es => es.Select(e => new { e.Id, e.Polygon.Area }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task AsBinary(bool isAsync)
{
@ -41,7 +42,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task AsText(bool isAsync)
{
@ -55,7 +56,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Boundary(bool isAsync)
{
@ -69,7 +70,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Buffer(bool isAsync)
{
@ -83,7 +84,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Buffer_quadrantSegments(bool isAsync)
{
@ -97,7 +98,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Centroid(bool isAsync)
{
@ -111,7 +112,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Contains(bool isAsync)
{
@ -120,7 +121,7 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, Contains = e.Polygon.Contains(new Point(0.5, 0.25)) }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task ConvexHull(bool isAsync)
{
@ -134,7 +135,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task IGeometryCollection_Count(bool isAsync)
{
@ -143,7 +144,7 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, e.MultiLineString.Count }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task LineString_Count(bool isAsync)
{
@ -152,7 +153,7 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, ((LineString)e.LineString).Count }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task CoveredBy(bool isAsync)
{
@ -176,7 +177,7 @@ namespace Microsoft.EntityFrameworkCore.Query
}));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Covers(bool isAsync)
{
@ -185,7 +186,7 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, Covers = e.Polygon.Covers(new Point(0.5, 0.25)) }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Crosses(bool isAsync)
{
@ -205,7 +206,7 @@ namespace Microsoft.EntityFrameworkCore.Query
}));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Difference(bool isAsync)
{
@ -233,14 +234,14 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Dimension(bool isAsync)
{
return AssertQuery<PointEntity>(isAsync, es => es.Select(e => new { e.Id, e.Point.Dimension }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Disjoint(bool isAsync)
{
@ -249,7 +250,7 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, Disjoint = e.Polygon.Disjoint(new Point(1, 0)) }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Distance(bool isAsync)
{
@ -258,14 +259,14 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, Distance = e.Point.Distance(new Point(0, 1)) }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task EndPoint(bool isAsync)
{
return AssertQuery<LineStringEntity>(isAsync, es => es.Select(e => new { e.Id, e.LineString.EndPoint }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Envelope(bool isAsync)
{
@ -279,7 +280,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task EqualsTopologically(bool isAsync)
{
@ -288,21 +289,21 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, EqualsTopologically = e.Point.EqualsTopologically(new Point(0, 0)) }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task ExteriorRing(bool isAsync)
{
return AssertQuery<PolygonEntity>(isAsync, es => es.Select(e => new { e.Id, e.Polygon.ExteriorRing }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task GeometryType(bool isAsync)
{
return AssertQuery<PointEntity>(isAsync, es => es.Select(e => new { e.Id, e.Point.GeometryType }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task GetGeometryN(bool isAsync)
{
@ -311,7 +312,7 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, Geometry0 = e.MultiLineString.GetGeometryN(0) }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task GetInteriorRingN(bool isAsync)
{
@ -323,7 +324,7 @@ namespace Microsoft.EntityFrameworkCore.Query
select new { e.Id, InteriorRing0 = e.Polygon.GetInteriorRingN(0) });
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task GetPointN(bool isAsync)
{
@ -332,7 +333,7 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, Point0 = e.LineString.GetPointN(0) }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Intersection(bool isAsync)
{
@ -360,7 +361,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Intersects(bool isAsync)
{
@ -380,14 +381,14 @@ namespace Microsoft.EntityFrameworkCore.Query
}));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task ICurve_IsClosed(bool isAsync)
{
return AssertQuery<LineStringEntity>(isAsync, es => es.Select(e => new { e.Id, e.LineString.IsClosed }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task IMultiCurve_IsClosed(bool isAsync)
{
@ -396,7 +397,7 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, e.MultiLineString.IsClosed }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task IsEmpty(bool isAsync)
{
@ -405,28 +406,28 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, e.MultiLineString.IsEmpty }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task IsRing(bool isAsync)
{
return AssertQuery<LineStringEntity>(isAsync, es => es.Select(e => new { e.Id, e.LineString.IsRing }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task IsSimple(bool isAsync)
{
return AssertQuery<LineStringEntity>(isAsync, es => es.Select(e => new { e.Id, e.LineString.IsSimple }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task IsValid(bool isAsync)
{
return AssertQuery<PointEntity>(isAsync, es => es.Select(e => new { e.Id, e.Point.IsValid }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Item(bool isAsync)
{
@ -435,14 +436,14 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, Item0 = e.MultiLineString[0] }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Length(bool isAsync)
{
return AssertQuery<LineStringEntity>(isAsync, es => es.Select(e => new { e.Id, e.LineString.Length }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task M(bool isAsync)
{
@ -456,7 +457,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task NumGeometries(bool isAsync)
{
@ -465,7 +466,7 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, e.MultiLineString.NumGeometries }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task NumInteriorRings(bool isAsync)
{
@ -474,14 +475,14 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, e.Polygon.NumInteriorRings }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task NumPoints(bool isAsync)
{
return AssertQuery<LineStringEntity>(isAsync, es => es.Select(e => new { e.Id, e.LineString.NumPoints }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Overlaps(bool isAsync)
{
@ -504,7 +505,7 @@ namespace Microsoft.EntityFrameworkCore.Query
}));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task PointOnSurface(bool isAsync)
{
@ -518,7 +519,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Relate(bool isAsync)
{
@ -542,7 +543,7 @@ namespace Microsoft.EntityFrameworkCore.Query
}));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Reverse(bool isAsync)
{
@ -551,21 +552,21 @@ namespace Microsoft.EntityFrameworkCore.Query
es => es.Select(e => new { e.Id, Reverse = e.LineString.Reverse() }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task SRID(bool isAsync)
{
return AssertQuery<PointEntity>(isAsync, es => es.Select(e => new { e.Id, e.Point.SRID }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task StartPoint(bool isAsync)
{
return AssertQuery<LineStringEntity>(isAsync, es => es.Select(e => new { e.Id, e.LineString.StartPoint }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task SymmetricDifference(bool isAsync)
{
@ -596,7 +597,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task ToBinary(bool isAsync)
{
@ -610,7 +611,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task ToText(bool isAsync)
{
@ -624,7 +625,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Touches(bool isAsync)
{
@ -647,7 +648,7 @@ namespace Microsoft.EntityFrameworkCore.Query
}));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Union(bool isAsync)
{
@ -675,7 +676,7 @@ namespace Microsoft.EntityFrameworkCore.Query
});
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Within(bool isAsync)
{
@ -699,21 +700,21 @@ namespace Microsoft.EntityFrameworkCore.Query
}));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task X(bool isAsync)
{
return AssertQuery<PointEntity>(isAsync, es => es.Select(e => new { e.Id, e.Point.X }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Y(bool isAsync)
{
return AssertQuery<PointEntity>(isAsync, es => es.Select(e => new { e.Id, e.Point.Y }));
}
[Theory]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Z(bool isAsync)
{

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.EntityFrameworkCore.TestModels.SpatialModel;
using Microsoft.EntityFrameworkCore.TestUtilities.Xunit;
using NetTopologySuite.Geometries;
using Xunit;
@ -16,7 +17,7 @@ namespace Microsoft.EntityFrameworkCore
protected virtual TFixture Fixture { get; }
[Fact]
[ConditionalFact]
public virtual void Values_are_copied_into_change_tracker()
{
using (var db = Fixture.CreateContext())
@ -34,7 +35,7 @@ namespace Microsoft.EntityFrameworkCore
}
}
[Fact]
[ConditionalFact]
public virtual void Values_arent_compared_by_reference()
{
using (var db = Fixture.CreateContext())

Просмотреть файл

@ -61,7 +61,7 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal
/// </summary>
protected override string GenerateNonNullSqlLiteral(object value)
{
// TODO: Can we avoid the conversion in the first place? Should we just inline the BLOB?
// TODO: Avoid converting in the first place
var geometry = _reader.Read(((SqlBytes)value).Value);
var srid = geometry.SRID;

Просмотреть файл

@ -22,6 +22,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="$(MicrosoftDataSqliteCorePackageVersion)" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(MicrosoftExtensionsDependencyModelPackageVersion)" />
<PackageReference Include="StyleCop.Analyzers" Version="$(StyleCopAnalyzersPackageVersion)" PrivateAssets="All" />
</ItemGroup>

Просмотреть файл

@ -0,0 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// SQLite specific extension methods for metadata.
/// </summary>
public static class SqliteMetadataExtensions
{
/// <summary>
/// Gets the SQLite specific metadata for a property.
/// </summary>
/// <param name="property"> The property to get metadata for. </param>
/// <returns> The SQLite specific metadata for the property. </returns>
public static SqlitePropertyAnnotations Sqlite([NotNull] this IMutableProperty property)
=> (SqlitePropertyAnnotations)Sqlite((IProperty)property);
/// <summary>
/// Gets the SQLite specific metadata for a property.
/// </summary>
/// <param name="property"> The property to get metadata for. </param>
/// <returns> The SQLite specific metadata for the property. </returns>
public static ISqlitePropertyAnnotations Sqlite([NotNull] this IProperty property)
=> new SqlitePropertyAnnotations(Check.NotNull(property, nameof(property)));
}
}

Просмотреть файл

@ -0,0 +1,41 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// SQLite specific extension methods for <see cref="PropertyBuilder" />.
/// </summary>
public static class SqlitePropertyBuilderExtensions
{
/// <summary>
/// Configures the SRID of the column that the property maps to when targeting SQLite.
/// </summary>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <param name="srid"> The SRID. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertyBuilder ForSqliteHasSrid([NotNull] this PropertyBuilder propertyBuilder, int srid)
{
Check.NotNull(propertyBuilder, nameof(propertyBuilder));
propertyBuilder.Metadata.Sqlite().Srid = srid;
return propertyBuilder;
}
/// <summary>
/// Configures the SRID of the column that the property maps to when targeting SQLite.
/// </summary>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <param name="srid"> The SRID. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertyBuilder<TProperty> ForSqliteHasSrid<TProperty>(
[NotNull] this PropertyBuilder<TProperty> propertyBuilder,
int srid)
=> (PropertyBuilder<TProperty>)ForSqliteHasSrid((PropertyBuilder)propertyBuilder, srid);
}
}

Просмотреть файл

@ -17,6 +17,7 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal
public class SqliteOptionsExtension : RelationalOptionsExtension, IDbContextOptionsExtensionWithDebugInfo
{
private bool _enforceForeignKeys = true;
private bool _loadSpatialite;
private string _logFragment;
/// <summary>
@ -37,6 +38,7 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal
: base(copyFrom)
{
_enforceForeignKeys = copyFrom._enforceForeignKeys;
_loadSpatialite = copyFrom._loadSpatialite;
}
/// <summary>
@ -52,6 +54,12 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal
/// </summary>
public virtual bool EnforceForeignKeys => _enforceForeignKeys;
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool LoadSpatialite => _loadSpatialite;
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
@ -65,6 +73,19 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal
return clone;
}
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual SqliteOptionsExtension WithLoadSpatialite(bool loadSpatialite)
{
var clone = (SqliteOptionsExtension)Clone();
clone._loadSpatialite = loadSpatialite;
return clone;
}
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
@ -106,6 +127,11 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal
builder.Append("SuppressForeignKeyEnforcement ");
}
if (_loadSpatialite)
{
builder.Append("LoadSpatialite ");
}
_logFragment = builder.ToString();
}

Просмотреть файл

@ -0,0 +1,165 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.Extensions.DependencyModel;
using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;
namespace Microsoft.EntityFrameworkCore.Infrastructure
{
/// <summary>
/// Finds and loads SpatiaLite.
/// </summary>
public static class SpatialiteLoader
{
private static readonly string _sharedLibraryExtension;
private static readonly string _pathVariableName;
private static string _extension;
static SpatialiteLoader()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_sharedLibraryExtension = ".dll";
_pathVariableName = "PATH";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
_sharedLibraryExtension = ".dylib";
_pathVariableName = "DYLD_LIBRARY_PATH";
}
else
{
_sharedLibraryExtension = ".so";
_pathVariableName = "LD_LIBRARY_PATH";
}
}
/// <summary>
/// Tries to load the mod_spatialite extension into the specified connection.
/// </summary>
/// <param name="connection"> The connection. </param>
/// <returns> true if the extension was loaded; otherwise, false. </returns>
public static bool TryLoad([NotNull] DbConnection connection)
{
try
{
Load(connection);
return true;
}
catch (SqliteException ex) when (ex.SqliteErrorCode == 1)
{
return false;
}
}
/// <summary>
/// <para>
/// Loads the mod_spatialite extension into the specified connection.
/// </para>
/// <para>
/// The the extension will be loaded from native NuGet assets when available.
/// </para>
/// </summary>
/// <param name="connection"> The connection. </param>
public static void Load([NotNull] DbConnection connection)
{
Check.NotNull(connection, nameof(connection));
var extension = FindExtension();
using (var command = connection.CreateCommand())
{
command.CommandText = "SELECT load_extension('" + extension + "');";
command.ExecuteNonQuery();
}
}
private static string FindExtension()
{
if (_extension != null)
{
return _extension;
}
var candidateAssets = new Dictionary<string, int>();
var rid = RuntimeEnvironment.GetRuntimeIdentifier();
var rids = DependencyContext.Default.RuntimeGraph.First(g => g.Runtime == rid).Fallbacks.ToList();
rids.Insert(0, rid);
foreach (var library in DependencyContext.Default.RuntimeLibraries)
{
foreach (var group in library.NativeLibraryGroups)
{
foreach (var file in group.RuntimeFiles)
{
if (string.Equals(
Path.GetFileName(file.Path),
"mod_spatialite" + _sharedLibraryExtension,
StringComparison.OrdinalIgnoreCase))
{
var fallbacks = rids.IndexOf(group.Runtime);
if (fallbacks != -1)
{
candidateAssets.Add(library.Path + "/" + file.Path, fallbacks);
}
}
}
}
}
var assetPath = candidateAssets.OrderBy(p => p.Value)
.Select(p => p.Key.Replace('/', Path.DirectorySeparatorChar)).FirstOrDefault();
if (assetPath != null)
{
string assetFullPath = null;
var probingDirectories = ((string)AppDomain.CurrentDomain.GetData("PROBING_DIRECTORIES"))
.Split(Path.PathSeparator);
foreach (var directory in probingDirectories)
{
var candidateFullPath = Path.Combine(directory, assetPath);
if (File.Exists(candidateFullPath))
{
assetFullPath = candidateFullPath;
}
}
Debug.Assert(assetFullPath != null);
var assetDirectory = Path.GetDirectoryName(assetFullPath);
var currentPath = Environment.GetEnvironmentVariable(_pathVariableName);
if (!currentPath.Split(Path.PathSeparator).Any(
p => string.Equals(
p.TrimEnd(Path.DirectorySeparatorChar),
assetDirectory,
StringComparison.OrdinalIgnoreCase)))
{
Environment.SetEnvironmentVariable(
_pathVariableName,
assetDirectory + Path.PathSeparator + currentPath);
}
}
var extension = "mod_spatialite";
// Workaround ericsink/SQLitePCL.raw#225
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
extension += _sharedLibraryExtension;
}
return _extension = extension;
}
}
}

Просмотреть файл

@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.EntityFrameworkCore.Metadata
{
/// <summary>
/// API for SQLite-specific annotations accessed through
/// <see cref="SqliteMetadataExtensions.Sqlite(IProperty)" />.
/// </summary>
public interface ISqlitePropertyAnnotations : IRelationalPropertyAnnotations
{
/// <summary>
/// Gets the SRID to use when creating a column for this property.
/// </summary>
int? Srid { get; }
/// <summary>
/// Gets the dimension to use when creating a column for this property.
/// </summary>
string Dimension { get; }
}
}

Просмотреть файл

@ -38,5 +38,23 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Metadata.Internal
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public const string InlinePrimaryKeyName = Prefix + "InlinePrimaryKeyName";
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public const string InitSpatialMetaData = Prefix + "InitSpatialMetaData";
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public const string Srid = Prefix + "Srid";
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public const string Dimension = Prefix + "Dimension";
}
}

Просмотреть файл

@ -25,10 +25,10 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Metadata.Internal
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static RelationalPropertyBuilderAnnotations Sqlite(
public static SqlitePropertyBuilderAnnotations Sqlite(
[NotNull] this InternalPropertyBuilder builder,
ConfigurationSource configurationSource)
=> new RelationalPropertyBuilderAnnotations(builder, configurationSource);
=> new SqlitePropertyBuilderAnnotations(builder, configurationSource);
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used

Просмотреть файл

@ -0,0 +1,104 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore.Sqlite.Metadata.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqlitePropertyBuilderAnnotations : SqlitePropertyAnnotations
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public SqlitePropertyBuilderAnnotations(
[NotNull] InternalPropertyBuilder internalBuilder,
ConfigurationSource configurationSource)
: base(new RelationalAnnotationsBuilder(internalBuilder, configurationSource))
{
}
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected new virtual RelationalAnnotationsBuilder Annotations => (RelationalAnnotationsBuilder)base.Annotations;
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected override bool ShouldThrowOnConflict => false;
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected override bool ShouldThrowOnInvalidConfiguration => Annotations.ConfigurationSource == ConfigurationSource.Explicit;
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool HasColumnName([CanBeNull] string value) => SetColumnName(value);
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool CanSetColumnName([CanBeNull] string value)
=> Annotations.CanSetAnnotation(RelationalAnnotationNames.ColumnName, value);
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool HasColumnType([CanBeNull] string value) => SetColumnType(value);
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool HasDefaultValueSql([CanBeNull] string value)
=> SetDefaultValueSql(value);
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool HasComputedColumnSql([CanBeNull] string value)
=> SetComputedColumnSql(value);
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool HasDefaultValue([CanBeNull] object value)
=> SetDefaultValue(value);
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public new virtual bool IsFixedLength(bool fixedLength)
=> SetFixedLength(fixedLength);
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool HasSrid(int? value) => SetSrid(value);
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool HasDimension(string value) => SetDimension(value);
}
}

Просмотреть файл

@ -0,0 +1,70 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Sqlite.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore.Metadata
{
/// <summary>
/// Properties for SQLite-specific annotations accessed through
/// <see cref="SqliteMetadataExtensions.Sqlite(IMutableProperty)" />.
/// </summary>
public class SqlitePropertyAnnotations : RelationalPropertyAnnotations, ISqlitePropertyAnnotations
{
/// <summary>
/// Constructs an instance for annotations of the given <see cref="IProperty" />.
/// </summary>
/// <param name="property"> The <see cref="IProperty" /> to use. </param>
public SqlitePropertyAnnotations([NotNull] IProperty property)
: base(property)
{
}
/// <summary>
/// Constructs an instance for annotations of the <see cref="IProperty" />
/// represented by the given annotation helper.
/// </summary>
/// <param name="annotations">
/// The <see cref="RelationalAnnotations" /> helper representing the <see cref="IProperty" /> to annotate.
/// </param>
protected SqlitePropertyAnnotations([NotNull] RelationalAnnotations annotations)
: base(annotations)
{
}
/// <summary>
/// Gets or sets the SRID to use when creating a column for this property.
/// </summary>
public virtual int? Srid
{
get => (int?)Annotations.Metadata[SqliteAnnotationNames.Srid];
set => SetSrid(value);
}
/// <summary>
/// Sets the SRID to use when creating a column for this property.
/// </summary>
/// <param name="value"> The SRID. </param>
/// <returns> true if the annotation was set; otherwise, false. </returns>
protected virtual bool SetSrid(int? value)
=> Annotations.SetAnnotation(SqliteAnnotationNames.Srid, value);
/// <summary>
/// Gets or sets the dimension to use when creating a column for this property.
/// </summary>
public virtual string Dimension
{
get => (string)Annotations.Metadata[SqliteAnnotationNames.Dimension];
set => SetDimension(value);
}
/// <summary>
/// Sets the dimension to use when creating a column for this property.
/// </summary>
/// <param name="value"> The dimension. </param>
/// <returns> true if the annotation was set; otherwise, false. </returns>
protected virtual bool SetDimension(string value)
=> Annotations.SetAnnotation(SqliteAnnotationNames.Dimension, value);
}
}

Просмотреть файл

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
@ -27,6 +28,19 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Migrations.Internal
{
}
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public override IEnumerable<IAnnotation> For(IModel model)
{
if (model.GetEntityTypes().SelectMany(t => t.GetProperties()).Any(
p => SqliteMigrationsSqlGenerator.IsSpatialiteType(p.Relational().ColumnType)))
{
yield return new Annotation(SqliteAnnotationNames.InitSpatialMetaData, true);
}
}
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
@ -39,6 +53,18 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Migrations.Internal
{
yield return new Annotation(SqliteAnnotationNames.Autoincrement, true);
}
var srid = property.Sqlite().Srid;
if (srid != null)
{
yield return new Annotation(SqliteAnnotationNames.Srid, srid);
}
var dimension = property.Sqlite().Dimension;
if (dimension != null)
{
yield return new Annotation(SqliteAnnotationNames.Dimension, dimension);
}
}
private static bool HasConverter(IProperty property)

Просмотреть файл

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -12,6 +13,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Sqlite.Internal;
using Microsoft.EntityFrameworkCore.Sqlite.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore.Migrations
@ -21,6 +23,19 @@ namespace Microsoft.EntityFrameworkCore.Migrations
/// </summary>
public class SqliteMigrationsSqlGenerator : MigrationsSqlGenerator
{
private static readonly HashSet<string> _spatialiteTypes
= new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"GEOMETRY",
"GEOMETRYCOLLECTION",
"LINESTRING",
"MULTILINESTRING",
"MULTIPOINT",
"MULTIPOLYGON",
"POINT",
"POLYGON"
};
private readonly IMigrationsAnnotationProvider _migrationsAnnotations;
/// <summary>
@ -42,9 +57,33 @@ namespace Microsoft.EntityFrameworkCore.Migrations
/// <param name="model"> The target model which may be <c>null</c> if the operations exist without a model. </param>
/// <returns> The list of commands to be executed or scripted. </returns>
public override IReadOnlyList<MigrationCommand> Generate(IReadOnlyList<MigrationOperation> operations, IModel model = null)
=> base.Generate(LiftForeignKeyOperations(operations), model);
=> base.Generate(RewriteOperations(operations, model), model);
private static IReadOnlyList<MigrationOperation> LiftForeignKeyOperations(IReadOnlyList<MigrationOperation> migrationOperations)
/// <summary>
/// Checks whether a column type is one of the SpatiaLite column types.
/// </summary>
/// <param name="columnType"> The column type to check. </param>
/// <returns> true if it's a SpatiaLite type; otherwise, false. </returns>
public static bool IsSpatialiteType(string columnType)
=> _spatialiteTypes.Contains(columnType);
private bool IsSpatialiteColumn(AddColumnOperation operation, IModel model)
=> IsSpatialiteType(
operation.ColumnType
?? GetColumnType(
operation.Schema,
operation.Table,
operation.Name,
operation.ClrType,
operation.IsUnicode,
operation.MaxLength,
operation.IsFixedLength,
operation.IsRowVersion,
model));
private IReadOnlyList<MigrationOperation> RewriteOperations(
IReadOnlyList<MigrationOperation> migrationOperations,
IModel model)
{
var operations = new List<MigrationOperation>();
foreach (var operation in migrationOperations)
@ -58,15 +97,120 @@ namespace Microsoft.EntityFrameworkCore.Migrations
if (table != null)
{
table.ForeignKeys.Add(foreignKeyOperation);
//do not add to fk operation migration
continue;
}
else
{
operations.Add(operation);
}
}
else if (operation is CreateTableOperation createTableOperation)
{
var spatialiteColumns = new Stack<AddColumnOperation>();
for (var i = createTableOperation.Columns.Count - 1; i >= 0; i--)
{
var addColumnOperation = createTableOperation.Columns[i];
operations.Add(operation);
if (IsSpatialiteColumn(addColumnOperation, model))
{
spatialiteColumns.Push(addColumnOperation);
createTableOperation.Columns.RemoveAt(i);
}
}
operations.Add(operation);
operations.AddRange(spatialiteColumns);
}
else
{
operations.Add(operation);
}
}
return operations.AsReadOnly();
return operations;
}
/// <summary>
/// Builds commands for the given <see cref="AlterDatabaseOperation" /> by making calls on the given
/// <see cref="MigrationCommandListBuilder" />.
/// </summary>
/// <param name="operation"> The operation. </param>
/// <param name="model"> The target model which may be <c>null</c> if the operations exist without a model. </param>
/// <param name="builder"> The command builder to use to build the commands. </param>
protected override void Generate(AlterDatabaseOperation operation, IModel model, MigrationCommandListBuilder builder)
{
if (operation[SqliteAnnotationNames.InitSpatialMetaData] as bool? != true
|| operation.OldDatabase[SqliteAnnotationNames.InitSpatialMetaData] as bool? == true)
{
return;
}
builder
.Append("SELECT InitSpatialMetaData()")
.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
EndStatement(builder);
}
/// <summary>
/// Builds commands for the given <see cref="AddColumnOperation" /> by making calls on the given
/// <see cref="MigrationCommandListBuilder" />.
/// </summary>
/// <param name="operation"> The operation. </param>
/// <param name="model"> The target model which may be <c>null</c> if the operations exist without a model. </param>
/// <param name="builder"> The command builder to use to build the commands. </param>
/// <param name="terminate"> Indicates whether or not to terminate the command after generating SQL for the operation. </param>
protected override void Generate(AddColumnOperation operation, IModel model, MigrationCommandListBuilder builder, bool terminate)
{
if (!IsSpatialiteColumn(operation, model))
{
base.Generate(operation, model, builder, terminate);
return;
}
var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string));
var longTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(long));
var srid = operation[SqliteAnnotationNames.Srid] as int? ?? 0;
var dimension = operation[SqliteAnnotationNames.Dimension] as string;
var geometryType = operation.ColumnType
?? GetColumnType(
operation.Schema,
operation.Table,
operation.Name,
operation.ClrType,
operation.IsUnicode,
operation.MaxLength,
operation.IsFixedLength,
operation.IsRowVersion,
model);
if (!string.IsNullOrEmpty(dimension))
{
geometryType += dimension;
}
builder
.Append("SELECT AddGeometryColumn(")
.Append(stringTypeMapping.GenerateSqlLiteral(operation.Table))
.Append(", ")
.Append(stringTypeMapping.GenerateSqlLiteral(operation.Name))
.Append(", ")
.Append(longTypeMapping.GenerateSqlLiteral(srid))
.Append(", ")
.Append(stringTypeMapping.GenerateSqlLiteral(geometryType))
.Append(", -1, ")
.Append(operation.IsNullable ? "0" : "1")
.Append(")");
if (terminate)
{
builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
EndStatement(builder);
}
else
{
Debug.Fail("I have a bad feeling about this. Geometry columns don't compose well.");
}
}
/// <summary>

Просмотреть файл

@ -12,6 +12,7 @@ using System.Text;
using JetBrains.Annotations;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Scaffolding;
@ -82,6 +83,9 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Scaffolding.Internal
if (!connectionStartedOpen)
{
connection.Open();
((SqliteConnection)connection).EnableExtensions();
SpatialiteLoader.TryLoad(connection);
}
try
@ -146,9 +150,9 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Scaffolding.Internal
command.CommandText = new StringBuilder()
.AppendLine("SELECT \"name\"")
.AppendLine("FROM \"sqlite_master\"")
.Append("WHERE \"type\" = 'table' AND instr(\"name\", 'sqlite_') <> 1 AND \"name\" <> '")
.Append("WHERE \"type\" = 'table' AND instr(\"name\", 'sqlite_') <> 1 AND \"name\" NOT IN ('")
.Append(HistoryRepository.DefaultTableName)
.AppendLine("';")
.AppendLine("', 'geometry_columns', 'spatial_ref_sys', 'spatialite_history');")
.ToString();
using (var reader = command.ExecuteReader())

Просмотреть файл

@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
@ -20,6 +21,7 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal
public class SqliteRelationalConnection : RelationalConnection, ISqliteRelationalConnection
{
private readonly IRawSqlCommandBuilder _rawSqlCommandBuilder;
private readonly bool _loadSpatialite;
private readonly bool _enforceForeignKeys = true;
/// <summary>
@ -38,6 +40,7 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal
var optionsExtension = dependencies.ContextOptions.Extensions.OfType<SqliteOptionsExtension>().FirstOrDefault();
if (optionsExtension != null)
{
_loadSpatialite = optionsExtension.LoadSpatialite;
_enforceForeignKeys = optionsExtension.EnforceForeignKeys;
}
}
@ -62,6 +65,7 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal
{
if (base.Open(errorsExpected))
{
LoadSpatialite();
EnableForeignKeys();
return true;
}
@ -77,6 +81,7 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal
{
if (await base.OpenAsync(cancellationToken, errorsExpected))
{
LoadSpatialite();
EnableForeignKeys();
return true;
}
@ -84,6 +89,19 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal
return false;
}
private void LoadSpatialite()
{
if (!_loadSpatialite)
{
return;
}
var connection = (SqliteConnection)DbConnection;
connection.EnableExtensions();
SpatialiteLoader.Load(DbConnection);
connection.EnableExtensions(false);
}
private void EnableForeignKeys()
{
if (_enforceForeignKeys)

Просмотреть файл

@ -49,6 +49,15 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal
{ typeof(Guid), new SqliteGuidTypeMapping(BlobTypeName) }
};
private readonly Dictionary<string, RelationalTypeMapping> _storeTypeMappings
= new Dictionary<string, RelationalTypeMapping>(StringComparer.OrdinalIgnoreCase)
{
{ IntegerTypeName, _integer },
{ RealTypeName, _real },
{ BlobTypeName, _blob },
{ TextTypeName, _text },
};
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
@ -74,14 +83,21 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal
}
var storeTypeName = mappingInfo.StoreTypeName;
if (storeTypeName == null)
if (storeTypeName != null
&& _storeTypeMappings.TryGetValue(storeTypeName, out mapping))
{
return null;
return mapping;
}
return storeTypeName.Length != 0
? _typeRules.Select(r => r(storeTypeName)).FirstOrDefault(r => r != null) ?? _text
: _text; // This may seem odd, but it's okay because we are matching SQLite's loose typing.
mapping = base.FindMapping(mappingInfo);
return mapping != null
? mapping
: storeTypeName != null
? storeTypeName.Length != 0
? _typeRules.Select(r => r(storeTypeName)).FirstOrDefault(r => r != null) ?? _text
: _text // This may seem odd, but it's okay because we are matching SQLite's loose typing.
: null;
}
private readonly Func<string, RelationalTypeMapping>[] _typeRules =

Просмотреть файл

@ -0,0 +1,56 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Microsoft.EntityFrameworkCore.Sqlite.NetTopologySuite</AssemblyName>
<PackageId>Microsoft.EntityFrameworkCore.Sqlite.NetTopologySuite</PackageId>
<RootNamespace>Microsoft.EntityFrameworkCore.Sqlite</RootNamespace>
<Description>NetTopologySuite support for the SQLite database provider for Entity Framework Core.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<MinClientVersion>3.6</MinClientVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>$(PackageTags);SQLite;GIS;NTS;OGC;SpatiaLite</PackageTags>
<CodeAnalysisRuleSet>..\..\EFCore.ruleset</CodeAnalysisRuleSet>
<EnableApiCheck>false</EnableApiCheck>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\*.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EFCore.Sqlite.Core\EFCore.Sqlite.Core.csproj" PrivateAssets="contentfiles;build" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="mod_spatialite" Version="$(mod_spatialitePackageVersion)" />
<PackageReference Include="NetTopologySuite.Core" Version="$(NetTopologySuiteCorePackageVersion)" />
<PackageReference Include="NetTopologySuite.IO.SpatiaLite" Version="$(NetTopologySuiteIOSpatiaLitePackageVersion)" />
<PackageReference Include="StyleCop.Analyzers" Version="$(StyleCopAnalyzersPackageVersion)" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<None Update="Properties\SqliteNTSStrings.Designer.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>SqliteNTSStrings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\SqliteNTSStrings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>SqliteNTSStrings.Designer.tt</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\SqliteNTSStrings.resx">
<CustomToolNamespace>Microsoft.EntityFrameworkCore.Sqlite.Internal</CustomToolNamespace>
</EmbeddedResource>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// NetTopologySuite specific extension methods for <see cref="SqliteDbContextOptionsBuilder"/>.
/// </summary>
public static class SqliteNTSDbContextOptionsBuilderExtensions
{
/// <summary>
/// Use NetTopologySuite to access SpatiaLite data.
/// </summary>
/// <param name="optionsBuilder"> The build being used to configure SQLite. </param>
/// <returns> The options builder so that further configuration can be chained. </returns>
public static SqliteDbContextOptionsBuilder UseNetTopologySuite(
[NotNull] this SqliteDbContextOptionsBuilder optionsBuilder)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)optionsBuilder).OptionsBuilder;
var infrastructure = (IDbContextOptionsBuilderInfrastructure)coreOptionsBuilder;
var sqliteExtension = coreOptionsBuilder.Options.FindExtension<SqliteOptionsExtension>()
?? new SqliteOptionsExtension();
var ntsExtension = coreOptionsBuilder.Options.FindExtension<SqliteNTSOptionsExtension>()
?? new SqliteNTSOptionsExtension();
infrastructure.AddOrUpdateExtension(sqliteExtension.WithLoadSpatialite(true));
infrastructure.AddOrUpdateExtension(ntsExtension);
return optionsBuilder;
}
}
}

Просмотреть файл

@ -0,0 +1,54 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using GeoAPI.Geometries;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// SQLite and NetTopologySuite specific extension methods for <see cref="PropertyBuilder" />.
/// </summary>
public static class SqliteNTSPropertyBuilderExtensions
{
/// <summary>
/// Configures the dimension of the column that the property maps to when targeting SQLite.
/// </summary>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <param name="ordinates"> The dimension ordinates. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertyBuilder ForSqliteHasDimension(
[NotNull] this PropertyBuilder propertyBuilder,
Ordinates ordinates)
{
Check.NotNull(propertyBuilder, nameof(propertyBuilder));
string dimension = null;
if (ordinates.HasFlag(Ordinates.Z))
{
dimension += "Z";
}
if (ordinates.HasFlag(Ordinates.M))
{
dimension += "M";
}
propertyBuilder.Metadata.Sqlite().Dimension = dimension;
return propertyBuilder;
}
/// <summary>
/// Configures the dimension of the column that the property maps to when targeting SQLite.
/// </summary>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <param name="ordinates"> The dimension ordinates. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertyBuilder<TProperty> ForSqliteHasDimension<TProperty>(
[NotNull] this PropertyBuilder<TProperty> propertyBuilder,
Ordinates ordinates)
=> (PropertyBuilder<TProperty>)ForSqliteHasDimension((PropertyBuilder)propertyBuilder, ordinates);
}
}

Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
using Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal;
using Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NetTopologySuite;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// EntityFrameworkCore.Sqlite.NetTopologySuite extension methods for <see cref="IServiceCollection" />.
/// </summary>
public static class SqliteNTSServiceCollectionExtensions
{
/// <summary>
/// Adds the services required for NetTopologySuite support in the SQLite provider for Entity Framework.
/// </summary>
/// <param name="serviceCollection"> The <see cref="IServiceCollection" /> to add services to. </param>
/// <returns> The same service collection so that multiple calls can be chained. </returns>
public static IServiceCollection AddEntityFrameworkSqliteNetTopologySuite(
[NotNull] this IServiceCollection serviceCollection)
{
Check.NotNull(serviceCollection, nameof(serviceCollection));
serviceCollection.TryAddSingleton(NtsGeometryServices.Instance);
new EntityFrameworkRelationalServicesBuilder(serviceCollection)
.TryAddProviderSpecificServices(
x => x.TryAddSingletonEnumerable<IRelationalTypeMappingSourcePlugin, SqliteNTSTypeMappingSourcePlugin>()
.TryAddSingletonEnumerable<IMethodCallTranslatorPlugin, SqliteNTSMethodCallTranslatorPlugin>()
.TryAddSingletonEnumerable<IMemberTranslatorPlugin, SqliteNTSMemberTranslatorPlugin>());
return serviceCollection;
}
}
}

Просмотреть файл

@ -0,0 +1,64 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Sqlite.Internal;
using Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqliteNTSOptionsExtension : IDbContextOptionsExtension
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual string LogFragment => "using NetTopologySuite ";
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool ApplyServices(IServiceCollection services)
{
services.AddEntityFrameworkSqliteNetTopologySuite();
return false;
}
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual long GetServiceProviderHashCode() => 0;
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual void Validate(IDbContextOptions options)
{
var internalServiceProvider = options.FindExtension<CoreOptionsExtension>()?.InternalServiceProvider;
if (internalServiceProvider != null)
{
using (var scope = internalServiceProvider.CreateScope())
{
if (scope.ServiceProvider.GetService<IEnumerable<IRelationalTypeMappingSourcePlugin>>()
?.Any(s => s is SqliteNTSTypeMappingSourcePlugin) != true)
{
throw new InvalidOperationException(SqliteNTSStrings.NTSServicesMissing);
}
}
}
}
}
}

38
src/EFCore.Sqlite.NTS/Properties/SqliteNTSStrings.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,38 @@
// <auto-generated />
using System;
using System.Reflection;
using System.Resources;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Microsoft.EntityFrameworkCore.Sqlite.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static class SqliteNTSStrings
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.EntityFrameworkCore.Sqlite.Properties.SqliteNTSStrings", typeof(SqliteNTSStrings).GetTypeInfo().Assembly);
/// <summary>
/// UseNetTopologySuite requires AddEntityFrameworkSqliteNetTopologySuite to be called on the internal service provider used.
/// </summary>
public static string NTSServicesMissing
=> GetString("NTSServicesMissing");
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
return value;
}
}
}

Просмотреть файл

@ -0,0 +1,4 @@
<#
Session["ResourceFile"] = "SqliteNTSStrings.resx";
#>
<#@ include file="..\..\..\tools\Resources.tt" #>

Просмотреть файл

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="NTSServicesMissing" xml:space="preserve">
<value>UseNetTopologySuite requires AddEntityFrameworkSqliteNetTopologySuite to be called on the internal service provider used.</value>
</data>
</root>

Просмотреть файл

@ -0,0 +1,45 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqliteCurveMemberTranslator : IMemberTranslator
{
private static readonly IDictionary<MemberInfo, string> _memberToFunctionName = new Dictionary<MemberInfo, string>
{
{ typeof(ICurve).GetRuntimeProperty(nameof(ICurve.EndPoint)), "EndPoint" },
{ typeof(ICurve).GetRuntimeProperty(nameof(ICurve.IsClosed)), "IsClosed" },
{ typeof(ICurve).GetRuntimeProperty(nameof(ICurve.IsRing)), "IsRing" },
{ typeof(ICurve).GetRuntimeProperty(nameof(ICurve.StartPoint)), "StartPoint" }
};
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public Expression Translate(MemberExpression memberExpression)
{
var member = memberExpression.Member.OnInterface(typeof(ICurve));
if (_memberToFunctionName.TryGetValue(member, out var functionName))
{
return new SqlFunctionExpression(
functionName,
memberExpression.Type,
new[] { memberExpression.Expression });
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqliteGeometryCollectionMemberTranslator : IMemberTranslator
{
private static readonly MemberInfo _count = typeof(IGeometryCollection).GetRuntimeProperty(nameof(IGeometryCollection.Count));
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual Expression Translate(MemberExpression memberExpression)
{
var member = memberExpression.Member.OnInterface(typeof(IGeometryCollection));
if (Equals(member, _count))
{
return new SqlFunctionExpression(
"NumGeometries",
memberExpression.Type,
new[] { memberExpression.Expression });
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqliteGeometryCollectionMethodTranslator : IMethodCallTranslator
{
private static readonly MethodInfo _item = typeof(IGeometryCollection).GetRuntimeProperty("Item").GetMethod;
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual Expression Translate(MethodCallExpression methodCallExpression)
{
var method = methodCallExpression.Method.OnInterface(typeof(IGeometryCollection));
if (Equals(method, _item))
{
return new SqlFunctionExpression(
"GeometryN",
methodCallExpression.Type,
new[]
{
methodCallExpression.Object,
Expression.Add(methodCallExpression.Arguments[0], Expression.Constant(1))
});
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,54 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqliteGeometryMemberTranslator : IMemberTranslator
{
private static readonly IDictionary<MemberInfo, string> _memberToFunctionName = new Dictionary<MemberInfo, string>
{
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.Area)), "Area" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.Boundary)), "Boundary" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.Centroid)), "Centroid" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.Dimension)), "Dimension" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.Envelope)), "Envelope" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.IsEmpty)), "IsEmpty" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.IsSimple)), "IsSimple" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.IsValid)), "IsValid" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.Length)), "GLength" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.NumGeometries)), "NumGeometries" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.NumPoints)), "NumPoints" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.PointOnSurface)), "PointOnSurface" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.SRID)), "SRID" }
};
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public Expression Translate(MemberExpression memberExpression)
{
var member = memberExpression.Member.OnInterface(typeof(IGeometry));
if (_memberToFunctionName.TryGetValue(member, out var functionName))
{
return new SqlFunctionExpression(
functionName,
memberExpression.Type,
new[] { memberExpression.Expression });
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,81 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
using NetTopologySuite.Geometries;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqliteGeometryMethodTranslator : IMethodCallTranslator
{
private static readonly IDictionary<MethodInfo, string> _methodToFunctionName = new Dictionary<MethodInfo, string>
{
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.AsBinary), Type.EmptyTypes), "AsBinary" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.AsText), Type.EmptyTypes), "AsText" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Buffer), new[] { typeof(double) }), "Buffer" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Buffer), new[] { typeof(double), typeof(int) }), "Buffer" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Contains), new[] { typeof(IGeometry) }), "Contains" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.ConvexHull), Type.EmptyTypes), "ConvexHull" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Crosses), new[] { typeof(IGeometry) }), "Crosses" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.CoveredBy), new[] { typeof(IGeometry) }), "CoveredBy" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Covers), new[] { typeof(IGeometry) }), "Covers" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Difference), new[] { typeof(IGeometry) }), "Difference" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Disjoint), new[] { typeof(IGeometry) }), "Disjoint" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Distance), new[] { typeof(IGeometry) }), "Distance" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.EqualsTopologically), new[] { typeof(IGeometry) }), "Equals" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Intersection), new[] { typeof(IGeometry) }), "Intersection" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Intersects), new[] { typeof(IGeometry) }), "Intersects" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Overlaps), new[] { typeof(IGeometry) }), "Overlaps" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Relate), new[] { typeof(IGeometry), typeof(string) }), "Relate" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Reverse), Type.EmptyTypes), "ST_Reverse" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.SymmetricDifference), new[] { typeof(IGeometry) }), "SymDifference" },
{ typeof(Geometry).GetRuntimeMethod(nameof(Geometry.ToBinary), Type.EmptyTypes), "AsBinary" },
{ typeof(Geometry).GetRuntimeMethod(nameof(Geometry.ToText), Type.EmptyTypes), "AsText" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Touches), new[] { typeof(IGeometry) }), "Touches" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Union), new[] { typeof(IGeometry) }), "GUnion" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Within), new[] { typeof(IGeometry) }), "Within" }
};
private static readonly MethodInfo _getGeometryN = typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.GetGeometryN), new[] { typeof(int) });
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public Expression Translate(MethodCallExpression methodCallExpression)
{
var method = methodCallExpression.Method.OnInterface(typeof(IGeometry));
if (_methodToFunctionName.TryGetValue(method, out var functionName))
{
return new SqlFunctionExpression(
functionName,
methodCallExpression.Type,
new[] { methodCallExpression.Object }.Concat(methodCallExpression.Arguments));
}
if (Equals(method, _getGeometryN))
{
return new SqlFunctionExpression(
"GeometryN",
methodCallExpression.Type,
new[]
{
methodCallExpression.Object,
Expression.Add(methodCallExpression.Arguments[0], Expression.Constant(1))
});
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
using NetTopologySuite.Geometries;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqliteLineStringMemberTranslator : IMemberTranslator
{
private static readonly MemberInfo _count = typeof(LineString).GetRuntimeProperty(nameof(LineString.Count));
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public Expression Translate(MemberExpression memberExpression)
{
if (Equals(memberExpression.Member, _count))
{
return new SqlFunctionExpression(
"NumPoints",
memberExpression.Type,
new[] { memberExpression.Expression });
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,41 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqliteLineStringMethodTranslator : IMethodCallTranslator
{
private static readonly MethodInfo _getPointN = typeof(ILineString).GetRuntimeMethod(nameof(ILineString.GetPointN), new[] { typeof(int) });
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public Expression Translate(MethodCallExpression methodCallExpression)
{
var method = methodCallExpression.Method.OnInterface(typeof(ILineString));
if (Equals(method, _getPointN))
{
return new SqlFunctionExpression(
"PointN",
methodCallExpression.Type,
new[] {
methodCallExpression.Object,
Expression.Add(methodCallExpression.Arguments[0], Expression.Constant(1))
});
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqliteMultiCurveMemberTranslator : IMemberTranslator
{
private static readonly MemberInfo _isClosed = typeof(IMultiCurve).GetRuntimeProperty(nameof(IMultiCurve.IsClosed));
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public Expression Translate(MemberExpression memberExpression)
{
var member = memberExpression.Member.OnInterface(typeof(IMultiCurve));
if (Equals(member, _isClosed))
{
return new SqlFunctionExpression(
"IsClosed",
memberExpression.Type,
new[] { memberExpression.Expression });
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqliteNTSMemberTranslatorPlugin : IMemberTranslatorPlugin
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual IEnumerable<IMemberTranslator> Translators { get; }
= new IMemberTranslator[]
{
new SqliteCurveMemberTranslator(),
new SqliteGeometryMemberTranslator(),
new SqliteGeometryCollectionMemberTranslator(),
new SqliteLineStringMemberTranslator(),
new SqliteMultiCurveMemberTranslator(),
new SqlitePointMemberTranslator(),
new SqlitePolygonMemberTranslator()
};
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqliteNTSMethodCallTranslatorPlugin : IMethodCallTranslatorPlugin
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual IEnumerable<IMethodCallTranslator> Translators { get; } = new IMethodCallTranslator[]
{
new SqliteGeometryMethodTranslator(),
new SqliteGeometryCollectionMethodTranslator(),
new SqliteLineStringMethodTranslator(),
new SqlitePolygonMethodTranslator()
};
}
}

Просмотреть файл

@ -0,0 +1,45 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqlitePointMemberTranslator : IMemberTranslator
{
private static readonly IDictionary<MemberInfo, string> _memberToFunctionName = new Dictionary<MemberInfo, string>
{
{ typeof(IPoint).GetRuntimeProperty(nameof(IPoint.M)), "M" },
{ typeof(IPoint).GetRuntimeProperty(nameof(IPoint.X)), "X" },
{ typeof(IPoint).GetRuntimeProperty(nameof(IPoint.Y)), "Y" },
{ typeof(IPoint).GetRuntimeProperty(nameof(IPoint.Z)), "Z" }
};
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public Expression Translate(MemberExpression memberExpression)
{
var member = memberExpression.Member.OnInterface(typeof(IPoint));
if (_memberToFunctionName.TryGetValue(member, out var functionName))
{
return new SqlFunctionExpression(
functionName,
memberExpression.Type,
new[] { memberExpression.Expression });
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,43 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqlitePolygonMemberTranslator : IMemberTranslator
{
private static readonly IDictionary<MemberInfo, string> _memberToFunctionName = new Dictionary<MemberInfo, string>
{
{ typeof(IPolygon).GetRuntimeProperty(nameof(IPolygon.ExteriorRing)), "ExteriorRing" },
{ typeof(IPolygon).GetRuntimeProperty(nameof(IPolygon.NumInteriorRings)), "NumInteriorRing" }
};
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public Expression Translate(MemberExpression memberExpression)
{
var member = memberExpression.Member.OnInterface(typeof(IPolygon));
if (_memberToFunctionName.TryGetValue(member, out var functionName))
{
return new SqlFunctionExpression(
functionName,
memberExpression.Type,
new[] { memberExpression.Expression });
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqlitePolygonMethodTranslator : IMethodCallTranslator
{
private static readonly MethodInfo _getInteriorRingN = typeof(IPolygon).GetRuntimeMethod(nameof(IPolygon.GetInteriorRingN), new[] { typeof(int) });
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public Expression Translate(MethodCallExpression methodCallExpression)
{
var method = methodCallExpression.Method.OnInterface(typeof(IPolygon));
if (Equals(method, _getInteriorRingN))
{
return new SqlFunctionExpression(
"InteriorRingN",
methodCallExpression.Type,
new[]
{
methodCallExpression.Object,
Expression.Add(methodCallExpression.Arguments[0], Expression.Constant(1))
});
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,70 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Sqlite.Storage.ValueConversion.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using NetTopologySuite.IO;
namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqliteGeometryTypeMapping : RelationalTypeMapping
{
private readonly GaiaGeoReader _reader;
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public SqliteGeometryTypeMapping(Type clrType, GaiaGeoReader reader, string storeType)
: base(
new RelationalTypeMappingParameters(
new CoreTypeMappingParameters(
clrType,
new GeometryValueConverter(clrType, reader),
new GeometryValueComparer(clrType)),
storeType))
{
_reader = reader;
}
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected SqliteGeometryTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters)
{
}
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new SqliteGeometryTypeMapping(parameters);
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected override string GenerateNonNullSqlLiteral(object value)
{
// TODO: Avoid converting in the first place
var geometry = _reader.Read((byte[])value);
var srid = geometry.SRID;
// TODO: This won't emit M (see NetTopologySuite/NetTopologySuite#156)
var text = "'" + geometry.AsText() + "'";
return srid > 0
? $"GeomFromText({text}, {srid})"
: $"GeomFromText({text})";
}
}
}

Просмотреть файл

@ -0,0 +1,111 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using GeoAPI;
using GeoAPI.Geometries;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
using NetTopologySuite.IO;
namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class SqliteNTSTypeMappingSourcePlugin : IRelationalTypeMappingSourcePlugin
{
private static readonly Dictionary<string, Type> _storeTypeMappings
= new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
{
{ "GEOMETRY", typeof(IGeometry) },
{ "GEOMETRYCOLLECTION", typeof(IGeometryCollection) },
{ "LINESTRING", typeof(ILineString) },
{ "MULTILINESTRING", typeof(IMultiLineString) },
{ "MULTIPOINT", typeof(IMultiPoint) },
{ "MULTIPOLYGON", typeof(IMultiPolygon) },
{ "POINT", typeof(IPoint) },
{ "POLYGON", typeof(IPolygon) }
};
private readonly GaiaGeoReader _reader;
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public SqliteNTSTypeMappingSourcePlugin([NotNull] IGeometryServices geometryServices)
{
Check.NotNull(geometryServices, nameof(geometryServices));
_reader = new GaiaGeoReader(
geometryServices.DefaultCoordinateSequenceFactory,
geometryServices.DefaultPrecisionModel);
}
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo)
{
var clrType = mappingInfo.ClrType;
var storeTypeName = mappingInfo.StoreTypeName;
string defaultStoreType = null;
Type defaultClrType = null;
return (clrType != null && TryGetDefaultStoreType(clrType, out defaultStoreType)
|| storeTypeName != null && _storeTypeMappings.TryGetValue(storeTypeName, out defaultClrType))
? new SqliteGeometryTypeMapping(
clrType ?? defaultClrType ?? typeof(IGeometry),
_reader,
storeTypeName ?? defaultStoreType ?? "GEOMETRY")
: null;
}
private static bool TryGetDefaultStoreType(Type clrType, out string defaultStoreType)
{
if (typeof(ILineString).IsAssignableFrom(clrType))
{
defaultStoreType = "LINESTRING";
}
else if (typeof(IMultiLineString).IsAssignableFrom(clrType))
{
defaultStoreType = "MULTILINESTRING";
}
else if (typeof(IMultiPoint).IsAssignableFrom(clrType))
{
defaultStoreType = "MULTIPOINT";
}
else if (typeof(IMultiPolygon).IsAssignableFrom(clrType))
{
defaultStoreType = "MULTIPOLYGON";
}
else if (typeof(IPoint).IsAssignableFrom(clrType))
{
defaultStoreType = "POINT";
}
else if (typeof(IPolygon).IsAssignableFrom(clrType))
{
defaultStoreType = "POLYGON";
}
else if (typeof(IGeometryCollection).IsAssignableFrom(clrType))
{
defaultStoreType = "GEOMETRYCOLLECTION";
}
else if (typeof(IGeometry).IsAssignableFrom(clrType))
{
defaultStoreType = "GEOMETRY";
}
else
{
defaultStoreType = null;
}
return defaultStoreType != null;
}
}
}

Просмотреть файл

@ -0,0 +1,95 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using NetTopologySuite.IO;
namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.ValueConversion.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class GeometryValueConverter : ValueConverter
{
private static readonly GaiaGeoWriter _writer = new GaiaGeoWriter();
private Func<object, object> _convertToProvider;
private Func<object, object> _convertFromProvider;
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public GeometryValueConverter(Type type, GaiaGeoReader reader)
: base(
(Expression<Func<IGeometry, byte[]>>)(g => _writer.Write(g)),
GetConvertFromProviderExpression(type, reader))
{
ModelClrType = type;
}
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public override Func<object, object> ConvertToProvider
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _convertToProvider,
this,
x => Compile(x.ConvertToProviderExpression));
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public override Func<object, object> ConvertFromProvider
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _convertFromProvider,
this,
x => Compile(x.ConvertFromProviderExpression));
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public override Type ModelClrType { get; }
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public override Type ProviderClrType
=> typeof(byte[]);
private static LambdaExpression GetConvertFromProviderExpression(Type type, GaiaGeoReader reader)
{
var bytes = Expression.Parameter(typeof(byte[]), "blob");
Expression body = Expression.Call(
Expression.Constant(reader),
typeof(GaiaGeoReader).GetRuntimeMethod(nameof(GaiaGeoReader.Read), new[] { typeof(byte[]) }),
bytes);
if (!type.IsAssignableFrom(typeof(IGeometry)))
{
body = Expression.Convert(body, type);
}
return Expression.Lambda(body, bytes);
}
private static Func<object, object> Compile(LambdaExpression convertExpression)
{
var compiled = convertExpression.Compile();
return x => x != null
? compiled.DynamicInvoke(x)
: null;
}
}
}

Просмотреть файл

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
@ -12,10 +13,17 @@ namespace Microsoft.EntityFrameworkCore.Query
protected override ITestStoreFactory TestStoreFactory
=> SqlServerTestStoreFactory.Instance;
// TODO: Use UseNetTopologySuite instead?
protected override IServiceCollection AddServices(IServiceCollection serviceCollection)
=> base.AddServices(serviceCollection)
.AddEntityFrameworkSqlServerNetTopologySuite();
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
{
var optionsBuilder = base.AddOptions(builder);
new SqlServerDbContextOptionsBuilder(optionsBuilder).UseNetTopologySuite();
return optionsBuilder;
}
}
#endif
}

Просмотреть файл

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
@ -12,10 +13,17 @@ namespace Microsoft.EntityFrameworkCore
protected override ITestStoreFactory TestStoreFactory
=> SqlServerTestStoreFactory.Instance;
// TODO: Use UseNetTopologySuite instead?
protected override IServiceCollection AddServices(IServiceCollection serviceCollection)
=> base.AddServices(serviceCollection)
.AddEntityFrameworkSqlServerNetTopologySuite();
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
{
var optionsBuilder = base.AddOptions(builder);
new SqlServerDbContextOptionsBuilder(optionsBuilder).UseNetTopologySuite();
return optionsBuilder;
}
}
#endif
}

Просмотреть файл

@ -23,7 +23,7 @@
</PropertyGroup>
<ItemGroup Condition="'$(FunctionalTests_PackageVersion)' == '0.0.0'">
<ProjectReference Include="..\..\src\EFCore.Sqlite.Core\EFCore.Sqlite.Core.csproj" />
<ProjectReference Include="..\..\src\EFCore.Sqlite.NTS\EFCore.Sqlite.NTS.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(FunctionalTests_PackageVersion)' != '0.0.0'">

Просмотреть файл

@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.EntityFrameworkCore.Query
{
#if !Test21
public class SpatialQuerySqliteFixture : SpatialQueryRelationalFixture
{
protected override ITestStoreFactory TestStoreFactory
=> SqliteTestStoreFactory.Instance;
protected override IServiceCollection AddServices(IServiceCollection serviceCollection)
=> base.AddServices(serviceCollection)
.AddEntityFrameworkSqliteNetTopologySuite();
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
{
var optionsBuilder = base.AddOptions(builder);
new SqliteDbContextOptionsBuilder(optionsBuilder).UseNetTopologySuite();
return optionsBuilder;
}
}
#endif
}

Просмотреть файл

@ -0,0 +1,512 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit.Abstractions;
namespace Microsoft.EntityFrameworkCore.Query
{
#if !Test21
[SpatialiteRequired]
public class SpatialQuerySqliteTest : SpatialQueryTestBase<SpatialQuerySqliteFixture>
{
public SpatialQuerySqliteTest(SpatialQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper)
: base(fixture)
{
Fixture.TestSqlLoggerFactory.Clear();
//Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
public override async Task Area(bool isAsync)
{
await base.Area(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Area(""e"".""Polygon"") AS ""Area""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task AsBinary(bool isAsync)
{
await base.AsBinary(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", AsBinary(""e"".""Point"") AS ""Binary""
FROM ""PointEntity"" AS ""e""");
}
public override async Task AsText(bool isAsync)
{
await base.AsText(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", AsText(""e"".""Point"") AS ""Text""
FROM ""PointEntity"" AS ""e""");
}
public override async Task Boundary(bool isAsync)
{
await base.Boundary(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Boundary(""e"".""Polygon"") AS ""Boundary""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task Buffer(bool isAsync)
{
await base.Buffer(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Buffer(""e"".""Polygon"", 1.0) AS ""Buffer""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task Buffer_quadrantSegments(bool isAsync)
{
await base.Buffer_quadrantSegments(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Buffer(""e"".""Polygon"", 1.0, 8) AS ""Buffer""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task Centroid(bool isAsync)
{
await base.Centroid(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Centroid(""e"".""Polygon"") AS ""Centroid""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task Contains(bool isAsync)
{
await base.Contains(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Contains(""e"".""Polygon"", GeomFromText('POINT (0.5 0.25)')) AS ""Contains""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task ConvexHull(bool isAsync)
{
await base.ConvexHull(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", ConvexHull(""e"".""Polygon"") AS ""ConvexHull""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task IGeometryCollection_Count(bool isAsync)
{
await base.IGeometryCollection_Count(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", NumGeometries(""e"".""MultiLineString"") AS ""Count""
FROM ""MultiLineStringEntity"" AS ""e""");
}
public override async Task LineString_Count(bool isAsync)
{
await base.LineString_Count(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", NumPoints(""e"".""LineString"") AS ""Count""
FROM ""LineStringEntity"" AS ""e""");
}
public override async Task CoveredBy(bool isAsync)
{
await base.CoveredBy(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", CoveredBy(""e"".""Point"", GeomFromText('POLYGON ((-1 -1, -1 2, 2 2, 2 -1, -1 -1))')) AS ""CoveredBy""
FROM ""PointEntity"" AS ""e""");
}
public override async Task Covers(bool isAsync)
{
await base.Covers(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Covers(""e"".""Polygon"", GeomFromText('POINT (0.5 0.25)')) AS ""Covers""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task Crosses(bool isAsync)
{
await base.Crosses(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Crosses(""e"".""LineString"", GeomFromText('LINESTRING (0.5 -0.5, 0.5 0.5)')) AS ""Crosses""
FROM ""LineStringEntity"" AS ""e""");
}
public override async Task Difference(bool isAsync)
{
await base.Difference(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Difference(""e"".""Polygon"", GeomFromText('POLYGON ((0 0, 1 0, 1 1, 0 0))')) AS ""Difference""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task Dimension(bool isAsync)
{
await base.Dimension(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Dimension(""e"".""Point"") AS ""Dimension""
FROM ""PointEntity"" AS ""e""");
}
public override async Task Disjoint(bool isAsync)
{
await base.Disjoint(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Disjoint(""e"".""Polygon"", GeomFromText('POINT (1 0)')) AS ""Disjoint""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task Distance(bool isAsync)
{
await base.Distance(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Distance(""e"".""Point"", GeomFromText('POINT (0 1)')) AS ""Distance""
FROM ""PointEntity"" AS ""e""");
}
public override async Task EndPoint(bool isAsync)
{
await base.EndPoint(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", EndPoint(""e"".""LineString"") AS ""EndPoint""
FROM ""LineStringEntity"" AS ""e""");
}
public override async Task Envelope(bool isAsync)
{
await base.Envelope(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Envelope(""e"".""Polygon"") AS ""Envelope""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task EqualsTopologically(bool isAsync)
{
await base.EqualsTopologically(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Equals(""e"".""Point"", GeomFromText('POINT (0 0)')) AS ""EqualsTopologically""
FROM ""PointEntity"" AS ""e""");
}
public override async Task ExteriorRing(bool isAsync)
{
await base.ExteriorRing(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", ExteriorRing(""e"".""Polygon"") AS ""ExteriorRing""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task GetGeometryN(bool isAsync)
{
await base.GetGeometryN(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", GeometryN(""e"".""MultiLineString"", 0 + 1) AS ""Geometry0""
FROM ""MultiLineStringEntity"" AS ""e""");
}
public override async Task GetInteriorRingN(bool isAsync)
{
await base.GetInteriorRingN(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", InteriorRingN(""e"".""Polygon"", 0 + 1) AS ""InteriorRing0""
FROM ""PolygonEntity"" AS ""e""
WHERE NumInteriorRing(""e"".""Polygon"") > 0");
}
public override async Task GetPointN(bool isAsync)
{
await base.GetPointN(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", PointN(""e"".""LineString"", 0 + 1) AS ""Point0""
FROM ""LineStringEntity"" AS ""e""");
}
public override async Task Intersection(bool isAsync)
{
await base.Intersection(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Intersection(""e"".""Polygon"", GeomFromText('POLYGON ((0 0, 1 0, 1 1, 0 0))')) AS ""Intersection""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task Intersects(bool isAsync)
{
await base.Intersects(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Intersects(""e"".""LineString"", GeomFromText('LINESTRING (0.5 -0.5, 0.5 0.5)')) AS ""Intersects""
FROM ""LineStringEntity"" AS ""e""");
}
public override async Task ICurve_IsClosed(bool isAsync)
{
await base.ICurve_IsClosed(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", IsClosed(""e"".""LineString"") AS ""IsClosed""
FROM ""LineStringEntity"" AS ""e""");
}
public override async Task IMultiCurve_IsClosed(bool isAsync)
{
await base.IMultiCurve_IsClosed(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", IsClosed(""e"".""MultiLineString"") AS ""IsClosed""
FROM ""MultiLineStringEntity"" AS ""e""");
}
public override async Task IsEmpty(bool isAsync)
{
await base.IsEmpty(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", IsEmpty(""e"".""MultiLineString"") AS ""IsEmpty""
FROM ""MultiLineStringEntity"" AS ""e""");
}
public override async Task IsRing(bool isAsync)
{
await base.IsRing(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", IsRing(""e"".""LineString"") AS ""IsRing""
FROM ""LineStringEntity"" AS ""e""");
}
public override async Task IsSimple(bool isAsync)
{
await base.IsSimple(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", IsSimple(""e"".""LineString"") AS ""IsSimple""
FROM ""LineStringEntity"" AS ""e""");
}
public override async Task IsValid(bool isAsync)
{
await base.IsValid(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", IsValid(""e"".""Point"") AS ""IsValid""
FROM ""PointEntity"" AS ""e""");
}
public override async Task Item(bool isAsync)
{
await base.Item(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", GeometryN(""e"".""MultiLineString"", 0 + 1) AS ""Item0""
FROM ""MultiLineStringEntity"" AS ""e""");
}
public override async Task Length(bool isAsync)
{
await base.Length(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", GLength(""e"".""LineString"") AS ""Length""
FROM ""LineStringEntity"" AS ""e""");
}
public override async Task M(bool isAsync)
{
await base.M(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", M(""e"".""Point"") AS ""M""
FROM ""PointEntity"" AS ""e""");
}
public override async Task NumGeometries(bool isAsync)
{
await base.NumGeometries(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", NumGeometries(""e"".""MultiLineString"") AS ""NumGeometries""
FROM ""MultiLineStringEntity"" AS ""e""");
}
public override async Task NumInteriorRings(bool isAsync)
{
await base.NumInteriorRings(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", NumInteriorRing(""e"".""Polygon"") AS ""NumInteriorRings""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task NumPoints(bool isAsync)
{
await base.NumPoints(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", NumPoints(""e"".""LineString"") AS ""NumPoints""
FROM ""LineStringEntity"" AS ""e""");
}
public override async Task Overlaps(bool isAsync)
{
await base.Overlaps(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Overlaps(""e"".""Polygon"", GeomFromText('POLYGON ((0 0, 1 0, 1 1, 0 0))')) AS ""Overlaps""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task PointOnSurface(bool isAsync)
{
await base.PointOnSurface(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", PointOnSurface(""e"".""Polygon"") AS ""PointOnSurface"", ""e"".""Polygon""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task Relate(bool isAsync)
{
await base.Relate(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Relate(""e"".""Polygon"", GeomFromText('POLYGON ((0 0, 1 0, 1 1, 0 0))'), '212111212') AS ""Relate""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task Reverse(bool isAsync)
{
await base.Reverse(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", ST_Reverse(""e"".""LineString"") AS ""Reverse""
FROM ""LineStringEntity"" AS ""e""");
}
public override async Task SRID(bool isAsync)
{
await base.SRID(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", SRID(""e"".""Point"") AS ""SRID""
FROM ""PointEntity"" AS ""e""");
}
public override async Task StartPoint(bool isAsync)
{
await base.StartPoint(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", StartPoint(""e"".""LineString"") AS ""StartPoint""
FROM ""LineStringEntity"" AS ""e""");
}
public override async Task SymmetricDifference(bool isAsync)
{
await base.SymmetricDifference(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", SymDifference(""e"".""Polygon"", GeomFromText('POLYGON ((0 0, 1 0, 1 1, 0 0))')) AS ""SymmetricDifference""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task ToBinary(bool isAsync)
{
await base.ToBinary(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", AsBinary(""e"".""Point"") AS ""Binary""
FROM ""PointEntity"" AS ""e""");
}
public override async Task ToText(bool isAsync)
{
await base.ToText(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", AsText(""e"".""Point"") AS ""Text""
FROM ""PointEntity"" AS ""e""");
}
public override async Task Touches(bool isAsync)
{
await base.Touches(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Touches(""e"".""Polygon"", GeomFromText('POLYGON ((0 1, 1 1, 1 0, 0 1))')) AS ""Touches""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task Union(bool isAsync)
{
await base.Union(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", GUnion(""e"".""Polygon"", GeomFromText('POLYGON ((0 0, 1 0, 1 1, 0 0))')) AS ""Union""
FROM ""PolygonEntity"" AS ""e""");
}
public override async Task Within(bool isAsync)
{
await base.Within(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Within(""e"".""Point"", GeomFromText('POLYGON ((-1 -1, -1 2, 2 2, 2 -1, -1 -1))')) AS ""Within""
FROM ""PointEntity"" AS ""e""");
}
public override async Task X(bool isAsync)
{
await base.X(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", X(""e"".""Point"") AS ""X""
FROM ""PointEntity"" AS ""e""");
}
public override async Task Y(bool isAsync)
{
await base.Y(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Y(""e"".""Point"") AS ""Y""
FROM ""PointEntity"" AS ""e""");
}
public override async Task Z(bool isAsync)
{
await base.Z(isAsync);
AssertSql(
@"SELECT ""e"".""Id"", Z(""e"".""Point"") AS ""Z""
FROM ""PointEntity"" AS ""e""");
}
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
#endif
}

Просмотреть файл

@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.EntityFrameworkCore
{
#if !Test21
public class SpatialSqliteFixture : SpatialFixtureBase
{
protected override ITestStoreFactory TestStoreFactory
=> SqliteTestStoreFactory.Instance;
protected override IServiceCollection AddServices(IServiceCollection serviceCollection)
=> base.AddServices(serviceCollection)
.AddEntityFrameworkSqliteNetTopologySuite();
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
{
var optionsBuilder = base.AddOptions(builder);
new SqliteDbContextOptionsBuilder(optionsBuilder).UseNetTopologySuite();
return optionsBuilder;
}
}
#endif
}

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.EntityFrameworkCore.TestUtilities;
namespace Microsoft.EntityFrameworkCore
{
#if !Test21
[SpatialiteRequired]
public class SpatialSqliteTest : SpatialTestBase<SpatialSqliteFixture>
{
public SpatialSqliteTest(SpatialSqliteFixture fixture)
: base(fixture)
{
}
}
#endif
}

Просмотреть файл

@ -15,11 +15,7 @@ namespace Microsoft.EntityFrameworkCore
typeof(AsyncFromSqlSprocQueryTestBase<>),
typeof(FromSqlSprocQueryTestBase<>),
typeof(SqlExecutorTestBase<>),
typeof(UdfDbFunctionTestBase<>),
#if !Test21
typeof(SpatialQueryTestBase<>),
typeof(SpatialTestBase<>)
#endif
typeof(UdfDbFunctionTestBase<>)
};
protected override Assembly TargetAssembly { get; } = typeof(SqliteComplianceTest).Assembly;

Просмотреть файл

@ -0,0 +1,30 @@
using System;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.TestUtilities.Xunit;
namespace Microsoft.EntityFrameworkCore.TestUtilities
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class SpatialiteRequiredAttribute : Attribute, ITestCondition
{
private static readonly Lazy<bool> _loaded
= new Lazy<bool>(
() =>
{
using (var connection = new SqliteConnection("Data Source=:memory:"))
{
connection.Open();
connection.EnableExtensions();
return SpatialiteLoader.TryLoad(connection);
}
});
public bool IsMet
=> _loaded.Value;
public string SkipReason
=> "mod_spatialite not found. Install it to run this test.";
}
}

Просмотреть файл

@ -4,6 +4,7 @@
using System;
using System.Data.Common;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Microsoft.EntityFrameworkCore.TestUtilities
{
@ -76,6 +77,9 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities
{
Connection.Open();
((SqliteConnection)Connection).EnableExtensions();
SpatialiteLoader.TryLoad(Connection);
using (var command = Connection.CreateCommand())
{
command.CommandText = "PRAGMA foreign_keys=ON;";