SqlServer: Support spatial data via NTS

There's still a lot to do (e.g. support mapping to GEOGRAPHY), bit it's at a point where we can start gathering feedback while we continue iterating on it.

To get started, install the `Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite` package, call `.UseSqlServer(..., x => x.UseNetTopologySuite())`, and add `Geometry` properties to your model.

Part of #1100
This commit is contained in:
Brice Lambson 2018-08-24 16:10:17 -07:00
Родитель 35bedd676d
Коммит da777ce517
74 изменённых файлов: 3460 добавлений и 63 удалений

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

@ -62,6 +62,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Tests", "test\EFCore
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Analyzers", "src\EFCore.Analyzers\EFCore.Analyzers.csproj", "{948D3EDA-ECF0-4367-B157-BF770F752A8B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.SqlServer.NTS", "src\EFCore.SqlServer.NTS\EFCore.SqlServer.NTS.csproj", "{F53EB45F-84D7-4520-B813-8916C0C756BB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -152,6 +154,10 @@ Global
{948D3EDA-ECF0-4367-B157-BF770F752A8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{948D3EDA-ECF0-4367-B157-BF770F752A8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{948D3EDA-ECF0-4367-B157-BF770F752A8B}.Release|Any CPU.Build.0 = Release|Any CPU
{F53EB45F-84D7-4520-B813-8916C0C756BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -178,6 +184,7 @@ Global
{7D1C4E40-0DE6-4C50-AB84-CA8647EA92DF} = {258D5057-81B9-40EC-A872-D21E27452749}
{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}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EC8BCF1F-A206-4420-A292-3E3F2A4CDC54}

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

@ -97,6 +97,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Cosmos.Sql.Functiona
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Cosmos.Sql.Tests", "test\EFCore.Cosmos.Sql.Tests\EFCore.Cosmos.Sql.Tests.csproj", "{B4E155E5-C0B8-4680-92A0-A0DE745486B1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.SqlServer.NTS", "src\EFCore.SqlServer.NTS\EFCore.SqlServer.NTS.csproj", "{F53EB45F-84D7-4520-B813-8916C0C756BB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -239,6 +241,10 @@ Global
{B4E155E5-C0B8-4680-92A0-A0DE745486B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B4E155E5-C0B8-4680-92A0-A0DE745486B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4E155E5-C0B8-4680-92A0-A0DE745486B1}.Release|Any CPU.Build.0 = Release|Any CPU
{F53EB45F-84D7-4520-B813-8916C0C756BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -281,6 +287,7 @@ Global
{6C115EF9-519E-4A4E-BD02-3801777BFEBA} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{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}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {285A5EB4-BCF4-40EB-B9E1-DF6DBCB5E705}

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

@ -44,7 +44,7 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal
return new InMemoryTypeMapping(clrType, new GeometryValueComparer(clrType));
}
return null;
return base.FindMapping(mappingInfo);
}
}
}

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

@ -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;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.EntityFrameworkCore.Query
{
public abstract class SpatialQueryRelationalFixture : SpatialQueryFixtureBase
{
public new RelationalTestStore TestStore
=> (RelationalTestStore)base.TestStore;
public TestSqlLoggerFactory TestSqlLoggerFactory
=> (TestSqlLoggerFactory)ServiceProvider.GetRequiredService<ILoggerFactory>();
}
}

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

@ -93,7 +93,10 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure
{ typeof(IRelationalConnection), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IRelationalDatabaseCreator), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IHistoryRepository), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(INamedConnectionStringResolver), new ServiceCharacteristics(ServiceLifetime.Scoped) }
{ typeof(INamedConnectionStringResolver), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IRelationalTypeMappingSourcePlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) },
{ typeof(IMethodCallTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) },
{ typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) }
};
/// <summary>

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

@ -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.
namespace Microsoft.EntityFrameworkCore.Infrastructure
{
/// <summary>
/// Explicitly implemented by <see cref="RelationalDbContextOptionsBuilder{TBuilder, TExtension}" /> to hide
/// methods that are used by database provider extension methods but not intended to be called by application
/// developers.
/// </summary>
public interface IRelationalDbContextOptionsBuilderInfrastructure
{
/// <summary>
/// Gets the core options builder.
/// </summary>
DbContextOptionsBuilder OptionsBuilder { get; }
}
}

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

@ -18,7 +18,7 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure
/// particular relational database provider.
/// </para>
/// </summary>
public abstract class RelationalDbContextOptionsBuilder<TBuilder, TExtension>
public abstract class RelationalDbContextOptionsBuilder<TBuilder, TExtension> : IRelationalDbContextOptionsBuilderInfrastructure
where TBuilder : RelationalDbContextOptionsBuilder<TBuilder, TExtension>
where TExtension : RelationalOptionsExtension, new()
{
@ -38,6 +38,8 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure
/// </summary>
protected virtual DbContextOptionsBuilder OptionsBuilder { get; }
DbContextOptionsBuilder IRelationalDbContextOptionsBuilderInfrastructure.OptionsBuilder => OptionsBuilder;
/// <summary>
/// Configures the maximum number of statements that will be included in commands sent to the database
/// during <see cref="DbContext.SaveChanges()" />.

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

@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Internal;
@ -64,8 +65,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
new FallbackRelationalTypeMappingSource(
new TypeMappingSourceDependencies(
new ValueConverterSelector(
new ValueConverterSelectorDependencies())),
new RelationalTypeMappingSourceDependencies(),
new ValueConverterSelectorDependencies()),
Enumerable.Empty<ITypeMappingSourcePlugin>()),
new RelationalTypeMappingSourceDependencies(
Enumerable.Empty<IRelationalTypeMappingSourcePlugin>()),
typeMapper),
null,
currentContext,
@ -153,8 +156,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
new FallbackRelationalTypeMappingSource(
new TypeMappingSourceDependencies(
new ValueConverterSelector(
new ValueConverterSelectorDependencies())),
new RelationalTypeMappingSourceDependencies(),
new ValueConverterSelectorDependencies()),
Enumerable.Empty<ITypeMappingSourcePlugin>()),
new RelationalTypeMappingSourceDependencies(
Enumerable.Empty<IRelationalTypeMappingSourcePlugin>()),
typeMapper),
Logger, Context, SetFinder, typeMapper);

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

@ -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 System.Collections.Generic;
namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
{
/// <summary>
/// Represents plugin member translators.
/// </summary>
public interface IMemberTranslatorPlugin
{
/// <summary>
/// Gets the member translators.
/// </summary>
IEnumerable<IMemberTranslator> Translators { get; }
}
}

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

@ -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 System.Collections.Generic;
namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
{
/// <summary>
/// Represents plugin method call translators.
/// </summary>
public interface IMethodCallTranslatorPlugin
{
/// <summary>
/// Gets the method call translators.
/// </summary>
IEnumerable<IMethodCallTranslator> Translators { get; }
}
}

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

@ -2,6 +2,7 @@
// 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;
using System.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Utilities;
@ -14,6 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
/// </summary>
public abstract class RelationalCompositeMemberTranslator : IMemberTranslator
{
private readonly List<IMemberTranslator> _plugins = new List<IMemberTranslator>();
private readonly List<IMemberTranslator> _translators = new List<IMemberTranslator>();
/// <summary>
@ -23,6 +25,8 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
protected RelationalCompositeMemberTranslator([NotNull] RelationalCompositeMemberTranslatorDependencies dependencies)
{
Check.NotNull(dependencies, nameof(dependencies));
_plugins.AddRange(dependencies.Plugins.SelectMany(p => p.Translators));
}
/// <summary>
@ -33,19 +37,9 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
/// A SQL expression representing the translated MemberExpression.
/// </returns>
public virtual Expression Translate(MemberExpression memberExpression)
{
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var translator in _translators)
{
var translatedMember = translator.Translate(memberExpression);
if (translatedMember != null)
{
return translatedMember;
}
}
return null;
}
=> Enumerable.Concat(_plugins, _translators)
.Select(translator => translator.Translate(memberExpression))
.FirstOrDefault(translatedMember => translatedMember != null);
/// <summary>
/// Adds additional translators to the dispatch list.

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

@ -1,6 +1,10 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
{
/// <summary>
@ -35,9 +39,26 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
/// the constructor at any point in this process.
/// </para>
/// </summary>
// ReSharper disable once EmptyConstructor
public RelationalCompositeMemberTranslatorDependencies()
/// <param name="plugins"> The plugins. </param>
public RelationalCompositeMemberTranslatorDependencies([NotNull] IEnumerable<IMemberTranslatorPlugin> plugins)
{
Check.NotNull(plugins, nameof(plugins));
Plugins = plugins;
}
/// <summary>
/// Gets the plugins.
/// </summary>
public IEnumerable<IMemberTranslatorPlugin> Plugins { get; }
/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
/// <param name="plugins"> A replacement for the current dependency of this type. </param>
/// <returns> A new parameter object with the given service replaced. </returns>
public RelationalCompositeMemberTranslatorDependencies With(
[NotNull] IEnumerable<IMemberTranslatorPlugin> plugins)
=> new RelationalCompositeMemberTranslatorDependencies(plugins);
}
}

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

@ -17,6 +17,7 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
/// </summary>
public abstract class RelationalCompositeMethodCallTranslator : ICompositeMethodCallTranslator
{
private readonly List<IMethodCallTranslator> _plugins = new List<IMethodCallTranslator>();
private readonly List<IMethodCallTranslator> _methodCallTranslators;
/// <summary>
@ -30,6 +31,8 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
Dependencies = dependencies;
_plugins.AddRange(dependencies.Plugins.SelectMany(p => p.Translators));
_methodCallTranslators
= new List<IMethodCallTranslator>
{
@ -56,7 +59,7 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
/// </returns>
public virtual Expression Translate(MethodCallExpression methodCallExpression, IModel model)
=> ((IMethodCallTranslator)model.Relational().FindDbFunction(methodCallExpression.Method))?.Translate(methodCallExpression)
?? _methodCallTranslators
?? Enumerable.Concat(_plugins, _methodCallTranslators)
.Select(translator => translator.Translate(methodCallExpression))
.FirstOrDefault(translatedMethodCall => translatedMethodCall != null);

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

@ -1,8 +1,10 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
{
@ -39,9 +41,16 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
/// </para>
/// </summary>
/// <param name="logger"> A logger. </param>
public RelationalCompositeMethodCallTranslatorDependencies([NotNull] IDiagnosticsLogger<DbLoggerCategory.Query> logger)
/// <param name="plugins"> The plugins. </param>
public RelationalCompositeMethodCallTranslatorDependencies(
[NotNull] IDiagnosticsLogger<DbLoggerCategory.Query> logger,
[NotNull] IEnumerable<IMethodCallTranslatorPlugin> plugins)
{
Check.NotNull(logger, nameof(logger));
Check.NotNull(plugins, nameof(plugins));
Logger = logger;
Plugins = plugins;
}
/// <summary>
@ -49,12 +58,26 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
/// </summary>
public IDiagnosticsLogger<DbLoggerCategory.Query> Logger { get; }
/// <summary>
/// Gets the plugins.
/// </summary>
public IEnumerable<IMethodCallTranslatorPlugin> Plugins { get; }
/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
/// <param name="logger"> A replacement for the current dependency of this type. </param>
/// <returns> A new parameter object with the given service replaced. </returns>
public RelationalCompositeMethodCallTranslatorDependencies With([NotNull] IDiagnosticsLogger<DbLoggerCategory.Query> logger)
=> new RelationalCompositeMethodCallTranslatorDependencies(logger);
=> new RelationalCompositeMethodCallTranslatorDependencies(logger, Plugins);
/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
/// <param name="plugins"> A replacement for the current dependency of this type. </param>
/// <returns> A new parameter object with the given service replaced. </returns>
public RelationalCompositeMethodCallTranslatorDependencies With(
[NotNull] IEnumerable<IMethodCallTranslatorPlugin> plugins)
=> new RelationalCompositeMethodCallTranslatorDependencies(Logger, plugins);
}
}

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

@ -693,10 +693,21 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionVisitors
|| (newExpression != null
&& newExpression.Type == memberExpression.Expression.Type))
{
var newMemberExpression
= newExpression != memberExpression.Expression
? Expression.Property(newExpression, memberExpression.Member.Name)
: memberExpression;
MemberExpression newMemberExpression;
if (newExpression != memberExpression.Expression)
{
if (memberExpression.Member.DeclaringType.IsInterface
&& newExpression.Type != memberExpression.Member.DeclaringType)
{
newExpression = Expression.Convert(newExpression, memberExpression.Member.DeclaringType);
}
newMemberExpression = Expression.Property(newExpression, memberExpression.Member.Name);
}
else
{
newMemberExpression = memberExpression;
}
var translatedExpression = _memberTranslator.Translate(newMemberExpression);

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

@ -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.
namespace Microsoft.EntityFrameworkCore.Storage
{
/// <summary>
/// Represents a plugin relational type mapping source.
/// </summary>
public interface IRelationalTypeMappingSourcePlugin
{
/// <summary>
/// Finds a type mapping for the given info.
/// </summary>
/// <param name="mappingInfo"> The mapping info to use to create the mapping. </param>
/// <returns> The type mapping, or <c>null</c> if none could be found. </returns>
RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo);
}
}

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

