Search code examples
c#sql-serverspatialdappermicro-orm

Query spatial data with dapper


I've found some related questions, but the author gave up and went ahead with using stored procedures to do the 'mapping'.

This is actually a continuation question from here

Model

public class Store
{
    public int Id { get; private set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public DbGeography Location { get; set; }
}

Querying

using (SqlConnection conn = SqlHelper.GetOpenConnection())
{
    const string sql = "Select * from Stores";
    return conn.Query<Store>(sql, new { Tenant_Id = tenantId });
}

Dapper doesn't understand spatial data, and as many had said, it wasn't the authors original intention to support vendor specific implementations. But the documentation to extend the Query<T> support is hard to find


Solution

  • I have an exploration into this here, for which the following test passes:

    class HazGeo
    {
        public int Id { get;set; }
        public DbGeography Geo { get; set; }
    }
    public void DBGeography_SO24405645_SO24402424()
    {
        global::Dapper.SqlMapper.AddTypeHandler(typeof(DbGeography), new GeographyMapper());
        connection.Execute("create table #Geo (id int, geo geography)");
    
        var obj = new HazGeo
        {
            Id = 1,
            Geo = DbGeography.LineFromText("LINESTRING(-122.360 47.656, -122.343 47.656 )", 4326)
        };
        connection.Execute("insert #Geo(id, geo) values (@Id, @Geo)", obj);
        var row = connection.Query<HazGeo>("select * from #Geo where id=1").SingleOrDefault();
        row.IsNotNull();
        row.Id.IsEqualTo(1);
        row.Geo.IsNotNull();
    }
    
    class GeographyMapper : Dapper.SqlMapper.TypeHandler<DbGeography>
    {
        public override void SetValue(IDbDataParameter parameter, DbGeography value)
        {
            parameter.Value = value == null ? (object)DBNull.Value : (object)SqlGeography.Parse(value.AsText());
            ((SqlParameter)parameter).UdtTypeName = "GEOGRAPHY";
        }
        public override DbGeography Parse(object value)
        {
            return (value == null || value is DBNull) ? null : DbGeography.FromText(value.ToString());
        }
    }
    

    It looks viable, but I haven't dotted every i and crossed every t just yet. You're welcome to experiment with that commit locally - I'd love feedback.