Иногда перед разработчиками встает задача использования JSON-полей в Entity Framework Core. Традиционный подход с использованием Fluent API требует написания дополнительного кода, что может усложнить проект. Пакет JsonProperty.EFCore решает эту проблему. Эта статья расскажет о пользе JsonProperty.EFCore и о том, как он упрощает работу с JSON-полями, что делает его удобным инструментом для разработчиков.

Проблема: Сложное управление JSON-полями

Entity Framework Core отлично поддерживает работу с реляционными базами данных, но управление JSON-полями может стать непростой задачей. Настройка Fluent API для отображения JSON-полей на свойства сущностей требует написания дополнительного кода, что может увеличить сложность проекта. С учетом возникающей иногда необходимости хранить данные в формате JSON разработчикам необходимо эффективное решение для интеграции JSON-полей в модели EF Core.

JsonProperty.EFCore: Упрощенный подход

JsonProperty.EFCore предлагает новое решение для управления JSON-полями. Он позволяет использовать JSON-поля в EF Core без необходимости настройки сложного Fluent API. Благодаря этому открытому проекту NuGet, разработчики могут упростить свой рабочий процесс и сосредоточиться на создании логики приложения, минуя сложные настройки EF Core.

Особенности и преимущества

  1. Простая интеграция: JsonProperty.EFCore предлагает простой процесс интеграции. Разработчики просто добавляют пакет, указывают директиву using и создают модель сущности, как обычно - не требуется дополнительная настройка для JSON-полей.

  2. Поддержка обобщенных типов: Пакет поддерживает обобщенные типы, такие как JsonEnumerable<T> и JsonDictionary<TKey, TValue>, что позволяет разработчикам работать с пользовательскими типами элементов в коллекциях JSON без усилий.

  3. Безупречное управление JSON: С JsonProperty.EFCore управление JSON-полями становится намного проще. Разработчики могут непосредственно добавлять свойства типов JsonEnumerable, JsonList, JsonDictionary или JsonItem в свои модели сущностей и легко управлять JSON-полями.

  4. Строгая сериализация типов: Пакет позволяет разработчикам включить строгую сериализацию типов. При включении типы данных включаются в JSON, обеспечивая повышенную целостность и последовательность преобразования данных.

  5. Полиморфизм: строгая типизация позволяет сохранить полиморфные типы при сериализации и десериализации в JSON.

Примеры использования

Давайте рассмотрим несколько примеров использования JsonProperty.EFCore, чтобы продемонстрировать его полезность и удобство:

  1. Хранение параметров продукта:

    Допустим, у нас есть сущность "Product" с различными параметрами, сохраняемыми в JSON-полях. С JsonProperty.EFCore добавление и управление этими параметрами становится невероятно простым:

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public JsonDictionary Parameters { get; set; } = new();
    }

    Запись JsonDictionary аналогична JsonDictionary<string, object>. При этом полиморфизм позволяет хранить значения любых типов в таком словаре.

    А вот пример управления коллекцией JsonDictionary:

    Product product = new() {Name="Phone",Price=500.95m,Amount=21,Parameters={
        VirtualDictionary = new Dictionary<string,object>() {
            {"Camera",13.5 },{"OS","Android" },{"Screen","1080x900"},{"Storage",32}
        }
    }};
    db.Goods.Add(product);
    db.SaveChanges();

    Это сгенерирует следующие данные для поля в формате JSON, если настройка JsonSettings.StrictTypeSerialization имеет значение true (по умолчанию):

    {
      "Camera": [13.5, "System.Double"],
      "OS": ["Android", "System.String"],
      "Screen": ["1080x900", "System.String"],
      "Storage": [32, "System.Int32"]
    }

    Также можно добавлять и редактировать элементы поля JsonDictionary:

    Product product = db.Goods.FirstOrDefault();
    product.Parameters.Add("Battery capacity", 3000);
    product.Parameters.Edit(dict => {
      dict["Battery capacity"] = 4000;
      dict["Storage"] = 64;
      dict.Add("RAM", 4);
      return dict;
    });

    После этого JSON-поле примет следующий вид:

    {
      "Camera": [13.5, "System.Double"],
      "OS": ["Android", "System.String"],
      "Screen": ["1080x900", "System.String"],
      "Storage": [64, "System.Int32"],
      "Battery capacity": [4000, "System.Int32"],
      "RAM": [4, "System.Int32"]
    }
  2. Управление элементами списка дел:

    Предположим, у нас есть сущность "Note" с коллекцией элементов "TodoItem", сохраняемых в JSON. JsonProperty.EFCore облегчает работу с коллекциями JSON:

    public class Note
    {
        public int Id { get; set; }
        public string Header { get; set; }
        public JsonList<TodoItem> Todos { get; set; } = new();
    }

    При этом в списке JsonList<TodoItem> можно также хранить элементы с типом, наследуемым от TodoItem.

  3. Простое добавление полиморфного поля:

    Допустим, нам надо добавить в нашу сущность поле с абстрактным или просто полиморфным типом, без организации сложной системы сущностей и таблиц для наследуемых типов. Здесь отлично поможет JsonProperty.EFCore:

    using JsonProperty.EFCore;
    
    class MyEntity
    {
        public int Id { get; set; }
        public int Title { get; set; }
        public JsonItem<Base> Content { get; set; } = new();
    }

    Теперь можно использовать полиморфное поле:

    MyEntity myEntity = new();
    myEntity.Content.Serialize(new DerivedType1());
    Base val = myEntity.Content.Deserialize();
    Console.WriteLine(val is DerivedType1); //true