@ -78,7 +78,7 @@ namespace Microsoft.EntityFrameworkCore.Storage.Internal
mapping = mapping.Clone(newStoreName, mapping.Size);
}
return mapping;
return mapping ?? base.FindMapping(mappingInfo);
}
private RelationalTypeMapping FindMappingForProperty(in RelationalTypeMappingInfo mappingInfo)

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.Internal;
@ -30,8 +31,10 @@ namespace Microsoft.EntityFrameworkCore.Storage
: new FallbackRelationalTypeMappingSource(
new TypeMappingSourceDependencies(
new ValueConverterSelector(
new ValueConverterSelectorDependencies())),
new RelationalTypeMappingSourceDependencies(),
new ValueConverterSelectorDependencies()),
Enumerable.Empty<ITypeMappingSourcePlugin>()),
new RelationalTypeMappingSourceDependencies(
Enumerable.Empty<IRelationalTypeMappingSourcePlugin>()),
typeMapper)
.GetMappingForValue(value);
@ -48,8 +51,10 @@ namespace Microsoft.EntityFrameworkCore.Storage
=> new FallbackRelationalTypeMappingSource(
new TypeMappingSourceDependencies(
new ValueConverterSelector(
new ValueConverterSelectorDependencies())),
new RelationalTypeMappingSourceDependencies(),
new ValueConverterSelectorDependencies()),
Enumerable.Empty<ITypeMappingSourcePlugin>()),
new RelationalTypeMappingSourceDependencies(
Enumerable.Empty<IRelationalTypeMappingSourcePlugin>()),
typeMapper)
.GetMapping(property);
@ -66,8 +71,10 @@ namespace Microsoft.EntityFrameworkCore.Storage
=> new FallbackRelationalTypeMappingSource(
new TypeMappingSourceDependencies(
new ValueConverterSelector(
new ValueConverterSelectorDependencies())),
new RelationalTypeMappingSourceDependencies(),
new ValueConverterSelectorDependencies()),
Enumerable.Empty<ITypeMappingSourcePlugin>()),
new RelationalTypeMappingSourceDependencies(
Enumerable.Empty<IRelationalTypeMappingSourcePlugin>()),
typeMapper)
.GetMapping(clrType);
@ -89,8 +96,10 @@ namespace Microsoft.EntityFrameworkCore.Storage
=> new FallbackRelationalTypeMappingSource(
new TypeMappingSourceDependencies(
new ValueConverterSelector(
new ValueConverterSelectorDependencies())),
new RelationalTypeMappingSourceDependencies(),
new ValueConverterSelectorDependencies()),
Enumerable.Empty<ITypeMappingSourcePlugin>()),
new RelationalTypeMappingSourceDependencies(
Enumerable.Empty<IRelationalTypeMappingSourcePlugin>()),
typeMapper)
.GetMapping(typeName);
}

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

@ -58,7 +58,19 @@ namespace Microsoft.EntityFrameworkCore.Storage
/// </summary>
/// <param name="mappingInfo"> The mapping info to use to create the mapping. </param>
/// <returns> The type mapping, or <c>null</c> if none could be found. </returns>
protected abstract RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo);
protected virtual RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo)
{
foreach (var plugin in RelationalDependencies.Plugins)
{
var typeMapping = plugin.FindMapping(mappingInfo);
if (typeMapping != null)
{
return typeMapping;
}
}
return null;
}
/// <summary>
/// Dependencies used to create this <see cref="RelationalTypeMappingSource" />

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

@ -1,6 +1,10 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore.Storage
{
/// <summary>
@ -35,9 +39,27 @@ namespace Microsoft.EntityFrameworkCore.Storage
/// the constructor at any point in this process.
/// </para>
/// </summary>
// ReSharper disable once EmptyConstructor
public RelationalTypeMappingSourceDependencies()
/// <param name="plugins"> The plugins. </param>
public RelationalTypeMappingSourceDependencies(
[NotNull] IEnumerable<IRelationalTypeMappingSourcePlugin> plugins)
{
Check.NotNull(plugins, nameof(plugins));
Plugins = plugins;
}
/// <summary>
/// Gets the plugins.
/// </summary>
public IEnumerable<IRelationalTypeMappingSourcePlugin> Plugins { get; }
/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
/// <param name="plugins"> A replacement for the current dependency of this type. </param>
/// <returns> A new parameter object with the given service replaced. </returns>
public RelationalTypeMappingSourceDependencies With(
[NotNull] IEnumerable<IRelationalTypeMappingSourcePlugin> plugins)
=> new RelationalTypeMappingSourceDependencies(plugins);
}
}

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

@ -218,5 +218,15 @@
"TypeId": "public interface Microsoft.EntityFrameworkCore.Storage.ISqlGenerationHelper",
"MemberId": "System.Void GenerateParameterNamePlaceholder(System.Text.StringBuilder builder, System.String name)",
"Kind": "Addition"
},
{
"TypeId": "public sealed class Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.RelationalCompositeMemberTranslatorDependencies",
"MemberId": "public .ctor()",
"Kind": "Removal"
},
{
"TypeId": "public sealed class Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.RelationalCompositeMethodCallTranslatorDependencies",
"MemberId": "public .ctor(Microsoft.EntityFrameworkCore.Diagnostics.IDiagnosticsLogger<Microsoft.EntityFrameworkCore.DbLoggerCategory+Query> logger)",
"Kind": "Removal"
}
]

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

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

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

@ -0,0 +1,36 @@
// 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.SqlServer.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// NetTopologySuite specific extension methods for <see cref="SqlServerDbContextOptionsBuilder"/>.
/// </summary>
public static class SqlServerNTSDbContextOptionsBuilderExtensions
{
/// <summary>
/// Use NetTopologySuite to access SQL Server spatial data.
/// </summary>
/// <param name="optionsBuilder"> The build being used to configure SQL Server. </param>
/// <returns> The options builder so that further configuration can be chained. </returns>
public static SqlServerDbContextOptionsBuilder UseNetTopologySuite(
[NotNull] this SqlServerDbContextOptionsBuilder optionsBuilder)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)optionsBuilder).OptionsBuilder;
var extension = coreOptionsBuilder.Options.FindExtension<SqlServerNTSOptionsExtension>()
?? new SqlServerNTSOptionsExtension();
((IDbContextOptionsBuilderInfrastructure)coreOptionsBuilder).AddOrUpdateExtension(extension);
return optionsBuilder;
}
}
}

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

@ -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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
using Microsoft.EntityFrameworkCore.SqlServer.Query.ExpressionTranslators.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NetTopologySuite;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// EntityFrameworkCore.SqlServer.NetTopologySuite extension methods for <see cref="IServiceCollection" />.
/// </summary>
public static class SqlServerNTSServiceCollectionExtensions
{
/// <summary>
/// Adds the services required for NetTopologySuite support in the SQL Server 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 AddEntityFrameworkSqlServerNetTopologySuite(
[NotNull] this IServiceCollection serviceCollection)
{
Check.NotNull(serviceCollection, nameof(serviceCollection));
// TODO: Use EF infrastructure?
serviceCollection.TryAddSingleton(NtsGeometryServices.Instance);
new EntityFrameworkRelationalServicesBuilder(serviceCollection)
.TryAddProviderSpecificServices(
x => x.TryAddSingletonEnumerable<IRelationalTypeMappingSourcePlugin, SqlServerNTSTypeMappingSourcePlugin>()
.TryAddSingletonEnumerable<IMethodCallTranslatorPlugin, SqlServerNTSMethodCallTranslatorPlugin>()
.TryAddSingletonEnumerable<IMemberTranslatorPlugin, SqlServerNTSMemberTranslatorPlugin>());
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.SqlServer.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.EntityFrameworkCore.SqlServer.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 SqlServerNTSOptionsExtension : 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.AddEntityFrameworkSqlServerNetTopologySuite();
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 SqlServerNTSTypeMappingSourcePlugin) != true)
{
throw new InvalidOperationException(SqlServerNTSStrings.NTSServicesMissing);
}
}
}
}
}
}

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

@ -0,0 +1,23 @@
using System.IO;
namespace NetTopologySuite.IO.Serialization
{
internal class Figure
{
public FigureAttribute FigureAttribute { get; set; }
public int PointOffset { get; set; }
public static Figure ReadFrom(BinaryReader reader)
=> new Figure
{
FigureAttribute = (FigureAttribute)reader.ReadByte(),
PointOffset = reader.ReadInt32()
};
public void WriteTo(BinaryWriter writer)
{
writer.Write((byte)FigureAttribute);
writer.Write(PointOffset);
}
}
}

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

@ -0,0 +1,10 @@
namespace NetTopologySuite.IO.Serialization
{
internal enum FigureAttribute : byte
{
None = 0, // NB: Called "Point" in MS-SSCLRT (v20170816), but never used
Line = 1,
Arc = 2,
Curve = 3
}
}

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

