Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spatial type and SRID annotations #718

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,16 @@ public override MethodCallCodeFragment GenerateFluentApi(IProperty property, IAn

case NpgsqlAnnotationNames.Comment:
return new MethodCallCodeFragment(nameof(NpgsqlPropertyBuilderExtensions.ForNpgsqlHasComment), annotation.Value);
}

return null;
case NpgsqlAnnotationNames.SpatialType:
return new MethodCallCodeFragment(nameof(NpgsqlPropertyBuilderExtensions.ForNpgsqlHasSpatialType), annotation.Value);

case NpgsqlAnnotationNames.Srid:
return new MethodCallCodeFragment(nameof(NpgsqlPropertyBuilderExtensions.ForNpgsqlHasSrid), annotation.Value);

default:
return null;
}
}

public override MethodCallCodeFragment GenerateFluentApi(IIndex index, IAnnotation annotation)
Expand Down
66 changes: 65 additions & 1 deletion src/EFCore.PG/Extensions/NpgsqlPropertyBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,74 @@ public static PropertyBuilder ForNpgsqlHasComment(
public static PropertyBuilder<TEntity> ForNpgsqlHasComment<TEntity>(
[NotNull] this PropertyBuilder<TEntity> propertyBuilder,
[CanBeNull] string comment)
=> (PropertyBuilder<TEntity>)ForNpgsqlHasComment((PropertyBuilder)propertyBuilder, comment);
=> (PropertyBuilder<TEntity>)ForNpgsqlHasComment((PropertyBuilder)propertyBuilder, comment);

#endregion Comment

#region Spatial methods

/// <summary>
/// Configures the spatial type of the column that the property maps to when targeting PostgreSQL.
/// </summary>
/// <param name="propertyBuilder">The builder for the property being configured.</param>
/// <param name="type">The spatial type.</param>
/// <returns>
/// The same builder instance so that multiple calls can be chained.
/// </returns>
public static PropertyBuilder ForNpgsqlHasSpatialType([NotNull] this PropertyBuilder propertyBuilder, [CanBeNull] string type)
{
Check.NotNull(propertyBuilder, nameof(propertyBuilder));

propertyBuilder.Metadata.Npgsql().SpatialType = type;

return propertyBuilder;
}

/// <summary>
/// Configures the spatial type of the column that the property maps to when targeting PostgreSQL.
/// </summary>
/// <param name="propertyBuilder">The builder for the property being configured.</param>
/// <param name="type">The spatial type.</param>
/// <returns>
/// The same builder instance so that multiple calls can be chained.
/// </returns>
public static PropertyBuilder<TProperty> ForNpgsqlHasSpatialType<TProperty>(
[NotNull] this PropertyBuilder<TProperty> propertyBuilder,
[CanBeNull] string type)
=> (PropertyBuilder<TProperty>)ForNpgsqlHasSpatialType((PropertyBuilder)propertyBuilder, type);

/// <summary>
/// Configures the SRID of the column that the property maps to when targeting PostgreSQL.
/// </summary>
/// <param name="propertyBuilder">The builder for the property being configured.</param>
/// <param name="srid">The SRID.</param>
/// <returns>
/// The same builder instance so that multiple calls can be chained.
/// </returns>
public static PropertyBuilder ForNpgsqlHasSrid([NotNull] this PropertyBuilder propertyBuilder, int srid)
{
Check.NotNull(propertyBuilder, nameof(propertyBuilder));

propertyBuilder.Metadata.Npgsql().Srid = srid;

return propertyBuilder;
}

/// <summary>
/// Configures the SRID of the column that the property maps to when targeting PostgreSQL.
/// </summary>
/// <param name="propertyBuilder">The builder for the property being configured.</param>
/// <param name="srid">The SRID.</param>
/// <returns>
/// The same builder instance so that multiple calls can be chained.
/// </returns>
public static PropertyBuilder<TProperty> ForNpgsqlHasSrid<TProperty>(
[NotNull] this PropertyBuilder<TProperty> propertyBuilder,
int srid)
=> (PropertyBuilder<TProperty>)ForNpgsqlHasSrid((PropertyBuilder)propertyBuilder, srid);

#endregion

static NpgsqlPropertyBuilderAnnotations GetNpgsqlInternalBuilder(PropertyBuilder propertyBuilder)
=> propertyBuilder.GetInfrastructure<InternalPropertyBuilder>().Npgsql(ConfigurationSource.Explicit);
}
Expand Down
18 changes: 17 additions & 1 deletion src/EFCore.PG/Metadata/INpgsqlPropertyAnnotations.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
using Microsoft.EntityFrameworkCore.Metadata;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata
{
public interface INpgsqlPropertyAnnotations : IRelationalPropertyAnnotations
{
[CanBeNull]
NpgsqlValueGenerationStrategy? ValueGenerationStrategy { get; }

[CanBeNull]
string HiLoSequenceName { get; }

[CanBeNull]
string HiLoSequenceSchema { get; }

[CanBeNull]
ISequence FindHiLoSequence();

[CanBeNull]
string Comment { get; }

[CanBeNull]
string SpatialType { get; }

[CanBeNull]
int? Srid { get; }
}
}
2 changes: 2 additions & 0 deletions src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public static class NpgsqlAnnotationNames
public const string Tablespace = Prefix + "Tablespace";
public const string StorageParameterPrefix = Prefix + "StorageParameter:";
public const string Comment = Prefix + "Comment";
public const string SpatialType = Prefix + "SpatialType";
public const string Srid = Prefix + "Srid";

