Deserializing DbGeometry с Newtonsoft.Json

Я создаю SPA, используя Angular, Breeze и Web API 2, следуя подходу, описанному Джоном Папой в его последнем курсе PluralSight.

Все работает хорошо, и я могу извлекать информацию, обновлять, вставлять, удалять обратно на сервер. Однако я использую пространственные типы, и когда я пытаюсь обновить объект с пространственным типом, я получаю следующую ошибку:

Исключение типа "Newtonsoft.Json.JsonSerializationException" произошло в     Newtonsoft.Json.dll, но не был обработан в коде пользователя

Дополнительная информация: Ошибка получения значения из "WellKnownValue" на     'System.Data.Entity.Spatial.DbGeometry'.

Внутреннее исключение, похоже, указывает на то, что значение WellKnownValue равно null, но, хотя я проверял отправку JSON на сервер, который затем отправляется на Breeze ContextProvider и сохраняется с помощью метода SaveChanges.

{
"entities": [
 {
  "TableKey": 2,
  "CaseName": "Mikhail Lermontov",
  "StartDate": "2013-06-11T00:00:00Z",
  "EndDate": null,
  "IsCurrent": true,
  "SRID": 109,
  "Shape": {
    "$id": "2",
    "$type": "System.Data.Entity.Spatial.DbGeometry, EntityFramework",
    "Geometry": {
      "$id": "3",
      "$type": "System.Data.Entity.Spatial.DbGeometryWellKnownValue, EntityFramework",
      "CoordinateSystemId": 2193,
      "WellKnownText": "POLYGON ((1695943 5462665, 1713098 5462665, 1713098 5449659, 1695943 5449659, 1695943 5462665))"
    }
  },
  "SpillLocation": "Marlborough Sounds",
  "Image": "http://www.nzmaritime.co.nz/images/lm5.jpg\r\n",
  "DefaultBaseMapKey": 2,
  "__unmapped": {
    "isPartial": false
  },
  "entityAspect": {
    "entityTypeName": "DatSpillCase:#Osiris.Model",
    "defaultResourceName": "DatSpillCases",
    "entityState": "Modified",
    "originalValuesMap": {
      "CaseName": "Mikhail Lermontov"
    },
    "autoGeneratedKey": {
      "propertyName": "TableKey",
      "autoGeneratedKeyType": "Identity"
    }
  }
}
 ],
  "saveOptions": {}
}

Итак, мой вопрос: можно ли десериализовать типы DbGeometry в библиотеке NewtonSoft, а если нет, то какие предложения существуют, чтобы обойти это.

Ответ 1

System.Data.Spatial.DbGeometry не играет хорошо с Newtonsoft.Json

Вам нужно создать JsonConverter для преобразования DbGeometry

public class DbGeometryConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType.IsAssignableFrom(typeof(string));
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JObject location = JObject.Load(reader);
            JToken token = location["Geometry"]["WellKnownText"];
            string value = token.ToString();

            DbGeometry converted = DbGeometry.PolygonFromText(value, 2193);
            return converted;
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            // Base serialization is fine
            serializer.Serialize(writer, value);
        }
    }

Затем на вашем свойстве в вашей модели добавьте атрибут

[JsonConverter(typeof(DbGeometryConverter))]
public DbGeometry Shape { get; set; }

Теперь, когда вы нажмете на BreezeController, десериализация будет обработана нашим новым DbGeometryConverter.

Надеюсь, что это поможет.

Ответ 2

Ответ выше отлично работает, но жестко запрограммирован для SRID (CoordinateSystemId) 2193. Однако идентификатор системы координат может присутствовать в сериализованных данных, как показано в вопросе, или он может присутствовать в WellKnownText "SRID = 2193; POINT (0 0)". Также этот метод будет читать только многоугольник, но для WellKnownText может быть много чего, например, коллекции геометрии, точки, Linestring и т.д. Чтобы восстановить этот метод, метод ReadJson можно обновить, чтобы использовать более общий метод FromText, как показано ниже. Здесь приведен класс выше, обновленный с более общей системой координат, а также для любого типа геометрии. Я также добавил версию географии для справки.

public class DbGeometryConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType.IsAssignableFrom(typeof(string));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject location = JObject.Load(reader);
        JToken token = location["Geometry"]["WellKnownText"];
        string value = token.ToString();
        JToken sridToken = location["Geometry"]["CoordinateSystemId"];
        int srid;
        if (sridToken == null || int.TryParse(sridToken.ToString(), out srid) == false || value.Contains("SRID"))
        {
            //Set default coordinate system here.
            srid = 0;
        }

        DbGeometry converted;
        if (srid > 0)
            converted = DbGeometry.FromText(value, srid);
        else
            converted = DbGeometry.FromText(value);
        return converted;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // Base serialization is fine
        serializer.Serialize(writer, value);
    }
}

public class DbGeographyConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType.IsAssignableFrom(typeof(string));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject location = JObject.Load(reader);
        JToken token = location["Geometry"]["WellKnownText"];
        string value = token.ToString();
        JToken sridToken = location["Geometry"]["CoordinateSystemId"];
        int srid;
        if (sridToken == null || int.TryParse(sridToken.ToString(), out srid) == false || value.Contains("SRID"))
        {
            //Set default coordinate system here.
            //NOTE: Geography should always have an SRID, and it has to match the data in the database else all comparisons will return NULL!
            srid = 0;
        }
        DbGeography converted;
        if (srid > 0)
            converted = DbGeography.FromText(value, srid);
        else
            converted = DbGeography.FromText(value);
        return converted;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // Base serialization is fine
        serializer.Serialize(writer, value);
    }
}

Ответ 3

Я не понимаю, почему нет. На линии с (DbGeometryWellKnownValue):

"$type": "System.Data.Entity.Spatial.DbGeometryWellKnownValue, EntityFramework",

должно ли это быть (DbGeometry.WellKnownValue)?

  "$type": "System.Data.Entity.Spatial.DbGeometry.WellKnownValue, EntityFramework",