@ -0,0 +1,283 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.EntityFrameworkCore.SqlServer.Internal;
namespace NetTopologySuite.IO.Serialization
{
internal class Geography
{
public int SRID { get; set; }
public byte Version { get; set; } = 1;
public IList<Point> Points { get; } = new List<Point>();
public IList<double> ZValues { get; } = new List<double>();
public IList<double> MValues { get; } = new List<double>();
public IList<Figure> Figures { get; } = new List<Figure>();
public IList<Shape> Shapes { get; } = new List<Shape>();
public IList<Segment> Segments { get; } = new List<Segment>();
public bool IsValid { get; set; } = true;
public bool IsLargerThanAHemisphere { get; set; }
public static Geography ReadFrom(BinaryReader reader)
{
try
{
var geography = new Geography
{
SRID = reader.ReadInt32()
};
if (geography.SRID == -1)
{
return geography;
}
geography.Version = reader.ReadByte();
if (geography.Version != 1 && geography.Version != 2)
{
throw new FormatException(SqlServerNTSStrings.UnexpectedGeographyVersion(geography.Version));
}
var properties = (SerializationProperties)reader.ReadByte();
geography.IsValid = properties.HasFlag(SerializationProperties.IsValid);
geography.IsLargerThanAHemisphere = properties.HasFlag(SerializationProperties.IsLargerThanAHemisphere);
var numberOfPoints = properties.HasFlag(SerializationProperties.IsSinglePoint)
? 1
: properties.HasFlag(SerializationProperties.IsSingleLineSegment)
? 2
: reader.ReadInt32();
for (var i = 0; i < numberOfPoints; i++)
{
geography.Points.Add(Point.ReadFrom(reader));
}
if (properties.HasFlag(SerializationProperties.HasZValues))
{
for (var i = 0; i < numberOfPoints; i++)
{
geography.ZValues.Add(reader.ReadDouble());
}
}
if (properties.HasFlag(SerializationProperties.HasMValues))
{
for (var i = 0; i < numberOfPoints; i++)
{
geography.MValues.Add(reader.ReadDouble());
}
}
var hasSegments = false;
if (properties.HasFlag(SerializationProperties.IsSinglePoint)
|| properties.HasFlag(SerializationProperties.IsSingleLineSegment))
{
geography.Figures.Add(
new Figure
{
FigureAttribute = FigureAttribute.Line,
PointOffset = 0
});
}
else
{
var numberOfFigures = reader.ReadInt32();
for (var i = 0; i < numberOfFigures; i++)
{
var figure = Figure.ReadFrom(reader);
if (geography.Version == 1)
{
// NB: The legacy value is ignored. Exterior rings are always first
figure.FigureAttribute = FigureAttribute.Line;
}
else if (figure.FigureAttribute == FigureAttribute.Curve)
{
hasSegments = true;
}
geography.Figures.Add(figure);
}
}
if (properties.HasFlag(SerializationProperties.IsSinglePoint)
|| properties.HasFlag(SerializationProperties.IsSingleLineSegment))
{
geography.Shapes.Add(
new Shape
{
ParentOffset = -1,
FigureOffset = 0,
Type = properties.HasFlag(SerializationProperties.IsSinglePoint)
? OpenGisType.Point
: OpenGisType.LineString
});
}
else
{
var numberOfShapes = reader.ReadInt32();
for (var i = 0; i < numberOfShapes; i++)
{
geography.Shapes.Add(Shape.ReadFrom(reader));
}
}
if (hasSegments)
{
var numberOfSegments = reader.ReadInt32();
for (var i = 0; i < numberOfSegments; i++)
{
geography.Segments.Add(Segment.ReadFrom(reader));
}
}
return geography;
}
catch (EndOfStreamException ex)
{
throw new FormatException(SqlServerNTSStrings.UnexpectedEndOfStream, ex);
}
}
public void WriteTo(BinaryWriter writer)
{
writer.Write(SRID);
if (SRID == -1)
{
return;
}
writer.Write(Version);
var properties = SerializationProperties.None;
if (ZValues.Any())
{
properties |= SerializationProperties.HasZValues;
}
if (MValues.Any())
{
properties |= SerializationProperties.HasMValues;
}
if (IsValid)
{
properties |= SerializationProperties.IsValid;
}
if (Shapes.First().Type == OpenGisType.Point && Points.Any())
{
properties |= SerializationProperties.IsSinglePoint;
}
if (Shapes.First().Type == OpenGisType.LineString && Points.Count == 2)
{
properties |= SerializationProperties.IsSingleLineSegment;
}
if (IsLargerThanAHemisphere)
{
properties |= SerializationProperties.IsLargerThanAHemisphere;
}
writer.Write((byte)properties);
if (!properties.HasFlag(SerializationProperties.IsSinglePoint)
&& !properties.HasFlag(SerializationProperties.IsSingleLineSegment))
{
writer.Write(Points.Count);
}
foreach (var point in Points)
{
point.WriteTo(writer);
}
foreach (var z in ZValues)
{
writer.Write(z);
}
foreach (var m in MValues)
{
writer.Write(m);
}
if (properties.HasFlag(SerializationProperties.IsSinglePoint)
|| properties.HasFlag(SerializationProperties.IsSingleLineSegment))
{
return;
}
writer.Write(Figures.Count);
if (Version == 1)
{
for (var shapeIndex = 0; shapeIndex < Shapes.Count; shapeIndex++)
{
var shape = Shapes[shapeIndex];
if (shape.FigureOffset == -1 || shape.IsCollection())
{
continue;
}
var nextShapeIndex = shapeIndex + 1;
while (nextShapeIndex < Shapes.Count && Shapes[nextShapeIndex].FigureOffset == -1)
{
nextShapeIndex++;
}
var lastFigureIndex = nextShapeIndex >= Shapes.Count
? Figures.Count - 1
: Shapes[nextShapeIndex].FigureOffset - 1;
if (Shapes[shapeIndex].Type == OpenGisType.Polygon)
{
// NB: Although never mentioned in MS-SSCLRT (v20170816), exterior rings must be first
writer.Write((byte)LegacyFigureAttribute.ExteriorRing);
writer.Write(Figures[shape.FigureOffset].PointOffset);
for (var figureIndex = shape.FigureOffset + 1; figureIndex <= lastFigureIndex; figureIndex++)
{
writer.Write((byte)LegacyFigureAttribute.InteriorRing);
writer.Write(Figures[figureIndex].PointOffset);
}
continue;
}
for (var figureIndex = shape.FigureOffset; figureIndex <= lastFigureIndex; figureIndex++)
{
writer.Write((byte)LegacyFigureAttribute.Stroke);
writer.Write(Figures[figureIndex].PointOffset);
}
}
}
else
{
foreach (var figure in Figures)
{
figure.WriteTo(writer);
}
}
writer.Write(Shapes.Count);
foreach (var shape in Shapes)
{
shape.WriteTo(writer);
}
if (Segments.Any())
{
writer.Write(Segments.Count);
foreach (var segment in Segments)
{
segment.WriteTo(writer);
}
}
}
}
}

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

@ -0,0 +1,9 @@
namespace NetTopologySuite.IO.Serialization
{
internal enum LegacyFigureAttribute : byte
{
InteriorRing = 0,
Stroke = 1,
ExteriorRing = 2
}
}

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

@ -0,0 +1,18 @@
namespace NetTopologySuite.IO.Serialization
{
internal enum OpenGisType : byte
{
Unknown,
Point,
LineString,
Polygon,
MultiPoint,
MultiLineString,
MultiPolygon,
GeometryCollection,
CircularString,
CompoundCurve,
CurvePolygon,
FullGlobe // NB: Doesn't align with OgcGeometryType
};
}

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

@ -0,0 +1,23 @@
using System.IO;
namespace NetTopologySuite.IO.Serialization
{
internal class Point
{
public double X { get; set; }
public double Y { get; set; }
public static Point ReadFrom(BinaryReader reader)
=> new Point
{
X = reader.ReadDouble(),
Y = reader.ReadDouble()
};
public void WriteTo(BinaryWriter writer)
{
writer.Write(X);
writer.Write(Y);
}
}
}

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

@ -0,0 +1,18 @@
using System.IO;
namespace NetTopologySuite.IO.Serialization
{
internal class Segment
{
public SegmentType Type { get; set; }
public static Segment ReadFrom(BinaryReader reader)
=> new Segment
{
Type = (SegmentType)reader.ReadByte()
};
public void WriteTo(BinaryWriter writer)
=> writer.Write((byte)Type);
}
}

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

@ -0,0 +1,10 @@
namespace NetTopologySuite.IO.Serialization
{
internal enum SegmentType : byte
{
Line = 0,
Arc = 1,
FirstLine = 2,
FirstArc = 3
}
}

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

@ -0,0 +1,16 @@
using System;
namespace NetTopologySuite.IO.Serialization
{
[Flags]
internal enum SerializationProperties : byte
{
None = 0,
HasZValues = 1,
HasMValues = 2,
IsValid = 4,
IsSinglePoint = 8,
IsSingleLineSegment = 0x10,
IsLargerThanAHemisphere = 0x20
}
}

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

@ -0,0 +1,32 @@
using System.IO;
namespace NetTopologySuite.IO.Serialization
{
internal class Shape
{
public int ParentOffset { get; set; }
public int FigureOffset { get; set; }
public OpenGisType Type { get; set; }
public bool IsCollection()
=> Type == OpenGisType.MultiPoint
|| Type == OpenGisType.MultiLineString
|| Type == OpenGisType.MultiPolygon
|| Type == OpenGisType.GeometryCollection;
public static Shape ReadFrom(BinaryReader reader)
=> new Shape
{
ParentOffset = reader.ReadInt32(),
FigureOffset = reader.ReadInt32(),
Type = (OpenGisType)reader.ReadByte()
};
public void WriteTo(BinaryWriter writer)
{
writer.Write(ParentOffset);
writer.Write(FigureOffset);
writer.Write((byte)Type);
}
}
}

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

@ -0,0 +1,261 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using GeoAPI;
using GeoAPI.Geometries;
using GeoAPI.IO;
using Microsoft.EntityFrameworkCore.SqlServer.Internal;
using NetTopologySuite.Geometries.Implementation;
using NetTopologySuite.IO.Serialization;
using GeoParseException = GeoAPI.IO.ParseException;
namespace NetTopologySuite.IO
{
/// <summary>
/// Reads geography or geometry data in the SQL Server serialization format (described in MS-SSCLRT) into
/// <see cref="IGeometry"/> instances.
/// </summary>
public class SqlServerSpatialReader : IBinaryGeometryReader
{
private readonly IGeometryServices _services;
private readonly ICoordinateSequenceFactory _sequenceFactory;
private Ordinates _handleOrdinates;
/// <summary>
/// Initializes a new instance of the <see cref="SqlServerSpatialReader"/> class.
/// </summary>
public SqlServerSpatialReader()
: this(GeometryServiceProvider.Instance)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SqlServerSpatialReader"/> class.
/// </summary>
/// <param name="services"> The geometry services used to create <see cref="IGeometry"/> instances. </param>
public SqlServerSpatialReader(IGeometryServices services)
{
_services = services ?? GeometryServiceProvider.Instance;
_sequenceFactory = _services.DefaultCoordinateSequenceFactory;
_handleOrdinates = AllowedOrdinates;
}
/// <summary>
/// Gets or sets whether invalid linear rings should be fixed. Returns false since invalid rings are
/// disallowed. Setting does nothing.
/// </summary>
public virtual bool RepairRings
{
get => false;
set { }
}
/// <summary>
/// Gets or sets whether the SpatialReference ID must be handled. Returns true since it's always handled.
/// Setting does nothing.
/// </summary>
public virtual bool HandleSRID
{
get => true;
set { }
}
/// <summary>
/// Gets and <see cref="Ordinates"/> flag that indicate which ordinates can be handled.
/// </summary>
public virtual Ordinates AllowedOrdinates
{
get
{
var ordinates = _sequenceFactory.Ordinates;
// TODO: Remove when fixed in NTS
if (_sequenceFactory is PackedCoordinateSequenceFactory packedFactory)
{
ordinates = OrdinatesUtility.DimensionToOrdinates(packedFactory.Dimension);
}
return Ordinates.XYZM & ordinates;
}
}
/// <summary>
/// Gets and sets <see cref="Ordinates"/> flag that indicate which ordinates shall be handled.
/// </summary>
/// <remarks>
/// No matter which <see cref="Ordinates"/> flag you supply, <see cref="Ordinates.XY"/> are always
/// processed, the rest is binary and 'ed with <see cref="AllowedOrdinates"/>.
/// </remarks>
public virtual Ordinates HandleOrdinates
{
get => _handleOrdinates;
set
{
value = Ordinates.XY | (AllowedOrdinates & value);
_handleOrdinates = value;
}
}
/// <summary>
/// Reads a geometry representation from a <see cref="T:byte[]"/> to a Geometry.
/// </summary>
/// <param name="source"> The source to read the geometry from </param>
/// <returns> A Geometry </returns>
public virtual IGeometry Read(byte[] source)
{
using (var stream = new MemoryStream(source))
{
return Read(stream);
}
}
/// <summary>
/// Reads a geometry representation from a <see cref="Stream"/> to a Geometry.
/// </summary>
/// <param name="stream"> The stream to read from. </param>
/// <returns> A geometry. </returns>
public virtual IGeometry Read(Stream stream)
{
Geography geography;
using (var reader = new BinaryReader(stream))
{
geography = Geography.ReadFrom(reader);
}
return ToGeometry(geography);
}
private IGeometry ToGeometry(Geography geography)
{
if (geography.SRID == -1)
{
return null;
}
var handleZ = _handleOrdinates.HasFlag(Ordinates.Z) && geography.ZValues.Any();
var handleM = _handleOrdinates.HasFlag(Ordinates.M) && geography.MValues.Any();
var factory = _services.CreateGeometryFactory(geography.SRID);
var geometries = new Dictionary<int, Stack<IGeometry>>();
var lastFigureIndex = geography.Figures.Count - 1;
var lastPointIndex = geography.Points.Count - 1;
for (var shapeIndex = geography.Shapes.Count - 1; shapeIndex >= 0; shapeIndex--)
{
var shape = geography.Shapes[shapeIndex];
var figures = new Stack<ICoordinateSequence>();
if (shape.FigureOffset != -1)
{
for (var figureIndex = lastFigureIndex; figureIndex >= shape.FigureOffset; figureIndex--)
{
var figure = geography.Figures[figureIndex];
var pointCount = figure.PointOffset != -1
? lastPointIndex + 1 - figure.PointOffset
: 0;
var coordinates = _sequenceFactory.Create(pointCount, _handleOrdinates);
if (pointCount != 0)
{
for (var pointIndex = figure.PointOffset; pointIndex <= lastPointIndex; pointIndex++)
{
var point = geography.Points[pointIndex];
var coordinateIndex = pointIndex - figure.PointOffset;
// TODO: For geography (ellipsoidal) data, the point's X value is Latitude, and the Y
// value is Longitude.
coordinates.SetOrdinate(coordinateIndex, Ordinate.X, point.X);
coordinates.SetOrdinate(coordinateIndex, Ordinate.Y, point.Y);
if (handleZ)
{
coordinates.SetOrdinate(coordinateIndex, Ordinate.Z, geography.ZValues[pointIndex]);
}
if (handleM)
{
coordinates.SetOrdinate(coordinateIndex, Ordinate.M, geography.MValues[pointIndex]);
}
}
lastPointIndex = figure.PointOffset - 1;
}
figures.Push(coordinates);
}
lastFigureIndex = shape.FigureOffset - 1;
}
IGeometry geometry;
switch (shape.Type)
{
case OpenGisType.Point:
geometry = factory.CreatePoint(figures.SingleOrDefault());
Debug.Assert(!geometries.ContainsKey(shapeIndex));
break;
case OpenGisType.LineString:
geometry = factory.CreateLineString(figures.SingleOrDefault());
Debug.Assert(!geometries.ContainsKey(shapeIndex));
break;
case OpenGisType.Polygon:
// TODO: For geography (ellipsoidal) data, the shell is the figure oriented counter-clockwise
geometry = factory.CreatePolygon(
factory.CreateLinearRing(figures.FirstOrDefault()),
figures.Skip(1).Select(f => factory.CreateLinearRing(f)).ToArray());
Debug.Assert(!geometries.ContainsKey(shapeIndex));
break;
case OpenGisType.MultiPoint:
geometry = factory.CreateMultiPoint(
geometries.TryGetValue(shapeIndex, out var points)
? points.Cast<IPoint>().ToArray()
: null);
geometries.Remove(shapeIndex);
break;
case OpenGisType.MultiLineString:
geometry = factory.CreateMultiLineString(
geometries.TryGetValue(shapeIndex, out var lineStrings)
? lineStrings.Cast<ILineString>().ToArray()
: null);
geometries.Remove(shapeIndex);
break;
case OpenGisType.MultiPolygon:
geometry = factory.CreateMultiPolygon(
geometries.TryGetValue(shapeIndex, out var polygons)
? polygons.Cast<IPolygon>().ToArray()
: null);
geometries.Remove(shapeIndex);
break;
case OpenGisType.GeometryCollection:
geometry = factory.CreateGeometryCollection(
geometries.TryGetValue(shapeIndex, out var children)
? children.ToArray()
: null);
geometries.Remove(shapeIndex);
break;
default:
throw new GeoParseException(SqlServerNTSStrings.UnexpectedGeographyType(shape.Type));
}
if (!geometries.ContainsKey(shape.ParentOffset))
{
geometries.Add(shape.ParentOffset, new Stack<IGeometry>());
}
geometries[shape.ParentOffset].Push(geometry);
}
Debug.Assert(geometries.Keys.Count == 1);
return geometries[-1].Single();
}
}
}

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

@ -0,0 +1,242 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using GeoAPI.Geometries;
using GeoAPI.IO;
using NetTopologySuite.IO.Serialization;
using NetTopologySuite.Utilities;
using SqlShape = NetTopologySuite.IO.Serialization.Shape;
namespace NetTopologySuite.IO
{
/// <summary>
/// Writes <see cref="IGeometry"/> instances into geography or geometry data in the SQL Server serialization
/// format (described in MS-SSCLRT).
/// </summary>
public class SqlServerSpatialWriter : IBinaryGeometryWriter
{
// TODO: Default these to true?
private bool _emitZ;
private bool _emitM;
/// <summary>
/// Gets or sets the desired <see cref="ByteOrder"/>. Returns <see cref="ByteOrder.LittleEndian"/> since
/// it's required. Setting does nothing.
/// </summary>
public virtual ByteOrder ByteOrder
{
get => ByteOrder.LittleEndian;
set { }
}
/// <summary>
/// Gets or sets whether the SpatialReference ID must be handled. Returns true since it's required. Setting
/// does nothing.
/// </summary>
public virtual bool HandleSRID
{
get => true;
set { }
}
/// <summary>
/// Gets and <see cref="Ordinates"/> flag that indicate which ordinates can be handled.
/// </summary>
public virtual Ordinates AllowedOrdinates
=> Ordinates.XYZM;
/// <summary>
/// Gets and sets <see cref="Ordinates"/> flag that indicate which ordinates shall be handled.
/// </summary>
/// <remarks>
/// No matter which <see cref="Ordinates"/> flag you supply, <see cref="Ordinates.XY"/> are always
/// processed, the rest is binary and 'ed with <see cref="AllowedOrdinates"/>.
/// </remarks>
public virtual Ordinates HandleOrdinates
{
get
{
var value = Ordinates.XY;
if (_emitZ)
{
value |= Ordinates.Z;
}
if (_emitM)
{
value |= Ordinates.M;
}
return value;
}
set
{
_emitZ = value.HasFlag(Ordinates.Z);
_emitM = value.HasFlag(Ordinates.M);
}
}
/// <summary>
/// Writes a binary representation of a given geometry.
/// </summary>
/// <param name="geometry"> The geometry </param>
/// <returns> The binary representation of geometry </returns>
public virtual byte[] Write(IGeometry geometry)
{
using (var stream = new MemoryStream())
{
Write(geometry, stream);
return stream.ToArray();
}
}
/// <summary>
/// Writes a binary representation of a given geometry.
/// </summary>
/// <param name="geometry"> The geometry </param>
/// <param name="stream"> The stream to write to. </param>
public virtual void Write(IGeometry geometry, Stream stream)
{
var geography = ToGeography(geometry);
using (var writer = new BinaryWriter(stream))
{
geography.WriteTo(writer);
}
}
private Geography ToGeography(IGeometry geometry)
{
if (geometry == null)
{
return new Geography { SRID = -1 };
}
var geometries = new Queue<(IGeometry, int)>();
geometries.Enqueue((geometry, -1));
// TODO: For geography (ellipsoidal) data, set IsLargerThanAHemisphere.
var geography = new Geography
{
SRID = Math.Max(0, geometry.SRID),
IsValid = geometry.IsValid
};
while (geometries.Any())
{
var (currentGeometry, parentOffset) = geometries.Dequeue();
var figureOffset = geography.Figures.Count;
var figureAdded = false;
switch (currentGeometry)
{
case IPoint point:
case ILineString lineString:
figureAdded = addFigure(currentGeometry, FigureAttribute.Line);
break;
case IPolygon polygon:
// TODO: For geography (ellipsoidal) data, the shell must be oriented counter-clockwise
figureAdded = addFigure(polygon.Shell, FigureAttribute.Line);
foreach (var hole in polygon.Holes)
{
// TODO: For geography (ellipsoidal) data, the holes must be oriented clockwise
figureAdded |= addFigure(hole, FigureAttribute.Line);
}
break;
case IGeometryCollection geometryCollection:
foreach (var item in geometryCollection.Geometries)
{
geometries.Enqueue((item, geography.Shapes.Count));
figureAdded = true;
}
break;
default:
Assert.ShouldNeverReachHere("Unsupported Geometry implementation: " + geometry.GetType());
break;
}
geography.Shapes.Add(
new SqlShape
{
ParentOffset = parentOffset,
FigureOffset = figureAdded ? figureOffset : -1,
Type = ToOpenGisType(currentGeometry.OgcGeometryType)
});
bool addFigure(IGeometry g, FigureAttribute figureAttribute)
{
var pointOffset = geography.Points.Count;
var pointsAdded = false;
foreach (var coordinate in g.Coordinates)
{
// TODO: For geography (ellipsoidal) data, the point's X value must be Latitude, and the Y
// value Longitude.
geography.Points.Add(
new Point
{
X = coordinate.X,
Y = coordinate.Y
});
pointsAdded = true;
if (_emitZ)
{
geography.ZValues.Add(coordinate.Z);
}
}
if (!pointsAdded)
{
return false;
}
if (_emitM)
{
foreach (var m in g.GetOrdinates(Ordinate.M))
{
geography.MValues.Add(m);
}
}
geography.Figures.Add(
new Figure
{
FigureAttribute = figureAttribute,
PointOffset = pointOffset
});
return true;
}
}
if (geography.ZValues.All(double.IsNaN))
{
geography.ZValues.Clear();
}
if (geography.MValues.All(double.IsNaN))
{
geography.MValues.Clear();
}
return geography;
}
private OpenGisType ToOpenGisType(OgcGeometryType type)
{
if (type < OgcGeometryType.Point
|| type > OgcGeometryType.CurvePolygon)
{
Assert.ShouldNeverReachHere("Unsupported type: " + type);
}
return (OpenGisType)type;
}
}
}

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

@ -0,0 +1,60 @@
// <auto-generated />
using System;
using System.Reflection;
using System.Resources;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Microsoft.EntityFrameworkCore.SqlServer.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 SqlServerNTSStrings
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.EntityFrameworkCore.SqlServer.Properties.SqlServerNTSStrings", typeof(SqlServerNTSStrings).GetTypeInfo().Assembly);
/// <summary>
/// UseNetTopologySuite requires AddEntityFrameworkSqlServerNetTopologySuite to be called on the internal service provider used.
/// </summary>
public static string NTSServicesMissing
=> GetString("NTSServicesMissing");
/// <summary>
/// The GEOGRAPHY structure recieved is incomplete and cannot be read.
/// </summary>
public static string UnexpectedEndOfStream
=> GetString("UnexpectedEndOfStream");
/// <summary>
/// Unsupported type: {type}
/// </summary>
public static string UnexpectedGeographyType([CanBeNull] object type)
=> string.Format(
GetString("UnexpectedGeographyType", nameof(type)),
type);
/// <summary>
/// Version {version} of the GEOGRAPHY structure received. This version isn't supported.
/// </summary>
public static string UnexpectedGeographyVersion([CanBeNull] object version)
=> string.Format(
GetString("UnexpectedGeographyVersion", nameof(version)),
version);
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"] = "SqlServerNTSStrings.resx";
#>
<#@ include file="..\..\..\tools\Resources.tt" #>

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

@ -0,0 +1,132 @@
<?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 AddEntityFrameworkSqlServerNetTopologySuite to be called on the internal service provider used.</value>
</data>
<data name="UnexpectedEndOfStream" xml:space="preserve">
<value>The GEOGRAPHY structure recieved is incomplete and cannot be read.</value>
</data>
<data name="UnexpectedGeographyType" xml:space="preserve">
<value>Unsupported type: {type}</value>
</data>
<data name="UnexpectedGeographyVersion" xml:space="preserve">
<value>Version {version} of the GEOGRAPHY structure received. This version isn't supported.</value>
</data>
</root>

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