Заключение

JsonProperty.EFCore - это ценный открытый проект, который упрощает управление JSON-полями в Entity Framework Core. Избавляя от необходимости настройки Fluent API, он дает возможность разработчикам проще работать с JSON-полями, обеспечивая простоту и эффективность разработки. Простая интеграция, поддержка обобщенных типов и полиморфизм делают JsonProperty.EFCore полезным инструментом для разработчиков приложений.

Если вы хотите упростить управление JSON-полями в EF Core, попробуйте JsonProperty.EFCore. Посетите репозиторий на GitHub, чтобы узнать больше и оценить проект. Удачной разработки!

Комментарии (15)


  1. granit1986
    06.08.2023 16:02
    +1

    Не очень понял в чём отличие от хранения обычной строки и преобразовании в объект через HasConversation()?


    1. maxchistt Автор
      06.08.2023 16:02
      -1

      Во-первых, это более простая в использовании, более наглядная и удобная альтернатива HasConversation(), вообще не требующая использовать Fluent API. Во вторых, тут доступна строгая сериализация, в том числе позволяющая поддерживать полиморфизм для сложных пользовательских классов, а не только базовых типов. Конечно, можно это реализовать и через Fluent API, но тогда будет еще больше лишнего кода, а тут готовый удобный функционал.


      1. hello_my_name_is_dany
        06.08.2023 16:02
        +1

        Две строчки кода, которые позволяют вообще не думать в большинстве случаев из-за использования проверенных временем сериализаторов, выглядят не так уж и плохо, чем работа с непонятным API, который к тому же будет просачиваться везде, потому что становится частью модели


        1. maxchistt Автор
          06.08.2023 16:02
          -1

          Это будет не две строчки кода, если вам нужно поле типа List<object>, в котором значения типа int, double и decimal, инициализированные литералом "1", будут десериализованы при получении из бд как int , double и decimal, а не как int64 все трое. Или если вам нужно, чтобы поле типа BaseType десериализовалось как ChildType, если туда был записан объект такого типа.


          1. hello_my_name_is_dany
            06.08.2023 16:02

            Всё подводить к object вообще глупо из-за боксинга, который далеко не бесплатный, да и разнародный список уже говорит о том, что лучше уж сделать класс с полями/свойствами, чем поддерживать непонятно что, каждый раз думая, а что же там храниться


      1. hVostt
        06.08.2023 16:02
        +2

        Когда вы добавляете внешнюю зависимость в проект в виде сторонней библиотеки — это уже само по себе рискованно и не очень хорошо:

        1. Зависимость это ещё одна дополнительная точка, которую постоянно, при обновлениях, потребуется проверять на уязвимость.

        2. Большинство разработчиков не имеют никакого опыта работы с большинством сторонних библиотек. Значит это дополнительная когнитивная нагрузка, усложнение дальнейшего развития и сопровождения, особенно когда зависимость просачивается в контракты и модели (вот это совсем плохо).

        3. Частенько, сторонние библиотеки перестают поддерживаться, и придётся переходить на форки.

        Это не всё, конечно, но достаточно, чтоб не тащить в проект абсолютно любую фигню, чтобы сократить пару строк.

        Если вы добавляете себе в проект зависимость, плюсы должны перевешивать все минусы. Это действительно должно быть решение, которое здорово облегчит вам жизнь и вы готовы мириться со всеми рисками.

        Решение, которое вы описали к таковым, как мне кажется, не относится. Его можно изучить и даже кое что взять на вооружение, но задача решается "из коробки" — и вот этому стоило посвятить статью.

        Как сложить 2+2? Берём библиотеку TwoPlusTwo и вуаля! Не надо так :)


        1. maxchistt Автор
          06.08.2023 16:02
          -1

          Как минимум, задача записать в json-поле типа BaseType объект типа ChildType, а затем десериализовать значение именно последнего типа, не решается "из коробки" в EF


          1. hVostt
            06.08.2023 16:02

            Не буду прям утверждать хорошо это или плохо, но я бы таки настоятельно советовал избегать наследования при сериализации/десериализации в JSON. Никаких стандартов на эту тему нет, а наследование в перспективе начнёт приносить всё меньше каких-либо выгод, и всё больше боли и страданий.


  1. Heggi
    06.08.2023 16:02
    +1

    EFCore прекрасно поддерживает json поля без всяких сторонних библиотек и fluent api. Нужно просто правильно описать модельки.

    Проверял на PostgreSQL и MySQL


    1. maxchistt Автор
      06.08.2023 16:02

      Ну, во-первых, нет, без fluent api, на данный момент, в .net 7, невозможно настроить конвертацию в json. Во-вторых, придется постараться, чтобы json-поля поддерживали полиморфизм с точным преобразованием типов.


      1. Heggi
        06.08.2023 16:02

        Мы видимо про разное?
        Я на модельке просто делаю вот так и мои потребности закрывает.

        [Column(TypeName = "jsonb")]
        public Dictionary<string, string> Details { get; set; } = new();


        Или так:
        [Column("additional", TypeName = "json")] public MetaObject? Meta { get; set; } public class MetaObject { [JsonPropertyName("service")] public string Service { get; set; } = string.Empty; [JsonPropertyName("payout")] [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] public decimal Payout { get; set; }
        }

        P.S. как тут код нормально отформатировать???


        1. maxchistt Автор
          06.08.2023 16:02
          -1

          По крайней мере, с mssql такая запись не сработает. Может, вы не указали какие-то детали.

          Даже если в некоторых условиях такая запись сработает, она всё-же не будет поддерживать полиморфизм.

          P.S. для форматирования кода, поместите его в тройные косые кавычки (не знаю, как правильно назвать такие кавычки): " ``` "


          1. Heggi
            06.08.2023 16:02

            С mssql не работал, там не знаю.
            Для полиморфизма (точнее для чтения raw json, а потом что хочешь с ним, то и делай) у драйверов pgsql и mysql есть соответствующие типы, которые можно подставить в модельку.


        1. s207883
          06.08.2023 16:02

          Попробуй вот так
          Попробуй вот так
          record Sample ();


          1. Heggi
            06.08.2023 16:02

            [Column("additional", TypeName = "json")]
            public MetaObject? Meta { get; set; } 
            
            public class MetaObject 
            { 
              [JsonPropertyName("service")] 
              public string Service { get; set; } = string.Empty; 
              
              [JsonPropertyName("payout")] 
              [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] 
              public decimal Payout { get; set; }
            }

            У меня почему-то другой интерфейс, но разобрался.