// Database model annotations

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ public NpgsqlPropertyBuilderAnnotations(
/// </summary>
protected override bool ShouldThrowOnInvalidConfiguration => Annotations.ConfigurationSource == ConfigurationSource.Explicit;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool HasSpatialType(string value) => SetSpatialType(value);

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool HasSrid(int? value) => SetSrid(value);

#pragma warning disable 109
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
Expand Down
40 changes: 40 additions & 0 deletions src/EFCore.PG/Metadata/NpgsqlPropertyAnnotations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,5 +319,45 @@ protected virtual bool SetComment([CanBeNull] string value)
=> Annotations.SetAnnotation(
NpgsqlAnnotationNames.Comment,
Check.NullButNotEmpty(value, nameof(value)));

/// <summary>
/// Gets or sets the spatial type to use when creating a column for this property.
/// </summary>
public virtual string SpatialType
{
get => (string)Annotations.Metadata[NpgsqlAnnotationNames.SpatialType];
[param: CanBeNull]
set => SetSpatialType(value);
}

/// <summary>
/// Sets the spatial type to use when creating a column for this property.
/// </summary>
/// <param name="value">The SRID.</param>
/// <returns>
/// True if the annotation was set; otherwise, false.
/// </returns>
protected virtual bool SetSpatialType(string value)
=> Annotations.SetAnnotation(NpgsqlAnnotationNames.SpatialType, value);

/// <summary>
/// Gets or sets the SRID to use when creating a column for this property.
/// </summary>
public virtual int? Srid
{
get => (int?)Annotations.Metadata[NpgsqlAnnotationNames.Srid];
[param: CanBeNull]
set => SetSrid(value);
}

/// <summary>
/// Sets the SRID to use when creating a column for this property.
/// </summary>
/// <param name="value">The SRID.</param>
/// <returns>
/// True if the annotation was set; otherwise, false.
/// </returns>
protected virtual bool SetSrid(int? value)
=> Annotations.SetAnnotation(NpgsqlAnnotationNames.Srid, value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ public override IEnumerable<IAnnotation> For(IEntityType entityType)

public override IEnumerable<IAnnotation> For(IProperty property)
{
if (property.Npgsql().ValueGenerationStrategy == NpgsqlValueGenerationStrategy.SerialColumn)
yield return new Annotation(NpgsqlAnnotationNames.ValueGenerationStrategy, NpgsqlValueGenerationStrategy.SerialColumn);
if (property.Npgsql().ValueGenerationStrategy == NpgsqlValueGenerationStrategy.IdentityAlwaysColumn)
yield return new Annotation(NpgsqlAnnotationNames.ValueGenerationStrategy, NpgsqlValueGenerationStrategy.IdentityAlwaysColumn);
if (property.Npgsql().ValueGenerationStrategy == NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
yield return new Annotation(NpgsqlAnnotationNames.ValueGenerationStrategy, NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
if (property.Npgsql().Comment != null)
yield return new Annotation(NpgsqlAnnotationNames.Comment, property.Npgsql().Comment);
if (property.Npgsql().ValueGenerationStrategy is NpgsqlValueGenerationStrategy npgsqlValueGenerationStrategy)
yield return new Annotation(NpgsqlAnnotationNames.ValueGenerationStrategy, npgsqlValueGenerationStrategy);
if (property.Npgsql().Comment is string comment)
yield return new Annotation(NpgsqlAnnotationNames.Comment, comment);
if (property.Npgsql().SpatialType is string spatialType)
yield return new Annotation(NpgsqlAnnotationNames.SpatialType, spatialType);
if (property.Npgsql().Srid is int srid)
yield return new Annotation(NpgsqlAnnotationNames.Srid, srid);
}

public override IEnumerable<IAnnotation> For(IIndex index)
Expand Down
45 changes: 28 additions & 17 deletions src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -957,23 +957,34 @@ protected override void ColumnDefinition(
}
}

base.ColumnDefinition(
schema,
table,
name,
clrType,
type,
unicode,
maxLength,
fixedLength,
rowVersion,
nullable,
defaultValue,
defaultValueSql,
computedColumnSql,
annotatable,
model,
builder);
builder
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(name))
.Append(' ')
.Append(type);

if (string.Equals(type, "geography", StringComparison.OrdinalIgnoreCase) ||
string.Equals(type, "geometry", StringComparison.OrdinalIgnoreCase))
{
if (annotatable[NpgsqlAnnotationNames.SpatialType] is string spatialType)
{
builder
.Append('(')
.Append(spatialType);

if (annotatable[NpgsqlAnnotationNames.Srid] is int srid)
{
builder
.Append(',')
.Append(srid);
}

builder.Append(')');
}
}

builder.Append(nullable ? " NULL" : " NOT NULL");

DefaultValue(defaultValue, defaultValueSql, builder);

switch (valueGenerationStrategy)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestModels.SpatialModel;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
using NetTopologySuite;
Expand Down Expand Up @@ -40,7 +41,7 @@ protected override IServiceCollection AddServices(IServiceCollection serviceColl
NpgsqlConnection.GlobalTypeMapper.UseNetTopologySuite();

return base.AddServices(serviceCollection)
.AddEntityFrameworkNpgsqlNetTopologySuite();
.AddEntityFrameworkNpgsqlNetTopologySuite();
}

public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
Expand All @@ -56,6 +57,24 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
base.OnModelCreating(modelBuilder, context);

modelBuilder.HasPostgresExtension("postgis");

modelBuilder.Entity<PointEntity>()
.Property(e => e.Point)
.ForNpgsqlHasSpatialType("POINT");

modelBuilder.Entity<PointEntity>()
.Property(e => e.ConcretePoint)
.ForNpgsqlHasSpatialType("POINT")
.ForNpgsqlHasSrid(4326);

modelBuilder.Entity<LineStringEntity>()
.Property(e => e.LineString)
.ForNpgsqlHasSpatialType("LINESTRING");

modelBuilder.Entity<MultiLineStringEntity>()
.Property(e => e.MultiLineString)
.ForNpgsqlHasSpatialType("MULTILINESTRING")
.ForNpgsqlHasSrid(4326);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using GeoAPI.Geometries;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestModels.SpatialModel;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
Expand All @@ -18,7 +18,7 @@ protected override IServiceCollection AddServices(IServiceCollection serviceColl
NpgsqlConnection.GlobalTypeMapper.UseNetTopologySuite();

return base.AddServices(serviceCollection)
.AddEntityFrameworkNpgsqlNetTopologySuite();
.AddEntityFrameworkNpgsqlNetTopologySuite();
}

public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
Expand All @@ -34,6 +34,24 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
base.OnModelCreating(modelBuilder, context);

modelBuilder.HasPostgresExtension("postgis");

modelBuilder.Entity<PointEntity>()
.Property(e => e.Point)
.ForNpgsqlHasSpatialType("POINT");

modelBuilder.Entity<PointEntity>()
.Property(e => e.ConcretePoint)
.ForNpgsqlHasSpatialType("POINT")
.ForNpgsqlHasSrid(0);

modelBuilder.Entity<LineStringEntity>()
.Property(e => e.LineString)
.ForNpgsqlHasSpatialType("LINESTRING");

modelBuilder.Entity<MultiLineStringEntity>()
.Property(e => e.MultiLineString)
.ForNpgsqlHasSpatialType("MULTILINESTRING")
.ForNpgsqlHasSrid(0);
}
}
}
19 changes: 19 additions & 0 deletions test/EFCore.PG.FunctionalTests/SpatialNpgsqlFixture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.TestModels.SpatialModel;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
Expand Down Expand Up @@ -29,6 +30,24 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con

modelBuilder.HasPostgresExtension("postgis")
.HasPostgresExtension("uuid-ossp");

modelBuilder.Entity<PointEntity>()
.Property(e => e.Point)
.ForNpgsqlHasSpatialType("POINT");

modelBuilder.Entity<PointEntity>()
.Property(e => e.ConcretePoint)
.ForNpgsqlHasSpatialType("POINT")
.ForNpgsqlHasSrid(0);

modelBuilder.Entity<LineStringEntity>()
.Property(e => e.LineString)
.ForNpgsqlHasSpatialType("LINESTRING");

modelBuilder.Entity<MultiLineStringEntity>()
.Property(e => e.MultiLineString)
.ForNpgsqlHasSpatialType("MULTILINESTRING")
.ForNpgsqlHasSrid(0);
}
}
}