Циркулярная ссылка обнаружила исключение при сериализации объекта в 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;
}
}
Ответы
Ответ 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; }
}