Wie weit ist es noch Papi? Geolocation im SQL Server

Vor laaanger Zeit hörte ich schon mal über CLR im SQL Server und die neue Unterstützung von Geodaten in der Version 2008. Aha, ein Feature, für was auch immer habe ich mir gedacht. In der Zwischenzeit sind viele Jahre vergangen. Ich bin zur Überzeugung gelangt, das Location Information die nächste Revolution auslöste. Genau so wenig wie heute IT ohne Timer funktioniert, wird es in Zukunft undenkbar sein ohne Location zu operieren.

Die zentrale Frage heute lautet. Wo sind die nächsten Tankstellen im Umkreis von 10 km? Um Entfernungen performant in Querys verwenden zu können, braucht man Geo Datentypen.

Also zunächst einmal im SQL Server 2012 ein Feld vom Typ geography angelegt.

image

Wenn man im SQL Manager damit direkt arbeiten möchte, wird man nur Binärdaten sehen. In Visual Studio 2012 lassen sich dann auch direkt Daten eingeben. Die Syntax dazu ist Point ( Längengrad und Breitengrad)

image

Nun wollte ich mit ASP.NET Webforms SqlDatasource und einem GridView diese Daten visualisieren. Das funktioniert nicht automatisch. Das Feld mit den Geodaten wird ausgelassen. Aber ich konnte es manuell einfügen.

<asp:GridView ID="GridView1" runat="server" AllowSorting="True" AutoGenerateColumns="False" 
CellPadding="4" DataKeyNames="Id" DataSourceID="SqlDataSource1"
EmptyDataText="There are no data records to display."
ForeColor="#333333" GridLines="None">
<AlternatingRowStyle BackColor="White" /> <Columns>
<asp:BoundField DataField="Id" HeaderText="Id" ReadOnly="True" SortExpression="Id" />
<asp:BoundField DataField="Ort" HeaderText="Ort" SortExpression="Ort" />
<asp:BoundField DataField="Postal" HeaderText="Postal" SortExpression="Postal" />
<asp:BoundField DataField="Tel" HeaderText="Tel" SortExpression="Tel" />
<asp:BoundField DataField="Geo" HeaderText="Geo" SortExpression="Geo" />
<asp:CheckBoxField DataField="isBase" HeaderText="isBase" SortExpression="isBase" />
</Columns>

image

So weit so gut. Das speichern der Daten wird ungleich schwieriger.  Es fängt damit an, das der Parameter keinen passenden Typ aufweist. Bei GUIDs lässt man den Type einfach weg. Funktioniert aber bei Geography nicht.

 <InsertParameters>
<asp:Parameter Name="Ort" Type="String" />
<asp:Parameter Name="Postal" Type="String" />
<asp:Parameter Name="Tel" Type="String" /> <asp:Parameter Name="Geo" />

Meiner Recherche nach ist das schlicht nicht implementiert, wie die ganze Unterstützung nicht, kaum oder erst sehr spät in den benötigten Klassen auftaucht.

Also habe ich versucht mit SQLCommand und benannten Parametern zu arbeiten. So richtig werden die Spatial Types nicht unterstützt. Meine erste Lösung (auch bekannt als Well Known Text oder WKT) nimmt einen VarChar Parameter und setzt einfach den String zusammen.

sqlcmd.Parameters.Add(

New
SqlParameter("@Geo",
"POINT (" + CType(FormView1.FindControl("GeoTextBox"), TextBox).Text + ")"))

Angeblich soll dies wesentlich langsamer sein als die Methode über UDT sein.

 Dim par As New SqlParameter("@Geo", SqlDbType.Udt)

par.UdtTypeName = "Geography"
par.Value = DbGeography.FromText("POINT (" + CType(FormView1.FindControl("GeoTextBox"), TextBox).Text + ")")
sqlcmd.Parameters.Add(par)

Das sieht zwar ganz gut aus, funktioniert aber nicht.

Der angegebene Typ ist nicht auf dem Zielserver System.Data.Spatial.DbGeography, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 registriert.

Man kann sich über Referenz auf Microsoft.SqlServer.Types.dll (zu finden unter C:\Program Files (x86)\Microsoft SQL Server\110\Shared) die SQL Datentypen besorgen. Für die Point bestimmung wird Longitude, Latidude und Messschema benötigt. 4326 ist offensichtlich das gängigste. Wenn es unterschiedliche Werte sind kann man dann keine Entfernung zwischen Punkten berechnen.

 Dim cords As String() = CType(FormView1.FindControl("GeoTextBox"), TextBox).Text.Split(" ")
Dim par As New SqlParameter("@Geo", SqlDbType.Udt)
par.UdtTypeName = "Geography"
par.Value = SqlGeography.Point(

Double.Parse(cords(0), System.Globalization.CultureInfo.InvariantCulture),
Double
.Parse(cords(1), System.Globalization.CultureInfo.InvariantCulture),
4326)
sqlcmd.Parameters.Add(par)

Wenn man die gängigen Suchmaschinen (Bing natürlich) bemüht, wird man fast immer über das Entity Framework stolpern. In der Tat ist seit .net 4.5 und EF 5 recht einfach möglich Spatial Datentypen zu verwenden.

In einem ASP.NET Detailsview kommt  eine Textbox zum Einsatz. Per Placeholder wird der Benutzer darauf hingewiesen in welchem Format die Daten erwartet werden. Wenn er nicht lesen kann, bekommt er dann vom Validator Control einen auf die Finger geklopft.

  <asp:TemplateField>
<InsertItemTemplate>
<asp:RegularExpressionValidator ID="RegularExpressionValidator2" runat="server"
ErrorMessage="Format 0.00000 0.000000"
ControlToValidate="Geo" Display="Dynamic"
ForeColor="Red" ValidationExpression="(-)?(\d+)(.)(\d+)(\s+)(-)?(\d+)(.)(\d+)">
</
asp:RegularExpressionValidator>
<asp:TextBox runat="server" ID="Geo" Text='<%# Bind("Geo")%>' placeholder="0.00000 0.00000">
</
asp:TextBox>
</InsertItemTemplate>
</asp:TemplateField>

Der Datenzugriff wird in der Tat über ein Webform Control erledigt, ohne eine Zeile Code.

<asp:EntityDataSource ID="EntityDataSource1" runat="server" 
ConnectionString="name=ppcompanyEntities1"
DefaultContainerName="ppcompanyEntities1" EnableDelete="True"
EnableFlattening="False" EnableInsert="True"
EnableUpdate="True" EntitySetName="prometricLocations">
</asp:EntityDataSource>

Letzendlich braucht es eine Zeile VB.NET um den passenden Typ zu erzeugen. Nicht ganz nachvollziehbar, klappt es hier mit der dbGeography Klasse (statt der SqlGeography).

 Protected Sub DetailsView1_ItemInserting(sender As Object, e As DetailsViewInsertEventArgs)
e.Values("Geo") = DbGeography.FromText("POINT (" + e.Values("Geo") + ")")

Dieser Weg erscheint wesentlich einfacher.

Hinweise auf besseres Vorgehen oder Fehler sind herzlich Willkommen. Einige Fragen habe ich mir selbst noch nicht beantworten können.

Training, Schulung, JavaScript, HTML, CSS, Dot Net, Asp Net

Month List