it-swarm.com.ru

Циклическая ссылка обнаружила исключение при сериализации объекта в JSON

Как упоминалось в this post, я получаю ошибку сериализации Json при сериализации прокси Entity Framework:

Обнаружена циклическая ссылка при сериализации объекта типа 'System.Data.Entity.DynamicProxies.PurchaseOrder_446B939192F161CDBC740067F174F7A6059B0F9C0EEE68CD3EBBD63CF9AF5BD0.

Но разница в том, что у меня / нет круговой ссылки в моих сущностях, и она only встречается в нашей производственной среде. Локально все отлично работает ...

Мои сущности:

public interface IEntity
{
    Guid UniqueId { get; }
    int Id { get; }
} 

public class Entity : IEntity
{
    public int Id { get; set; }
    public Guid UniqueId { get; set; }
}

public class PurchaseOrder : Entity
{
    public string Username { get; set; }
    public string Company { get; set; }

    public string SupplierId { get; set; }
    public string SupplierName { get; set; }

    public virtual ICollection<PurchaseOrderLine> Lines { get; set; }
}

public class PurchaseOrderLine : Entity
{
    public string Code { get; set; }
    public string Name { get; set; }
    public decimal Quantity { get; set; }
}

Действие GetCurrent на моем PurchaseOrderController вызывает исключение:

public class PurchaseOrderController : Controller
{
    private readonly IUnitOfWork _unitOfWork;

    public PurchaseOrderController(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public JsonResult GetCurrent()
    {
        return Json(EnsurePurchaseOrder(), JsonRequestBehavior.AllowGet);
    }

    private PurchaseOrder EnsurePurchaseOrder()
    {
        var company = RouteData.GetRequiredString("company");
        var repository = _unitOfWork.GetRepository<PurchaseOrder>();

        var purchaseOrder = repository
                .Include(p => p.Lines)
                .FirstOrDefault
                (
                    p => p.Company == company && 
                         p.Username == User.Identity.Name
                );

        if (purchaseOrder == null)
        {
            purchaseOrder = repository.Create();
            purchaseOrder.UniqueId = Guid.NewGuid();
            purchaseOrder.Company = company;
            purchaseOrder.Username = User.Identity.Name;
            _unitOfWork.SaveChanges();
        }

        return purchaseOrder;
    }
}
34
Mathijs

Ваши объекты POCO идеально сериализуемы. Ваша проблема в том, что динамические прокси, которые среда выполнения EF создает для вас, обычно не являются. Вы можете установить context.Configuration.ProxyCreationEnabled в false, но тогда вы потеряете ленивую загрузку. Я настоятельно рекомендую вам использовать Json.NET, который поддерживает сериализацию для объектов EF:

В Json.NET случайно добавлена ​​поддержка ADO.NET Entity Framework

Популярная высокопроизводительная среда JSON для .NET

30
Radu Popovici - Oncica

Вариант 1 (рекомендуется)

Попробуйте отключить создание объекта Proxy на вашем DbContext .

DbContext.Configuration.ProxyCreationEnabled = false;

Обычно этот сценарий объясняется тем, что приложение использует объекты POCO (сгенерированные T4 или Code-First). Проблема возникает, когда Entity Framework хочет отслеживать изменения в вашем объекте, который не встроен в объекты POCO. Чтобы решить эту проблему, EF создает прокси-объекты, которые не имеют атрибутов в объектах POCO и не являются сериализуемыми.

Причины, по которым я рекомендую этот подход; использование веб-сайта означает, что вам, вероятно, не нужно отслеживать изменения (с состоянием) для объектов Entity Framework, он освобождает память и процессор, потому что отслеживание изменений отключено и он будет работать одинаково на всех ваших объектах.

Вариант 2 

Используйте сериализатор (например, JSON.Net , который уже включен в ASP.Net 4), который позволяет настройке сериализовать объект (ы).

Причины, по которым я не рекомендую этот подход, заключаются в том, что в конечном итоге для последовательных прокси-объектов в виде других типов объектов потребуется пользовательская логика сериализации. Это означает, что у вас есть зависимость от логики для доставки результата вниз по течению. Смена объекта означает изменение логики, и в проекте ASP.Net MVC (любой версии) вместо изменения только View, вы можете изменить кое-что еще, что не всегда известно за пределами того, кто первым написал логику.

Вариант 3 (Entity Framework 5.x +)

Используйте .AsNoTracking () , чтобы отключить прокси-объекты для конкретного запроса. Если вам нужно использовать отслеживание изменений, это позволяет промежуточное решение Nice к решению № 1.

42
Erik Philips

Я потратил бесчисленные часы, пытаясь найти различные решения, найденные в Интернете, в том числе:

