You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在EF Core 5配置Oracle DbContext时集成NetTopologySuite

解决方案:.NET 5.0 + Oracle EF Core 拓扑类型映射(无需Devart)

环境说明

你的项目依赖版本:

  • Microsoft.EntityFrameworkCore 5.0.10
  • NetTopologySuite.IO.Oracle 3.0.0
  • Oracle.EntityFrameworkCore 5.21.3

Oracle官方EF Core 5.x版本无内置NetTopologySuite类型映射支持,需要通过自定义类型映射、值转换器和参数配置实现SDO_GEOMETRY列的迁移与读写,同时兼容Oracle 11g。


步骤1:实现自定义值转换器

借助NetTopologySuite.IO.Oracle的序列化工具,实现Geometry与Oracle原生SdoGeometry的双向转换:

using NetTopologySuite.Geometries;
using NetTopologySuite.IO.Sdo;

// 非可空Geometry类型转换器
public class GeometryToSdoConverter : ValueConverter<Geometry, SdoGeometry>
{
    private static readonly SdoGeometryReader _reader = new();
    private static readonly SdoGeometryWriter _writer = new();

    public GeometryToSdoConverter()
        : base(
            geo => _writer.Write(geo),
            sdo => _reader.Read(sdo))
    {
    }
}

// 可空Geometry类型转换器
public class NullableGeometryToSdoConverter : ValueConverter<Geometry?, SdoGeometry?>
{
    private static readonly SdoGeometryReader _reader = new();
    private static readonly SdoGeometryWriter _writer = new();

    public NullableGeometryToSdoConverter()
        : base(
            geo => geo == null ? null : _writer.Write(geo),
            sdo => sdo == null ? null : _reader.Read(sdo))
    {
    }
}

步骤2:注册Oracle拓扑类型映射

实现自定义类型映射源,让EF Core识别Geometry类型对应Oracle的SDO_GEOMETRY列,并正确配置数据库参数:

using Microsoft.EntityFrameworkCore.Storage;
using Oracle.EntityFrameworkCore.Storage;
using Oracle.ManagedDataAccess.Client;

// 自定义类型映射源,处理Geometry类型的数据库映射逻辑
public class OracleGeoTypeMappingSource : RelationalTypeMappingSource
{
    public OracleGeoTypeMappingSource(RelationalTypeMappingSourceDependencies dependencies)
        : base(dependencies)
    {
    }

    protected override RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo)
    {
        var clrType = mappingInfo.ClrType;
        if (clrType == typeof(Geometry) || clrType == typeof(Geometry?))
        {
            return new OracleGeoTypeMapping("SDO_GEOMETRY", clrType);
        }
        return base.FindMapping(mappingInfo);
    }
}

// 自定义Oracle类型映射,配置SDO_GEOMETRY参数的类型
public class OracleGeoTypeMapping : RelationalTypeMapping
{
    public OracleGeoTypeMapping(string storeType, Type clrType)
        : base(storeType, clrType)
    {
    }

    protected OracleGeoTypeMapping(RelationalTypeMappingParameters parameters)
        : base(parameters)
    {
    }

    public override RelationalTypeMapping Clone(string storeType, int? size)
    {
        return new OracleGeoTypeMapping(Parameters.WithStoreTypeAndSize(storeType, size));
    }

    public override CoreTypeMapping Clone(ValueConverter converter)
    {
        return new OracleGeoTypeMapping(Parameters.WithConverter(converter));
    }

    protected override void ConfigureParameter(DbParameter parameter)
    {
        base.ConfigureParameter(parameter);
        if (parameter is OracleParameter oracleParam)
        {
            oracleParam.OracleDbType = OracleDbType.Object;
            oracleParam.UdtTypeName = "MDSYS.SDO_GEOMETRY";
        }
    }
}

步骤3:配置DbContext

在OnModelCreating中绑定转换器,并在服务注册时替换类型映射源,同时保留Oracle 11兼容性:

DbContext代码

public class MyDbContext : DbContext
{
    public DbSet<MyGeo> MyGeos { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // 全局注册Geometry类型的转换器与列类型
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            foreach (var property in entityType.GetProperties())
            {
                if (property.ClrType == typeof(Geometry))
                {
                    property.SetColumnType("SDO_GEOMETRY");
                    property.SetValueConverter(new GeometryToSdoConverter());
                }
                else if (property.ClrType == typeof(Geometry?))
                {
                    property.SetColumnType("SDO_GEOMETRY");
                    property.SetValueConverter(new NullableGeometryToSdoConverter());
                }
            }
        }

        // 实体单独配置(可选,与全局配置二选一)
        modelBuilder.Entity<MyGeo>(builder =>
        {
            builder.Property(p => p.MySdoGeo)
                   .HasColumnType("SDO_GEOMETRY");
        });
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseOracle("your_connection_string", 
            o => o.UseOracleSQLCompatibility("11"));
    }
}

// 实体类
public class MyGeo
{
    public int Id { get; set; }
    public Geometry MySdoGeo { get; set; }
}

服务注册(Startup/Program.cs)

services.AddDbContext<MyDbContext>(options =>
    options.UseOracle("your_connection_string", 
            o => o.UseOracleSQLCompatibility("11"))
        .ReplaceService<IRelationalTypeMappingSource, OracleGeoTypeMappingSource>());

为什么之前的尝试失败?

  1. 直接使用SdoGeometry类型:Oracle EF Core provider无内置该类型的映射逻辑,EF Core无法识别其与数据库列的关联。
  2. 自定义转换器未配类型映射:仅添加HasConversion但未注册类型映射源,EF Core仍不认可Geometry可映射到SDO_GEOMETRY,且未配置Oracle参数的UDT类型,驱动无法正确处理数据。

扩展支持的思路(若上述方案仍有问题)

  1. 增强类型映射源:若需支持更多拓扑类型(如Point/Polygon),可在OracleGeoTypeMappingSource中添加对应CLR类型的映射逻辑。
  2. 自定义迁移SQL生成:重写IMigrationsSqlGenerator,确保生成的CREATE TABLE语句符合Oracle 11g的SDO_GEOMETRY定义规范。
  3. 验证UDT权限:确保数据库用户拥有MDSYS.SDO_GEOMETRY类型的访问权限,避免读写时出现权限错误。

内容的提问来源于stack exchange,提问作者CalciferZero

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 01:35:16