Skip to content

Commit

Permalink
MSSQL Spatial: Add EF.Functions.CurveToLine
Browse files Browse the repository at this point in the history
Provides a workaround for returning CircularString, CompoundCurve, and CurvePolygon instances to the client
  • Loading branch information
bricelam committed May 27, 2022
1 parent 8a6370f commit 8eef3f9
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using NetTopologySuite.Geometries;

// ReSharper disable once CheckNamespace

namespace Microsoft.EntityFrameworkCore;

/// <summary>
/// Contains extension methods on <see cref="DbFunctions" /> for the Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite library.
/// </summary>
public static class SqlServerNetTopologySuiteDbFunctionsExtensions
{
/// <summary>
/// Maps to the SQL Server <c>STCurveToLine</c> function which returns a polygonal approximation of an instance that contains circular arc segments.
/// </summary>
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
/// <param name="geometry">The instance containing circular arc segments.</param>
/// <returns>The polygonal approximation.</returns>
public static Geometry CurveToLine(this DbFunctions _, Geometry geometry)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(CurveToLine)));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using NetTopologySuite.Geometries;
using ExpressionExtensions = Microsoft.EntityFrameworkCore.Query.ExpressionExtensions;

namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class SqlServerNetTopologySuiteDbFunctionsMethodCallTranslator : IMethodCallTranslator
{
private static readonly MethodInfo CurveToLineMethodInfo = typeof(SqlServerNetTopologySuiteDbFunctionsExtensions)
.GetMethod(nameof(SqlServerNetTopologySuiteDbFunctionsExtensions.CurveToLine), new[] { typeof(DbFunctions), typeof(Geometry) })!;

private readonly IRelationalTypeMappingSource _typeMappingSource;
private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public SqlServerNetTopologySuiteDbFunctionsMethodCallTranslator(
IRelationalTypeMappingSource typeMappingSource,
ISqlExpressionFactory sqlExpressionFactory)
{
_typeMappingSource = typeMappingSource;
_sqlExpressionFactory = sqlExpressionFactory;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual SqlExpression? Translate(SqlExpression? instance, MethodInfo method, IReadOnlyList<SqlExpression> arguments, IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
if (method.Equals(CurveToLineMethodInfo))
{
return _sqlExpressionFactory.Function(
arguments[1],
"STCurveToLine",
Enumerable.Empty<SqlExpression>(),
nullable: true,
instancePropagatesNullability: true,
argumentsPropagateNullability: Enumerable.Empty<bool>(),
method.ReturnType,
_typeMappingSource.FindMapping(
method.ReturnType,
ExpressionExtensions.InferTypeMapping(arguments[1])!.StoreType));
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public SqlServerNetTopologySuiteMethodCallTranslatorPlugin(
new SqlServerGeometryMethodTranslator(typeMappingSource, sqlExpressionFactory),
new SqlServerGeometryCollectionMethodTranslator(typeMappingSource, sqlExpressionFactory),
new SqlServerLineStringMethodTranslator(typeMappingSource, sqlExpressionFactory),
new SqlServerPolygonMethodTranslator(typeMappingSource, sqlExpressionFactory)
new SqlServerPolygonMethodTranslator(typeMappingSource, sqlExpressionFactory),
new SqlServerNetTopologySuiteDbFunctionsMethodCallTranslator(typeMappingSource, sqlExpressionFactory)
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@

namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;

internal class SqlServerPointMemberTranslator : IMemberTranslator
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class SqlServerPointMemberTranslator : IMemberTranslator
{
private static readonly IDictionary<MemberInfo, string> MemberToPropertyName = new Dictionary<MemberInfo, string>
{
Expand All @@ -28,12 +34,24 @@ internal class SqlServerPointMemberTranslator : IMemberTranslator

private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public SqlServerPointMemberTranslator(ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

public SqlExpression? Translate(
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual SqlExpression? Translate(
SqlExpression? instance,
MemberInfo member,
Type returnType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.TestModels.SpatialModel;
using NetTopologySuite.Geometries;

namespace Microsoft.EntityFrameworkCore.Query;

public class SpatialQuerySqlServerGeographyTest : SpatialQueryRelationalTestBase<SpatialQuerySqlServerGeographyFixture>
Expand Down Expand Up @@ -156,6 +159,26 @@ public override Task Covers(bool async)
public override Task Crosses(bool async)
=> Task.CompletedTask;

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task CurveToLine(bool async)
{
await AssertQuery(
async,
ss => ss.Set<PolygonEntity>().Select(e => new { e.Id, CurveToLine = EF.Functions.CurveToLine(e.Polygon) }),
ss => ss.Set<PolygonEntity>().Select(e => new { e.Id, CurveToLine = (Geometry)e.Polygon }),
elementSorter: x => x.Id,
elementAsserter: (e, a) =>
{
Assert.Equal(e.Id, a.Id);
Assert.Equal(e.CurveToLine, a.CurveToLine, GeometryComparer.Instance);
});

AssertSql(
@"SELECT [p].[Id], [p].[Polygon].STCurveToLine() AS [CurveToLine]
FROM [PolygonEntity] AS [p]");
}

public override async Task Difference(bool async)
{
await base.Difference(async);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,26 @@ public override async Task Crosses(bool async)
FROM [LineStringEntity] AS [l]");
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task CurveToLine(bool async)
{
await AssertQuery(
async,
ss => ss.Set<PolygonEntity>().Select(e => new { e.Id, CurveToLine = EF.Functions.CurveToLine(e.Polygon) }),
ss => ss.Set<PolygonEntity>().Select(e => new { e.Id, CurveToLine = (Geometry)e.Polygon }),
elementSorter: x => x.Id,
elementAsserter: (e, a) =>
{
Assert.Equal(e.Id, a.Id);
Assert.Equal(e.CurveToLine, a.CurveToLine, GeometryComparer.Instance);
});

AssertSql(
@"SELECT [p].[Id], [p].[Polygon].STCurveToLine() AS [CurveToLine]
FROM [PolygonEntity] AS [p]");
}

public override async Task Difference(bool async)
{
await base.Difference(async);
Expand Down

0 comments on commit 8eef3f9

Please sign in to comment.