@ -0,0 +1,47 @@
// 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;
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.SqlServer.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 SqlServerCurveMemberTranslator : IMemberTranslator
{
private static readonly IDictionary<MemberInfo, string> _memberToFunctionName = new Dictionary<MemberInfo, string>
{
{ typeof(ICurve).GetRuntimeProperty(nameof(ICurve.EndPoint)), "STEndPoint" },
{ typeof(ICurve).GetRuntimeProperty(nameof(ICurve.IsClosed)), "STIsClosed" },
{ typeof(ICurve).GetRuntimeProperty(nameof(ICurve.IsRing)), "STIsRing" },
{ typeof(ICurve).GetRuntimeProperty(nameof(ICurve.StartPoint)), "STStartPoint" },
};
/// <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(ICurve));
if (_memberToFunctionName.TryGetValue(member, out var functionName))
{
return new SqlFunctionExpression(
memberExpression.Expression,
functionName,
memberExpression.Type,
Enumerable.Empty<Expression>());
}
return null;
}
}
}

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

@ -0,0 +1,40 @@
// 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;
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.SqlServer.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 SqlServerGeometryCollectionMemberTranslator : 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(
memberExpression.Expression,
"STNumGeometries",
memberExpression.Type,
Enumerable.Empty<Expression>());
}
return null;
}
}
}

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

@ -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 System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.SqlServer.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 SqlServerGeometryCollectionMethodTranslator : 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(
methodCallExpression.Object,
"STGeometryN",
methodCallExpression.Type,
new[] { Expression.Add(methodCallExpression.Arguments[0], Expression.Constant(1)) });
}
return null;
}
}
}

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

@ -0,0 +1,66 @@
// 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;
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.SqlServer.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 SqlServerGeometryMemberTranslator : IMemberTranslator
{
private static readonly IDictionary<MemberInfo, string> _memberToFunctionName = new Dictionary<MemberInfo, string>
{
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.Area)), "STArea" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.Boundary)), "STBoundary" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.Centroid)), "STCentroid" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.Dimension)), "STDimension" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.Envelope)), "STEnvelope" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.GeometryType)), "STGeometryType" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.IsEmpty)), "STIsEmpty" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.IsSimple)), "STIsSimple" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.IsValid)), "STIsValid" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.Length)), "STLength" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.NumGeometries)), "STNumGeometries" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.NumPoints)), "STNumPoints" },
{ typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.PointOnSurface)), "STPointOnSurface" }
};
private static readonly MemberInfo _srid = typeof(IGeometry).GetRuntimeProperty(nameof(IGeometry.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 virtual Expression Translate(MemberExpression memberExpression)
{
var member = memberExpression.Member.OnInterface(typeof(IGeometry));
if (_memberToFunctionName.TryGetValue(member, out var functionName))
{
return new SqlFunctionExpression(
memberExpression.Expression,
functionName,
memberExpression.Type,
Enumerable.Empty<Expression>());
}
if (Equals(member, _srid))
{
return new SqlFunctionExpression(
memberExpression.Expression,
"STSrid",
memberExpression.Type,
niladic: true);
}
return null;
}
}
}

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

@ -0,0 +1,74 @@
// 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.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
using NetTopologySuite.Geometries;
namespace Microsoft.EntityFrameworkCore.SqlServer.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 SqlServerGeometryMethodTranslator : IMethodCallTranslator
{
private static readonly IDictionary<MethodInfo, string> _methodToFunctionName = new Dictionary<MethodInfo, string>
{
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.AsBinary), Type.EmptyTypes), "STAsBinary" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.AsText), Type.EmptyTypes), "AsTextZM" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Buffer), new[] { typeof(double) }), "STBuffer" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Contains), new[] { typeof(IGeometry) }), "STContains" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.ConvexHull), Type.EmptyTypes), "STConvexHull" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Crosses), new[] { typeof(IGeometry) }), "STCrosses" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Difference), new[] { typeof(IGeometry) }), "STDifference" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Disjoint), new[] { typeof(IGeometry) }), "STDisjoint" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Distance), new[] { typeof(IGeometry) }), "STDistance" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.EqualsTopologically), new[] { typeof(IGeometry) }), "STEquals" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Intersection), new[] { typeof(IGeometry) }), "STIntersection" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Intersects), new[] { typeof(IGeometry) }), "STIntersects" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Overlaps), new[] { typeof(IGeometry) }), "STOverlaps" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Relate), new[] { typeof(IGeometry), typeof(string) }), "STRelate" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.SymmetricDifference), new[] { typeof(IGeometry) }), "STSymDifference" },
{ typeof(Geometry).GetRuntimeMethod(nameof(Geometry.ToBinary), Type.EmptyTypes), "STAsBinary" },
{ typeof(Geometry).GetRuntimeMethod(nameof(Geometry.ToText), Type.EmptyTypes), "AsTextZM" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Touches), new[] { typeof(IGeometry) }), "STTouches" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Union), new[] { typeof(IGeometry) }), "STUnion" },
{ typeof(IGeometry).GetRuntimeMethod(nameof(IGeometry.Within), new[] { typeof(IGeometry) }), "STWithin" }
};
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 virtual Expression Translate(MethodCallExpression methodCallExpression)
{
var method = methodCallExpression.Method.OnInterface(typeof(IGeometry));
if (_methodToFunctionName.TryGetValue(method, out var functionName))
{
return new SqlFunctionExpression(
methodCallExpression.Object,
functionName,
methodCallExpression.Type,
methodCallExpression.Arguments);
}
if (Equals(method, _getGeometryN))
{
return new SqlFunctionExpression(
methodCallExpression.Object,
"STGeometryN",
methodCallExpression.Type,
new[] { Expression.Add(methodCallExpression.Arguments[0], Expression.Constant(1)) });
}
return null;
}
}
}

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

@ -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 System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
using NetTopologySuite.Geometries;
namespace Microsoft.EntityFrameworkCore.SqlServer.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 SqlServerLineStringMemberTranslator : 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 virtual Expression Translate(MemberExpression memberExpression)
{
if (Equals(memberExpression.Member, _count))
{
return new SqlFunctionExpression(
memberExpression.Expression,
"STNumPoints",
memberExpression.Type,
Enumerable.Empty<Expression>());
}
return null;
}
}
}

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

@ -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 System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.SqlServer.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 SqlServerLineStringMethodTranslator : 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 virtual Expression Translate(MethodCallExpression methodCallExpression)
{
var method = methodCallExpression.Method.OnInterface(typeof(ILineString));
if (Equals(method, _getPointN))
{
return new SqlFunctionExpression(
methodCallExpression.Object,
"STPointN",
methodCallExpression.Type,
new[] { Expression.Add(methodCallExpression.Arguments[0], Expression.Constant(1)) });
}
return null;
}
}
}

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

@ -0,0 +1,40 @@
// 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;
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.SqlServer.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 SqlServerMultiCurveMemberTranslator : 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 virtual Expression Translate(MemberExpression memberExpression)
{
var member = memberExpression.Member.OnInterface(typeof(IMultiCurve));
if (Equals(member, _isClosed))
{
return new SqlFunctionExpression(
memberExpression.Expression,
"STIsClosed",
memberExpression.Type,
Enumerable.Empty<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.SqlServer.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 SqlServerNTSMemberTranslatorPlugin : 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 SqlServerCurveMemberTranslator(),
new SqlServerGeometryMemberTranslator(),
new SqlServerGeometryCollectionMemberTranslator(),
new SqlServerLineStringMemberTranslator(),
new SqlServerMultiCurveMemberTranslator(),
new SqlServerPointMemberTranslator(),
new SqlServerPolygonMemberTranslator()
};
}
}

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

@ -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.SqlServer.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 SqlServerNTSMethodCallTranslatorPlugin : 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 SqlServerGeometryMethodTranslator(),
new SqlServerGeometryCollectionMethodTranslator(),
new SqlServerLineStringMethodTranslator(),
new SqlServerPolygonMethodTranslator()
};
}
}

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

@ -0,0 +1,46 @@
// 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.SqlServer.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 SqlServerPointMemberTranslator : IMemberTranslator
{
private static readonly IDictionary<MemberInfo, string> _memberToPropertyName = new Dictionary<MemberInfo, string>
{
{ typeof(IPoint).GetRuntimeProperty(nameof(IPoint.M)), "M" },
{ typeof(IPoint).GetRuntimeProperty(nameof(IPoint.X)), "STX" },
{ typeof(IPoint).GetRuntimeProperty(nameof(IPoint.Y)), "STY" },
{ 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 virtual Expression Translate(MemberExpression memberExpression)
{
var member = memberExpression.Member.OnInterface(typeof(IPoint));
if (_memberToPropertyName.TryGetValue(member, out var propertyName))
{
return new SqlFunctionExpression(
memberExpression.Expression,
propertyName,
memberExpression.Type,
niladic: true);
}
return null;
}
}
}

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

@ -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;
using System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.SqlServer.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 SqlServerPolygonMemberTranslator : IMemberTranslator
{
private static readonly IDictionary<MemberInfo, string> _memberToFunctionName = new Dictionary<MemberInfo, string>
{
{ typeof(IPolygon).GetRuntimeProperty(nameof(IPolygon.ExteriorRing)), "STExteriorRing" },
{ typeof(IPolygon).GetRuntimeProperty(nameof(IPolygon.NumInteriorRings)), "STNumInteriorRing" }
};
/// <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(IPolygon));
if (_memberToFunctionName.TryGetValue(member, out var functionName))
{
return new SqlFunctionExpression(
memberExpression.Expression,
functionName,
memberExpression.Type,
Enumerable.Empty<Expression>());
}
return null;
}
}
}

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

@ -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 System.Linq.Expressions;
using System.Reflection;
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
namespace Microsoft.EntityFrameworkCore.SqlServer.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 SqlServerPolygonMethodTranslator : 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 virtual Expression Translate(MethodCallExpression methodCallExpression)
{
var method = methodCallExpression.Method.OnInterface(typeof(IPolygon));
if (Equals(method, _getInteriorRingN))
{
return new SqlFunctionExpression(
methodCallExpression.Object,
"STInteriorRingN",
methodCallExpression.Type,
new[] { Expression.Add(methodCallExpression.Arguments[0], Expression.Constant(1)) });
}
return null;
}
}
}

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

@ -0,0 +1,83 @@
// 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.Data.SqlClient;
using System.Data.SqlTypes;
using System.Reflection;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.SqlServer.Storage.ValueConversion.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using NetTopologySuite.IO;
namespace Microsoft.EntityFrameworkCore.SqlServer.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 SqlServerGeometryTypeMapping : RelationalTypeMapping
{
private static readonly MethodInfo _getSqlBytes
= typeof(SqlDataReader).GetTypeInfo().GetDeclaredMethod(nameof(SqlDataReader.GetSqlBytes));
private readonly SqlServerSpatialReader _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 SqlServerGeometryTypeMapping(Type clrType, SqlServerSpatialReader 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 SqlServerGeometryTypeMapping(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 SqlServerGeometryTypeMapping(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: Can we avoid the conversion in the first place? Should we just inline the BLOB?
var geometry = _reader.Read(((SqlBytes)value).Value);
var srid = geometry.SRID;
// TODO: This won't emit M (see NetTopologySuite/NetTopologySuite#156)
var text = "'" + geometry.AsText() + "'";
return srid > 0
? $"geometry::STGeomFromText({text}, {srid})"
: 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.
/// </summary>
public override MethodInfo GetDataReaderMethod()
=> _getSqlBytes;
}
}

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

@ -0,0 +1,55 @@
// 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.SqlServer.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 SqlServerNTSTypeMappingSourcePlugin : IRelationalTypeMappingSourcePlugin
{
private readonly HashSet<string> _spatialStoreTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"geometry",
"geography"
};
private readonly SqlServerSpatialReader _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 SqlServerNTSTypeMappingSourcePlugin([NotNull] IGeometryServices geometryServices)
{
Check.NotNull(geometryServices, nameof(geometryServices));
_reader = new SqlServerSpatialReader(geometryServices);
}
/// <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;
return (clrType != null && typeof(IGeometry).IsAssignableFrom(clrType)
|| storeTypeName != null && _spatialStoreTypes.Contains(storeTypeName))
? new SqlServerGeometryTypeMapping(clrType ?? typeof(IGeometry), _reader, storeTypeName ?? "geometry")
: null;
}
}
}

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

