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:
Родитель
35bedd676d
Коммит
da777ce517
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")));
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче