Не сериализуйте ссылки на класс класса Entity Framework в JSON (библиотека ServiceStack.Text)

Как и большинство людей, я также столкнулся с проблемой круговой контрольной ошибки при сериализации (ужасных) объектов EF для JSON. Выполнение db.Detach(efObject) помогает, но я все равно получаю мусор, например, "EntityKey".

Итак, мне было интересно, есть ли опция (через JsConfig?), чтобы сказать, что сериализатор игнорирует свойства либо через имя (EntityKey), либо через тип (EntityReference <T> или EntityCollection <T> )?

Или я буду вынужден перевернуть EF alltogether и переключиться на что-то лучше (я не хочу вручную определять классы ORM - я хочу, чтобы они автоматически генерировались из БД)?

Ответ 1

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: В настоящее время я делаю прототипирование, и для меня удобство имеет большее значение, чем надежность решения. Итак, то, что я сделал, это не очень хороший способ - я копирую его здесь на всякий случай, когда кто-то еще находится в том же положении и хочет получить легкий (не лучший) выход. Вы были предупреждены.

  • Предполагаю, что вы уже используете ServiceStack.Text в своем проекте MVC. Если это не так, следуйте инструкциям из этого QA: Преобразование сериализации ASP.NET MVC Json DateTime в UTC

  • Откажитесь от библиотеки ServiceStack.Text от GitHub и (конечно) ссылайтесь на нее в своем проекте MVC вместо DLL.

  • Добавьте это в класс ServiceStack.Text/JsConfig:

    // Added properties for global excluding
    public static Type[] GlobalExcludedBaseTypes;
    public static string[] GlobalExcludedProperties;
    
  • Измените статический тип TypeConfig() в классе ServiceStack.Text/TypeConfig:

    static TypeConfig()
    {
        config = new TypeConfig(typeof(T));
    
        var excludedProperties = JsConfig<T>.ExcludePropertyNames ?? new string[0];
        var excludedGlobalBaseTypes = JsConfig.GlobalExcludedBaseTypes ?? new Type[0];
        var excludedGlobalProperties = JsConfig.GlobalExcludedProperties ?? new string[0];
    
        IEnumerable<PropertyInfo> properties = null;
    
        if (excludedProperties.Any() || excludedGlobalBaseTypes.Any() || excludedGlobalProperties.Any())
        {
            properties = config.Type.GetSerializableProperties().
                Where(x => !excludedProperties.Contains(x.Name) && !excludedGlobalProperties.Contains(x.Name) && !excludedGlobalBaseTypes.Contains(x.PropertyType.BaseType));
        }
        else
        {
            properties = config.Type.GetSerializableProperties();
        }
    
        Properties = properties.Where(x => x.GetIndexParameters().Length == 0).ToArray();
        Fields = config.Type.GetSerializableFields().ToArray();
    }
    
  • В вашем Global.asax просто добавьте эти две строки:

    JsConfig.GlobalExcludedProperties = new[] { "EntityKey", "EntityState" };
    JsConfig.GlobalExcludedBaseTypes = new[] { typeof(RelatedEnd), typeof(EntityReference) };
    

Что это - EntityState и EntityKey больше не будут сериализованы, и вам больше не придется беспокоиться о круговых зависимостях.

Но, опять же - ЭТО НЕ ЛУЧШАЯ ПРАКТИКА - и как только вы стабилизируете свое решение, обязательно следуйте тому, что рекомендуют мифы и мигрируйте в сторону DTO.

Ответ 2

Вам не следует пытаться повторно использовать типы Entity Framework как DTO, так как они плохо разработаны для замены DTO. Вы должны вместо этого сопоставить их с типами DTO специального назначения, используя встроенные в ServiceStack преобразователи TranslateTo/PopulateFrom (или AutoMapper) и возвратите их.

С учетом этого используйте IgnoreDataMember или укажите DataMembers для свойств, которые вы хотите сериализовать.

Ответ 3

Вы можете использовать ScriptIgnore атрибут вашей модели:

public class Item
{
    [ScriptIgnore]
    public Item ParentItem { get; set; }
}