@ -0,0 +1,100 @@
// 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.Data.SqlTypes;
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.SqlServer.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 SqlServerSpatialWriter _writer
= new SqlServerSpatialWriter { HandleOrdinates = Ordinates.XYZM };
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, SqlServerSpatialReader reader)
: base(
(Expression<Func<IGeometry, SqlBytes>>)(g => new SqlBytes(_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(SqlBytes);
private static LambdaExpression GetConvertFromProviderExpression(Type type, SqlServerSpatialReader reader)
{
var bytes = Expression.Parameter(typeof(SqlBytes), "bytes");
Expression body = Expression.Call(
Expression.Constant(reader),
typeof(SqlServerSpatialReader).GetRuntimeMethod(nameof(SqlServerSpatialReader.Read), new[] { typeof(byte[]) }),
new[]
{
Expression.Property(bytes, nameof(SqlBytes.Value))
});
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;
}
}
}

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

@ -239,7 +239,8 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected override RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo)
=> FindRawMapping(mappingInfo)?.Clone(mappingInfo);
=> FindRawMapping(mappingInfo)?.Clone(mappingInfo)
?? base.FindMapping(mappingInfo);
private RelationalTypeMapping FindRawMapping(RelationalTypeMappingInfo mappingInfo)
{

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

@ -35,10 +35,6 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking
/// </summary>
public override Type Type { get; }
/// <inheritdoc/>
public override LambdaExpression EqualsExpression
=> base.EqualsExpression;
/// <summary>
/// Compares the two instances to determine if they are equal.
/// </summary>

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

@ -145,7 +145,8 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure
{ typeof(IPropertyListener), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
{ typeof(IResettableService), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
{ typeof(ISingletonOptions), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) },
{ typeof(IEvaluatableExpressionFilter), new ServiceCharacteristics(ServiceLifetime.Scoped) }
{ typeof(IEvaluatableExpressionFilter), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(ITypeMappingSourcePlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) }
};
/// <summary>

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

@ -63,7 +63,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
new FallbackTypeMappingSource(
new TypeMappingSourceDependencies(
new ValueConverterSelector(
new ValueConverterSelectorDependencies())),
new ValueConverterSelectorDependencies()),
Enumerable.Empty<ITypeMappingSourcePlugin>()),
typeMapper),
null,
null,
@ -175,7 +176,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
new FallbackTypeMappingSource(
new TypeMappingSourceDependencies(
new ValueConverterSelector(
new ValueConverterSelectorDependencies())),
new ValueConverterSelectorDependencies()),
Enumerable.Empty<ITypeMappingSourcePlugin>()),
typeMapper),
ConstructorBindingFactory,
ParameterBindingFactories,

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

@ -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.
namespace Microsoft.EntityFrameworkCore.Storage
{
/// <summary>
/// Represents a plugin type mapping source.
/// </summary>
public interface ITypeMappingSourcePlugin
{
/// <summary>
/// Finds a type mapping for the given info.
/// </summary>
/// <param name="mappingInfo"> The mapping info to use to create the mapping. </param>
/// <returns> The type mapping, or <c>null</c> if none could be found. </returns>
CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo);
}
}

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

@ -45,7 +45,7 @@ namespace Microsoft.EntityFrameworkCore.Storage.Internal
protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo)
=> _typeMapper.IsTypeMapped(mappingInfo.ClrType)
? new ConcreteTypeMapping(mappingInfo.ClrType)
: null;
: base.FindMapping(mappingInfo);
private class ConcreteTypeMapping : CoreTypeMapping
{

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

@ -51,7 +51,19 @@ namespace Microsoft.EntityFrameworkCore.Storage
/// </summary>
/// <param name="mappingInfo"> The mapping info to use to create the mapping. </param>
/// <returns> The type mapping, or <c>null</c> if none could be found. </returns>
protected abstract CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo);
protected virtual CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo)
{
foreach (var plugin in Dependencies.Plugins)
{
var typeMapping = plugin.FindMapping(mappingInfo);
if (typeMapping != null)
{
return typeMapping;
}
}
return null;
}
/// <summary>
/// Called after a mapping has been found so that it can be validated for the given property.

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

@ -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 System.Collections.Generic;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.EntityFrameworkCore.Utilities;
@ -40,11 +41,16 @@ namespace Microsoft.EntityFrameworkCore.Storage
/// </para>
/// </summary>
/// <param name="valueConverterSelector"> The registry of known <see cref="ValueConverter" />s. </param>
public TypeMappingSourceDependencies([NotNull] IValueConverterSelector valueConverterSelector)
/// <param name="plugins"> The plugins. </param>
public TypeMappingSourceDependencies(
[NotNull] IValueConverterSelector valueConverterSelector,
[NotNull] IEnumerable<ITypeMappingSourcePlugin> plugins)
{
Check.NotNull(valueConverterSelector, nameof(valueConverterSelector));
Check.NotNull(plugins, nameof(plugins));
ValueConverterSelector = valueConverterSelector;
Plugins = plugins;
}
/// <summary>
@ -52,12 +58,26 @@ namespace Microsoft.EntityFrameworkCore.Storage
/// </summary>
public IValueConverterSelector ValueConverterSelector { get; }
/// <summary>
/// Gets the plugins.
/// </summary>
public IEnumerable<ITypeMappingSourcePlugin> Plugins { get; }
/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
/// <param name="valueConverterSelector"> A replacement for the current dependency of this type. </param>
/// <returns> A new parameter object with the given service replaced. </returns>
public TypeMappingSourceDependencies With([NotNull] IValueConverterSelector valueConverterSelector)
=> new TypeMappingSourceDependencies(valueConverterSelector);
=> new TypeMappingSourceDependencies(valueConverterSelector, Plugins);
/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
/// <param name="plugins"> A replacement for the current dependency of this type. </param>
/// <returns> A new parameter object with the given service replaced. </returns>
public TypeMappingSourceDependencies With([NotNull] IEnumerable<ITypeMappingSourcePlugin> plugins)
=> new TypeMappingSourceDependencies(ValueConverterSelector, plugins);
}
}

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

@ -1,7 +1,9 @@
// 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.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore.Internal;
namespace System.Reflection
{
@ -22,5 +24,53 @@ namespace System.Reflection
|| otherPropertyInfo.DeclaringType.GetTypeInfo().IsSubclassOf(propertyInfo.DeclaringType)
|| propertyInfo.DeclaringType.GetTypeInfo().ImplementedInterfaces.Contains(otherPropertyInfo.DeclaringType)
|| otherPropertyInfo.DeclaringType.GetTypeInfo().ImplementedInterfaces.Contains(propertyInfo.DeclaringType))));
public static MemberInfo OnInterface(this MemberInfo targetMember, Type interfaceType)
{
var declaringType = targetMember.DeclaringType;
if (declaringType == interfaceType
|| declaringType.IsInterface
|| !declaringType.GetInterfaces().Any(i => i == interfaceType))
{
return targetMember;
}
if (targetMember is MethodInfo targetMethod)
{
return targetMethod.OnInterface(interfaceType);
}
if (targetMember is PropertyInfo targetProperty)
{
var targetGetMethod = targetProperty.GetMethod;
var interfaceGetMethod = targetGetMethod.OnInterface(interfaceType);
if (interfaceGetMethod == targetGetMethod)
{
return targetProperty;
}
return interfaceType.GetProperties().First(p => Equals(p.GetMethod, interfaceGetMethod));
}
Debug.Fail("Unexpected member type: " + targetMember.MemberType);
return targetMember;
}
public static MethodInfo OnInterface(this MethodInfo targetMethod, Type interfaceType)
{
var declaringType = targetMethod.DeclaringType;
if (declaringType == interfaceType
|| declaringType.IsInterface
|| !declaringType.GetInterfaces().Any(i => i == interfaceType))
{
return targetMethod;
}
var map = targetMethod.DeclaringType.GetInterfaceMap(interfaceType);
var index = map.TargetMethods.IndexOf(targetMethod);
return index != -1
? map.InterfaceMethods[index]
: targetMethod;
}
}
}

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

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

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

@ -0,0 +1,19 @@
// 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;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.EntityFrameworkCore.Query
{
public class SpatialQuerySqlServerFixture : SpatialQueryRelationalFixture
{
protected override ITestStoreFactory TestStoreFactory
=> SqlServerTestStoreFactory.Instance;
// TODO: Use UseNetTopologySuite instead?
protected override IServiceCollection AddServices(IServiceCollection serviceCollection)
=> base.AddServices(serviceCollection)
.AddEntityFrameworkSqlServerNetTopologySuite();
}
}

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