  • [JsonIgnore]
  • Внутренние добытчики
  • Отключение LazyLoadingEnabled и ProxyCreationEnabled
  • Установка ReferenceLoopHandling в «игнорировать»
  • Тщательно используя явную загрузку, где это необходимо

Все это в конечном итоге оказалось для меня бесплодным. Игнорирование свойства помогло одному запросу, но повредило 3 другим. Это было похоже на программирование, эквивалентное удару моля. 

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

Решение:

Верните данные (свойства), которые вам нужны, как анонимные объекты.

Пример кода:

В этом случае мне понадобились 3 последних билета, основанные на «дате запланированного». Но также необходимо несколько свойств, хранящихся в связанных объектах.

var tickets =
     context.TicketDetails
    .Where(t => t.DateScheduled >= DateTime.Now)
    .OrderBy(t => t.DateScheduled)
    .Take(3)
    .Include(t => t.Ticket)
    .Include(t => t.Ticket.Feature)
    .Include(t => t.Ticket.Feature.Property)
    .AsEnumerable()
    .Select(
        t =>
        new {
            ID = t.Ticket.ID,
            Address = t.Ticket.Feature.Property.Address,
            Subject = t.Ticket.Subject,
            DateScheduled = String.Format("{0:MMMM dd, yyyy}", t.DateScheduled)
    }
);

И вуаля, никаких ссылок на себя. 

Я понимаю, что эта ситуация не может быть адекватной во всех случаях, учитывая, что объекты и объекты могут измениться. Но, безусловно, стоит подумать, если ничего не получится.

11
pimbrouwers

Какие бы классы не имели ссылки на другой класс, просто добавьте атрибут, подобный этому

[Newtonsoft.Json.JsonIgnoreAttribute]
public virtual ICollection<PurchaseOrderLine> Lines { get; set; }

Теперь все работает гладко

1
Ali Adravi

В своем классе DbContext добавьте следующую строку кода:

this.Configuration.ProxyCreationEnabled = false;

Например:

public partial class EmpDBEntities : DbContext
{
    public EmpDBEntities()
        : base("name=EmpDBEntities")
    {
        this.Configuration.ProxyCreationEnabled = false;
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        throw new UnintentionalCodeFirstException();
    }

    public virtual DbSet<Department> Departments { get; set; }
    public virtual DbSet<Employee> Employees { get; set; }
}
1
Abdi

У меня была такая же ошибка, однако я видел ее как на рабочем сервере, так и локально. Изменение конфигурации DbContext не решило мою проблему. Другое решение было представлено мне с

[IgnoreDataMember]

атрибут на ссылки на сущность БД. Посмотрите пост здесь, если это звучит более актуально для вашей проблемы.

Сериализированная ошибка JSON веб-API ASP.NET: «Самостоятельная ссылка»

1
Freestyle076

У меня была та же проблема, и я решил ее, отменив проверку Json.NET в Расширениях проекта в Менеджере ссылок.

(см. изображение http://i.stack.imgur.com/RqbXZ.png )

Мне также пришлось изменить файл project.csproj, чтобы отобразить правильный путь для новой версии:

<Reference Include="Newtonsoft.Json">
  <HintPath>..\packages\Newtonsoft.Json.6.0.5\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>

и все равно пришлось настраивать web.config

  <dependentAssembly>
    <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
  </dependentAssembly>

Обратите внимание, что в файле web.config я был вынужден ссылаться на версию OLDER (6.0.0.0), хотя установленная версия была 6.0.5.

Надеюсь, поможет!

0
Marco

Циклическая ссылка происходит потому, что вы активно загружаете объект.

У вас есть 3 метода:

  • Отключите нетерпеливую загрузку при загрузке вашего запроса (linq или lambda) DbContext.Configuration.ProxyCreationEnabled = false;
  • Отсоедините объекты (= нет активной загрузки и отсутствие прокси)
    • Repository.Detach (EntityObject)
    • DbContext.Entry (entityObject) .EntityState = EntityState.Detached
  • Клонировать свойства
    • Вы можете использовать что-то вроде AutoMapper для клонирования объекта, не используйте интерфейс ICloneable, потому что он также клонирует свойства ProxyProperties в объекте, так что это не будет работать.
  • В случае, если вы создаете API, попробуйте использовать отдельный проект с другой конфигурацией (которая не возвращает прокси)

PS. Прокси - это объект, который создается EF при загрузке из Entity Framework. Вкратце: это означает, что он содержит исходные значения и обновленные значения, поэтому они могут быть обновлены позже. Это обрабатывает другие вещи ;-)

0
NicoJuicy

У меня была та же проблема, что я сделал, передал только необходимый столбец для просмотра, в моем случае. только 2.

 List<SubCategory> lstSubCategory = GetSubCateroy() // list from repo

 var subCategoryToReturn = lstSubCategory.Select(S => new { Id  = S.Id, Name = S.Name }); 

return this.Json(subCategoryToReturn , JsonRequestBehavior.AllowGet);
0
BJ Patel