Подтвердить что ты не робот

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

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

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

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

Мои объекты:

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;
    }
}
4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

DbContext.Configuration.ProxyCreationEnabled = false;

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

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

Вариант 2

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

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

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

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

Ответ 3

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

  • [JsonIgnore]
  • Внутренние Getters
  • Отключение LazyLoadingEnabled и ProxyCreationEnabled
  • Настройка ReferenceLoopHandling для "игнорировать"
  • Осторожно, используя явную загрузку, где необходимо

Все это в конечном итоге оказалось бесплодным для меня. Игнорирование свойства помогло одному запросу, но ушибло еще 3 человека. Похоже, что программирование эквивалентно whack-a-mole.

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

Решение:

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

Пример кода:

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

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)
    }
);

И вуаля, не привязанные к себе петли.

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

Ответ 4

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

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

Теперь все работает плавно

Ответ 5

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

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

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

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

Ответ 6

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

[IgnoreDataMember]

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

ASP.NET Web API Сериализованная ошибка JSON: "Self Referencing loop"

Ответ 7

У меня была такая же проблема, и я решил ее, отменив проверку 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.

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

Ответ 8

У меня была одна и та же проблема, что я сделал, она прошла только нужный столбец для просмотра. В моем случае. только 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);

Ответ 9

В вашем классе 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; }
}