@ -0,0 +1,481 @@
// 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 Xunit.Abstractions;
namespace Microsoft.EntityFrameworkCore.Query
{
public class SpatialQuerySqlServerTest : SpatialQueryTestBase<SpatialQuerySqlServerFixture>
{
public SpatialQuerySqlServerTest(SpatialQuerySqlServerFixture 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], [e].[Polygon].STArea() AS [Area]
FROM [PolygonEntity] AS [e]");
}
public override async Task AsBinary(bool isAsync)
{
await base.AsBinary(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Point].STAsBinary() AS [Binary]
FROM [PointEntity] AS [e]");
}
public override async Task AsText(bool isAsync)
{
await base.AsText(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Point].AsTextZM() AS [Text]
FROM [PointEntity] AS [e]");
}
public override async Task Boundary(bool isAsync)
{
await base.Boundary(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STBoundary() AS [Boundary]
FROM [PolygonEntity] AS [e]");
}
public override async Task Buffer(bool isAsync)
{
await base.Buffer(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STBuffer(1.0E0) AS [Buffer]
FROM [PolygonEntity] AS [e]");
}
public override async Task Centroid(bool isAsync)
{
await base.Centroid(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STCentroid() AS [Centroid]
FROM [PolygonEntity] AS [e]");
}
public override async Task Contains(bool isAsync)
{
await base.Contains(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STContains('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], [e].[Polygon].STConvexHull() AS [ConvexHull]
FROM [PolygonEntity] AS [e]");
}
public override async Task IGeometryCollection_Count(bool isAsync)
{
await base.IGeometryCollection_Count(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[MultiLineString].STNumGeometries() AS [Count]
FROM [MultiLineStringEntity] AS [e]");
}
public override async Task LineString_Count(bool isAsync)
{
await base.LineString_Count(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[LineString].STNumPoints() AS [Count]
FROM [LineStringEntity] AS [e]");
}
public override async Task Crosses(bool isAsync)
{
await base.Crosses(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[LineString].STCrosses('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], [e].[Polygon].STDifference('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], [e].[Point].STDimension() AS [Dimension]
FROM [PointEntity] AS [e]");
}
public override async Task Disjoint(bool isAsync)
{
await base.Disjoint(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STDisjoint('POINT (1 0)') AS [Disjoint]
FROM [PolygonEntity] AS [e]");
}
public override async Task Distance(bool isAsync)
{
await base.Distance(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Point].STDistance('POINT (0 1)') AS [Distance]
FROM [PointEntity] AS [e]");
}
public override async Task EndPoint(bool isAsync)
{
await base.EndPoint(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[LineString].STEndPoint() AS [EndPoint]
FROM [LineStringEntity] AS [e]");
}
public override async Task Envelope(bool isAsync)
{
await base.Envelope(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STEnvelope() AS [Envelope]
FROM [PolygonEntity] AS [e]");
}
public override async Task EqualsTopologically(bool isAsync)
{
await base.EqualsTopologically(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Point].STEquals('POINT (0 0)') AS [EqualsTopologically]
FROM [PointEntity] AS [e]");
}
public override async Task ExteriorRing(bool isAsync)
{
await base.ExteriorRing(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STExteriorRing() AS [ExteriorRing]
FROM [PolygonEntity] AS [e]");
}
public override async Task GeometryType(bool isAsync)
{
await base.GeometryType(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Point].STGeometryType() AS [GeometryType]
FROM [PointEntity] AS [e]");
}
public override async Task GetGeometryN(bool isAsync)
{
await base.GetGeometryN(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[MultiLineString].STGeometryN(0 + 1) AS [Geometry0]
FROM [MultiLineStringEntity] AS [e]");
}
public override async Task GetInteriorRingN(bool isAsync)
{
await base.GetInteriorRingN(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STInteriorRingN(0 + 1) AS [InteriorRing0]
FROM [PolygonEntity] AS [e]
WHERE [e].[Polygon].STNumInteriorRing() > 0");
}
public override async Task GetPointN(bool isAsync)
{
await base.GetPointN(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[LineString].STPointN(0 + 1) AS [Point0]
FROM [LineStringEntity] AS [e]");
}
public override async Task Intersection(bool isAsync)
{
await base.Intersection(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STIntersection('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], [e].[LineString].STIntersects('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], [e].[LineString].STIsClosed() AS [IsClosed]
FROM [LineStringEntity] AS [e]");
}
public override async Task IMultiCurve_IsClosed(bool isAsync)
{
await base.IMultiCurve_IsClosed(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[MultiLineString].STIsClosed() AS [IsClosed]
FROM [MultiLineStringEntity] AS [e]");
}
public override async Task IsEmpty(bool isAsync)
{
await base.IsEmpty(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[MultiLineString].STIsEmpty() AS [IsEmpty]
FROM [MultiLineStringEntity] AS [e]");
}
public override async Task IsRing(bool isAsync)
{
await base.IsRing(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[LineString].STIsRing() AS [IsRing]
FROM [LineStringEntity] AS [e]");
}
public override async Task IsSimple(bool isAsync)
{
await base.IsSimple(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[LineString].STIsSimple() AS [IsSimple]
FROM [LineStringEntity] AS [e]");
}
public override async Task IsValid(bool isAsync)
{
await base.IsValid(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Point].STIsValid() AS [IsValid]
FROM [PointEntity] AS [e]");
}
public override async Task Item(bool isAsync)
{
await base.Item(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[MultiLineString].STGeometryN(0 + 1) AS [Item0]
FROM [MultiLineStringEntity] AS [e]");
}
public override async Task Length(bool isAsync)
{
await base.Length(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[LineString].STLength() AS [Length]
FROM [LineStringEntity] AS [e]");
}
public override async Task M(bool isAsync)
{
await base.M(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Point].M AS [M]
FROM [PointEntity] AS [e]");
}
public override async Task NumGeometries(bool isAsync)
{
await base.NumGeometries(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[MultiLineString].STNumGeometries() AS [NumGeometries]
FROM [MultiLineStringEntity] AS [e]");
}
public override async Task NumInteriorRings(bool isAsync)
{
await base.NumInteriorRings(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STNumInteriorRing() AS [NumInteriorRings]
FROM [PolygonEntity] AS [e]");
}
public override async Task NumPoints(bool isAsync)
{
await base.NumPoints(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[LineString].STNumPoints() AS [NumPoints]
FROM [LineStringEntity] AS [e]");
}
public override async Task Overlaps(bool isAsync)
{
await base.Overlaps(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STOverlaps('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], [e].[Polygon].STPointOnSurface() AS [PointOnSurface], [e].[Polygon]
FROM [PolygonEntity] AS [e]");
}
public override async Task Relate(bool isAsync)
{
await base.Relate(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STRelate('POLYGON ((0 0, 1 0, 1 1, 0 0))', N'212111212') AS [Relate]
FROM [PolygonEntity] AS [e]");
}
public override async Task SRID(bool isAsync)
{
await base.SRID(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Point].STSrid AS [SRID]
FROM [PointEntity] AS [e]");
}
public override async Task StartPoint(bool isAsync)
{
await base.StartPoint(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[LineString].STStartPoint() AS [StartPoint]
FROM [LineStringEntity] AS [e]");
}
public override async Task SymmetricDifference(bool isAsync)
{
await base.SymmetricDifference(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STSymDifference('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], [e].[Point].STAsBinary() AS [Binary]
FROM [PointEntity] AS [e]");
}
public override async Task ToText(bool isAsync)
{
await base.ToText(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Point].AsTextZM() AS [Text]
FROM [PointEntity] AS [e]");
}
public override async Task Touches(bool isAsync)
{
await base.Touches(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Polygon].STTouches('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], [e].[Polygon].STUnion('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], [e].[Point].STWithin('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], [e].[Point].STX AS [X]
FROM [PointEntity] AS [e]");
}
public override async Task Y(bool isAsync)
{
await base.Y(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Point].STY AS [Y]
FROM [PointEntity] AS [e]");
}
public override async Task Z(bool isAsync)
{
await base.Z(isAsync);
AssertSql(
@"SELECT [e].[Id], [e].[Point].Z AS [Z]
FROM [PointEntity] AS [e]");
}
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
}

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

@ -0,0 +1,19 @@
// 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;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.EntityFrameworkCore
{
public class SpatialSqlServerFixture : SpatialFixtureBase
{
protected override ITestStoreFactory TestStoreFactory
=> SqlServerTestStoreFactory.Instance;
// TODO: Use UseNetTopologySuite instead?
protected override IServiceCollection AddServices(IServiceCollection serviceCollection)
=> base.AddServices(serviceCollection)
.AddEntityFrameworkSqlServerNetTopologySuite();
}
}

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

@ -0,0 +1,13 @@
// 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
{
public class SpatialSqlServerTest : SpatialTestBase<SpatialSqlServerFixture>
{
public SpatialSqlServerTest(SpatialSqlServerFixture fixture)
: base(fixture)
{
}
}
}

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

@ -1,21 +1,12 @@
// 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.Reflection;
using Microsoft.EntityFrameworkCore.Query;
namespace Microsoft.EntityFrameworkCore
{
public class SqlServerComplianceTest : RelationalComplianceTestBase
{
protected override ICollection<Type> IgnoredTestBases { get; } = new HashSet<Type>
{
typeof(SpatialQueryTestBase<>),
typeof(SpatialTestBase<>)
};
protected override Assembly TargetAssembly { get; } = typeof(SqlServerComplianceTest).Assembly;
}
}

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

@ -0,0 +1,19 @@
// 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.Reflection;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.EntityFrameworkCore
{
public class NTSApiConsistencyTest : ApiConsistencyTestBase
{
protected override void AddServices(ServiceCollection serviceCollection)
{
serviceCollection.AddEntityFrameworkSqlServerNetTopologySuite();
}
protected override Assembly TargetAssembly
=> typeof(SqlServerNTSServiceCollectionExtensions).GetTypeInfo().Assembly;
}
}

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

@ -0,0 +1,203 @@
using System.Globalization;
using GeoAPI;
using GeoAPI.Geometries;
using NetTopologySuite.Geometries;
using NetTopologySuite.Geometries.Implementation;
using Xunit;
using GeoParseException = GeoAPI.IO.ParseException;
namespace NetTopologySuite.IO
{
public class SqlServerSpatialReaderTest
{
[Theory]
[InlineData(
"POINT EMPTY",
"000000000104000000000000000001000000FFFFFFFFFFFFFFFF01")]
[InlineData(
"POINT (1 2)",
"00000000010C000000000000F03F0000000000000040")]
[InlineData(
"POINT (1 2 3)",
"00000000010D000000000000F03F00000000000000400000000000000840")]
[InlineData(
"LINESTRING EMPTY",
"000000000104000000000000000001000000FFFFFFFFFFFFFFFF02")]
[InlineData(
"LINESTRING (0 0, 0 1)",
"000000000114000000000000000000000000000000000000000000000000000000000000F03F")]
[InlineData(
"LINESTRING (0 0 1, 0 1 2)",
"000000000115000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F0000000000000040")]
[InlineData(
"LINESTRING (0 0, 0 1 2)",
"000000000115000000000000000000000000000000000000000000000000000000000000F03F000000000000F8FF0000000000000040")]
[InlineData(
"LINESTRING (0 0, 0 1, 0 2)",
"00000000010403000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000004001000000010000000001000000FFFFFFFF0000000002")]
[InlineData(
"LINESTRING (0 0 1, 0 1 2, 0 2 3)",
"00000000010503000000000000000000000000000000000000000000000000000000000000000000F03F00000000000000000000000000000040000000000000F03F0000000000000040000000000000084001000000010000000001000000FFFFFFFF0000000002")]
[InlineData(
"POLYGON EMPTY",
"000000000104000000000000000001000000FFFFFFFFFFFFFFFF03")]
[InlineData(
"POLYGON ((0 0, 0 1, 1 1, 0 0))",
"00000000010404000000000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000000001000000020000000001000000FFFFFFFF0000000003")]
[InlineData(
"POLYGON ((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))",
"0000000001040A0000000000000000000000000000000000000000000000000000000000000000000840000000000000084000000000000008400000000000000840000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F0000000000000040000000000000004000000000000000400000000000000040000000000000F03F000000000000F03F000000000000F03F020000000200000000000500000001000000FFFFFFFF0000000003")]
[InlineData(
"GEOMETRYCOLLECTION EMPTY",
"000000000104000000000000000001000000FFFFFFFFFFFFFFFF07")]
[InlineData(
"GEOMETRYCOLLECTION (POINT (0 0))",
"000000000104010000000000000000000000000000000000000001000000010000000002000000FFFFFFFF0000000007000000000000000001")]
[InlineData(
"GEOMETRYCOLLECTION (POINT (0 0), POINT (0 1))",
"00000000010402000000000000000000000000000000000000000000000000000000000000000000F03F020000000100000000010100000003000000FFFFFFFF0000000007000000000000000001000000000100000001")]
[InlineData(
"GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (0 1)))",
"000000000104010000000000000000000000000000000000F03F01000000010000000003000000FFFFFFFF0000000007000000000000000007010000000000000001")]
[InlineData(
"GEOMETRYCOLLECTION (POINT (0 0), GEOMETRYCOLLECTION (POINT (0 1)))",
"00000000010402000000000000000000000000000000000000000000000000000000000000000000F03F020000000100000000010100000004000000FFFFFFFF0000000007000000000000000001000000000100000007020000000100000001")]
[InlineData(
"MULTIPOINT ((0 0))",
"000000000104010000000000000000000000000000000000000001000000010000000002000000FFFFFFFF0000000004000000000000000001")]
[InlineData(
"MULTILINESTRING ((0 0, 0 1))",
"00000000010402000000000000000000000000000000000000000000000000000000000000000000F03F01000000010000000002000000FFFFFFFF0000000005000000000000000002")]
[InlineData(
"MULTIPOLYGON (((0 0, 0 1, 1 1, 0 0)))",
"00000000010404000000000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000000001000000020000000002000000FFFFFFFF0000000006000000000000000003")]
public void Read_works(string expected, string bytes)
{
Assert.Equal(expected, Read(bytes).AsText());
}
[Fact]
public void Read_works_with_SRID()
{
var point = Read("E6100000010C00000000000000000000000000000000");
Assert.Equal(4326, point.SRID);
Assert.Equal("POINT (0 0)", point.AsText());
}
[Fact]
public void Read_works_when_Point_with_M()
{
var geometryServices = new NtsGeometryServices(
new PackedCoordinateSequenceFactory(
PackedCoordinateSequenceFactory.PackedType.Double,
dimension: 4),
new PrecisionModel(PrecisionModels.Floating),
srid: -1);
var point = (IPoint)Read(
"00000000010F000000000000F03F000000000000004000000000000008400000000000001040",
geometryServices);
Assert.Equal(4, point.M);
Assert.Equal("POINT (1 2 3)", point.AsText());
}
[Fact]
public void Read_works_when_LineString_with_Ms()
{
var geometryServices = new NtsGeometryServices(
new PackedCoordinateSequenceFactory(
PackedCoordinateSequenceFactory.PackedType.Double,
dimension: 4),
new PrecisionModel(PrecisionModels.Floating),
srid: -1);
var lineString = (ILineString)Read(
"000000000117000000000000000000000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F0000000000000040",
geometryServices);
var mValues = lineString.GetOrdinates(Ordinate.M);
Assert.Equal(1, mValues[0]);
Assert.Equal(2, mValues[1]);
Assert.Equal("LINESTRING (0 0 0, 0 1 0)", lineString.AsText());
}
[Fact]
public void Read_works_when_null()
{
Assert.Null(Read("FFFFFFFF"));
}
[Fact]
public void HandleOrdinates_works()
{
var geometryServices = new NtsGeometryServices(
new PackedCoordinateSequenceFactory(
PackedCoordinateSequenceFactory.PackedType.Double,
dimension: 4),
new PrecisionModel(PrecisionModels.Floating),
srid: -1);
var point = (IPoint)Read(
"00000000010F000000000000F03F000000000000004000000000000008400000000000001040",
geometryServices,
Ordinates.XY);
Assert.Equal("POINT (1 2)", point.AsText());
}
[Fact]
public void Read_throws_when_circular_string()
{
var ex = Assert.Throws<GeoParseException>(
() => Read("0000000002040300000000000000000000000000000000000000000000000000F03F000000000000F03F0000000000000040000000000000000001000000020000000001000000FFFFFFFF0000000008"));
Assert.Equal("Unsupported type: CircularString", ex.Message);
}
[Fact]
public void Read_throws_when_compound_curve()
{
var ex = Assert.Throws<GeoParseException>(
() => Read("0000000002040400000000000000000000000000000000000000000000000000F03F00000000000000000000000000000040000000000000F03F0000000000000840000000000000000001000000030000000001000000FFFFFFFF0000000009020000000203"));
Assert.Equal("Unsupported type: CompoundCurve", ex.Message);
}
[Fact]
public void Read_throws_when_curve_polygon()
{
var ex = Assert.Throws<GeoParseException>(
() => Read("000000000204050000000000000000000040000000000000F03F000000000000F03F00000000000000400000000000000000000000000000F03F000000000000F03F00000000000000000000000000000040000000000000F03F01000000020000000001000000FFFFFFFF000000000A"));
Assert.Equal("Unsupported type: CurvePolygon", ex.Message);
}
[Fact]
public void Read_throws_when_full_globe()
{
var ex = Assert.Throws<GeoParseException>(
() => Read("E61000000224000000000000000001000000FFFFFFFFFFFFFFFF0B"));
Assert.Equal("Unsupported type: FullGlobe", ex.Message);
}
private IGeometry Read(
string bytes,
IGeometryServices geometryServices = null,
Ordinates handleOrdinates = Ordinates.XYZM)
{
var byteArray = new byte[bytes.Length / 2];
for (var i = 0; i < bytes.Length; i += 2)
{
byteArray[i / 2] = byte.Parse(bytes.Substring(i, 2), NumberStyles.HexNumber);
}
var reader = new SqlServerSpatialReader(geometryServices ?? NtsGeometryServices.Instance)
{
HandleOrdinates = handleOrdinates
};
return reader.Read(byteArray);
}
}
}

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

@ -0,0 +1,187 @@
using System.Linq;
using GeoAPI;
using GeoAPI.Geometries;
using NetTopologySuite.Geometries;
using NetTopologySuite.Geometries.Implementation;
using Xunit;
using GeoParseException = GeoAPI.IO.ParseException;
namespace NetTopologySuite.IO
{
public class SqlServerSpatialWriterTest
{
[Theory]
[InlineData(
"POINT EMPTY",
"000000000104000000000000000001000000FFFFFFFFFFFFFFFF01")]
[InlineData(
"POINT (1 2)",
"00000000010C000000000000F03F0000000000000040")]
[InlineData(
"POINT (1 2 3)",
"00000000010D000000000000F03F00000000000000400000000000000840")]
[InlineData(
"LINESTRING EMPTY",
"000000000104000000000000000001000000FFFFFFFFFFFFFFFF02")]
[InlineData(
"LINESTRING (0 0, 0 1)",
"000000000114000000000000000000000000000000000000000000000000000000000000F03F")]
[InlineData(
"LINESTRING (0 0 1, 0 1 2)",
"000000000115000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F0000000000000040")]
[InlineData(
"LINESTRING (0 0, 0 1 2)",
"000000000115000000000000000000000000000000000000000000000000000000000000F03F000000000000F8FF0000000000000040")]
[InlineData(
"LINESTRING (0 0, 0 1, 0 2)",
"00000000010403000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000004001000000010000000001000000FFFFFFFF0000000002")]
[InlineData(
"LINESTRING (0 0 1, 0 1 2, 0 2 3)",
"00000000010503000000000000000000000000000000000000000000000000000000000000000000F03F00000000000000000000000000000040000000000000F03F0000000000000040000000000000084001000000010000000001000000FFFFFFFF0000000002")]
[InlineData(
"POLYGON EMPTY",
"000000000104000000000000000001000000FFFFFFFFFFFFFFFF03")]
[InlineData(
"POLYGON ((0 0, 0 1, 1 1, 0 0))",
"00000000010404000000000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000000001000000020000000001000000FFFFFFFF0000000003")]
[InlineData(
"POLYGON ((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))",
"0000000001040A0000000000000000000000000000000000000000000000000000000000000000000840000000000000084000000000000008400000000000000840000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F0000000000000040000000000000004000000000000000400000000000000040000000000000F03F000000000000F03F000000000000F03F020000000200000000000500000001000000FFFFFFFF0000000003")]
[InlineData(
"POLYGON ((0 0, 0 3, 3 3, 3 0, 0 0), (0 0, 0 2, 2 2, 2 0, 0 0))",
"0000000001000A00000000000000000000000000000000000000000000000000000000000000000008400000000000000840000000000000084000000000000008400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000004000000000000000400000000000000040000000000000000000000000000000000000000000000000020000000200000000000500000001000000FFFFFFFF0000000003")]
[InlineData(
"GEOMETRYCOLLECTION EMPTY",
"000000000104000000000000000001000000FFFFFFFFFFFFFFFF07")]
[InlineData(
"GEOMETRYCOLLECTION (POINT (0 0))",
"000000000104010000000000000000000000000000000000000001000000010000000002000000FFFFFFFF0000000007000000000000000001")]
[InlineData(
"GEOMETRYCOLLECTION (POINT (0 0), POINT (0 1))",
"00000000010402000000000000000000000000000000000000000000000000000000000000000000F03F020000000100000000010100000003000000FFFFFFFF0000000007000000000000000001000000000100000001")]
[InlineData(
"GEOMETRYCOLLECTION (POINT (0 0), POINT EMPTY, POINT (0 1))",
"00000000010402000000000000000000000000000000000000000000000000000000000000000000F03F020000000100000000010100000004000000FFFFFFFF000000000700000000000000000100000000FFFFFFFF01000000000100000001")]
[InlineData(
"GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (0 1)))",
"000000000104010000000000000000000000000000000000F03F01000000010000000003000000FFFFFFFF0000000007000000000000000007010000000000000001")]
[InlineData(
"GEOMETRYCOLLECTION (POINT (0 0), GEOMETRYCOLLECTION (POINT (0 1)))",
"00000000010402000000000000000000000000000000000000000000000000000000000000000000F03F020000000100000000010100000004000000FFFFFFFF0000000007000000000000000001000000000100000007020000000100000001")]
[InlineData(
"MULTIPOINT ((0 0))",
"000000000104010000000000000000000000000000000000000001000000010000000002000000FFFFFFFF0000000004000000000000000001")]
[InlineData(
"MULTILINESTRING ((0 0, 0 1))",
"00000000010402000000000000000000000000000000000000000000000000000000000000000000F03F01000000010000000002000000FFFFFFFF0000000005000000000000000002")]
[InlineData(
"MULTIPOLYGON (((0 0, 0 1, 1 1, 0 0)))",
"00000000010404000000000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000000001000000020000000002000000FFFFFFFF0000000006000000000000000003")]
public void Write_works(string wkt, string expected)
{
var geometry = new WKTReader().Read(wkt);
Assert.Equal(expected, Write(geometry));
}
[Fact]
public void Write_works_with_SRID()
{
var point = new Point(0, 0) { SRID = 4326 };
Assert.Equal(
"E6100000010C00000000000000000000000000000000",
Write(point));
}
[Fact]
public void Write_works_when_Point_with_M()
{
var factory = GeometryServiceProvider.Instance.CreateGeometryFactory(
new PackedCoordinateSequenceFactory(PackedCoordinateSequenceFactory.PackedType.Double, dimension: 4));
var point = factory.CreatePoint(new Coordinate(1, 2, 3));
point.M = 4;
Assert.Equal(
"00000000010F000000000000F03F000000000000004000000000000008400000000000001040",
Write(point));
}
[Fact]
public void Write_works_when_LineString_with_Ms()
{
var points = new PackedDoubleCoordinateSequence(
coords: new double[]
{
0, 0, 0, 1,
0, 1, 0, 2
},
dimensions: 4);
var lineString = new LineString(points, Geometry.DefaultFactory);
Assert.Equal(
"000000000117000000000000000000000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F0000000000000040",
Write(lineString));
}
[Fact]
public void Write_works_when_LineString_with_M_one_NaN()
{
var points = new PackedDoubleCoordinateSequence(
coords: new double[]
{
0, 0, 0, Coordinate.NullOrdinate,
0, 1, 0, 1
},
dimensions: 4);
var lineString = new LineString(points, Geometry.DefaultFactory);
Assert.Equal(
"000000000117000000000000000000000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000F8FF000000000000F03F",
Write(lineString));
}
[Fact]
public void Write_works_when_null()
{
Assert.Equal("FFFFFFFF", Write(null));
}
[Fact]
public void HandleOrdinates_works()
{
var factory = GeometryServiceProvider.Instance.CreateGeometryFactory(
new PackedCoordinateSequenceFactory(PackedCoordinateSequenceFactory.PackedType.Double, dimension: 4));
var point = factory.CreatePoint(new Coordinate(1, 2, 3));
point.M = 4;
Assert.Equal(
"00000000010C000000000000F03F0000000000000040",
Write(point, Ordinates.XY));
}
[Theory]
[InlineData("CIRCULARSTRING (0 0, 1 1, 2 0)")]
[InlineData("COMPOUNDCURVE ((0 0, 1 0), CIRCULARSTRING (1 0, 2 1, 3 0))")]
[InlineData("CURVEPOLYGON (CIRCULARSTRING (2 1, 1 2, 0 1, 1 0, 2 1))")]
[InlineData("FULLGLOBE")]
public void Types_still_unknown(string wkt)
{
var reader = new WKTReader();
// NB: If this doesn't throw, we're unblocked and can add support
Assert.Throws<GeoParseException>(
() => reader.Read(wkt));
}
private string Write(IGeometry geometry, Ordinates handleOrdinates = Ordinates.XYZM)
{
var writer = new SqlServerSpatialWriter
{
HandleOrdinates = handleOrdinates
};
return string.Concat(writer.Write(geometry).Select(b => b.ToString("X2")));
}
}
}