EF 4.1 - Code First - Ошибка серийной ссылки JSON
Я получаю сообщение об ошибке циклической ссылки, хотя, насколько мне известно, у меня нет никаких круговых ссылок. Я беру набор заказов из базы данных и отправляю их клиенту как JSON. Весь код показан ниже.
Это ошибка:
Ошибка
Обнаружена круговая ссылка при сериализации объекта типа 'System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812. Описание: Необработанное исключение произошли во время выполнения текущий веб-запрос. Пожалуйста, просмотрите трассировки стека для получения дополнительной информации о ошибка и где она возникла в код.
Сведения об исключении: System.InvalidOperationException: A круговая ссылка была обнаружена во время сериализация объекта типа 'System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812'.
Ошибка источника:
Создано необработанное исключение во время выполнения текущего веб-запрос. Информация о происхождение и местоположение исключения могут быть идентифицированы с использованием исключения трассировка стека ниже.
Мои классы следующие:
Order
public class Order
{
[Key]
public int OrderId { get; set; }
public int PatientId { get; set; }
public virtual Patient Patient { get; set; }
public int CertificationPeriodId { get; set; }
public virtual CertificationPeriod CertificationPeriod { get; set; }
public int AgencyId { get; set; }
public virtual Agency Agency { get; set; }
public int PrimaryDiagnosisId { get; set; }
public virtual Diagnosis PrimaryDiagnosis { get; set; }
public int ApprovalStatusId { get; set; }
public virtual OrderApprovalStatus ApprovalStatus { get; set; }
public int ApproverId { get; set; }
public virtual User Approver { get; set; }
public int SubmitterId { get; set; }
public virtual User Submitter { get; set; }
public DateTime ApprovalDate { get; set; }
public DateTime SubmittedDate { get; set; }
public Boolean IsDeprecated { get; set; }
}
Пациент
public class Patient
{
[Key]
public int PatientId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleInitial { get; set; }
public bool IsMale;
public DateTime DateOfBirth { get; set; }
public int PatientAddressId { get; set; }
public Address PatientAddress { get; set; }
public bool IsDeprecated { get; set; }
}
Период сертификации
public class CertificationPeriod
{
[Key]
public int CertificationPeriodId { get; set; }
public DateTime startDate { get; set; }
public DateTime endDate { get; set; }
public bool isDeprecated { get; set; }
}
Агентство
public class Agency
{
[Key]
public int AgencyId { get; set; }
public string Name { get; set; }
public int PatientAddressId { get; set; }
public virtual Address Address { get; set; }
}
Диагностика
public class Diagnosis
{
[Key]
public int DiagnosisId { get; set; }
public string Icd9Code { get; set; }
public string Description { get; set; }
public DateTime DateOfDiagnosis { get; set; }
public string Onset { get; set; }
public string Details { get; set; }
}
OrderApprovalStatus
public class OrderApprovalStatus
{
[Key]
public int OrderApprovalStatusId { get; set; }
public string Status { get; set; }
}
Пользователь
public class User
{
[Key]
public int UserId { get; set; }
public string Login { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string NPI { get; set; }
public string Email { get; set; }
}
ПРИМЕЧАНИЕ: ADDRESS CLASS - НОВЫЙ ДОПОЛНЕНИЕ ВО ВРЕМЯ РЕДАКТИРОВАНИЯ
Адрес
public class Address
{
[Key]
public int AddressId { get; set; }
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public string Phone { get; set; }
public string Title { get; set; }
public string Label { get; set; }
}
Код, который выполняет сериализацию, находится здесь:
Выдержка из OrderController
public ActionResult GetAll()
{
return Json(ppEFContext.Orders, JsonRequestBehavior.AllowGet);
}
Спасибо
Ответы
Ответ 1
Вы можете попытаться удалить ключевое слово virtual
из всех свойств навигации, чтобы отключить ленивую загрузку и создание прокси-сервера, а затем использовать загрузку с загрузкой вместо этого, чтобы явно загрузить требуемый граф объекта:
public ActionResult GetAll()
{
return Json(ppEFContext.Orders
.Include(o => o.Patient)
.Include(o => o.Patient.PatientAddress)
.Include(o => o.CertificationPeriod)
.Include(o => o.Agency)
.Include(o => o.Agency.Address)
.Include(o => o.PrimaryDiagnosis)
.Include(o => o.ApprovalStatus)
.Include(o => o.Approver)
.Include(o => o.Submitter),
JsonRequestBehavior.AllowGet);
}
Ссылаясь на ваш предыдущий пост, похоже, что ваше приложение не полагается на ленивую загрузку, так как вы представили там виртуальные свойства для загрузки графического объекта лениво, возможно вызывая проблемы с сериализацией.
Edit
Не нужно удалять ключевое слово virtual
из свойств навигации (что сделало бы ленивую загрузку полностью невозможной для модели). Этого достаточно, чтобы отключить создание прокси-сервера (что также отключает ленивую загрузку) для конкретных ситуаций, когда прокси-серверы беспокоят, например сериализация:
ppEFContext.Configuration.ProxyCreationEnabled = false;
Это отключает создание прокси только для конкретного экземпляра контекста ppEFContext
.
(Я только что видел, @WillC уже упомянул об этом здесь. Upvote для этого отредактируйте, пожалуйста, на его ответ.)
Ответ 2
Когда вы знаете, что вам нужно сериализоваться из определенного контекста, вы можете отключить создание прокси для этого конкретного запроса, как показано ниже. Это сработало для меня и было лучше, чем пересмотр моих моделей.
using (var context = new MeContext())
{
context.Configuration.ProxyCreationEnabled = false;
return context.cars.Where(w => w.Brand == "Ferrari")
}
Этот подход забирает тип объекта-прокси для этого конкретного экземпляра контекста, поэтому возвращаемые объекты являются фактическим классом, поэтому сериализация не является проблемой.
т
{Models.car}
вместо
{System.Data.Entity.DynamicProxies.car_231710A36F27E54BC6CE99BB50E0FE3B6BD4462ECA19695CD1BABB79605296EB}
Ответ 3
Проблема в том, что вы фактически сериализуете объект прокси-объекта, созданный сущностью. К сожалению, это связано с некоторыми проблемами при использовании с сериализатором JSON. Вы можете рассмотреть возможность привязки своих объектов к специальным классам класса POCO ради совместимости с JSON.
Ответ 4
Существует атрибут для добавления объектов Entity Framework
[ScriptIgnore]
Это делает код не выполняющим Циркулярные ссылки.
Ответ 5
Я думаю, что они исправили это в последней версии.
Ознакомьтесь с справочными документами в разделе" Сериализация и десериализация JSON → Сериализация и сохранение ссылок на объекты.
Установите этот параметр при инициализации сериализатора JSON.Net:
PreserveReferencesHandling = PreserveReferencesHandling.Objects;
Итак, пример будет таким:
var serializerSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects };
string json = JsonConvert.SerializeObject(people, Formatting.Indented, serializerSettings);
Я проверил, что это работает с моим первым решением кода и круговой ссылкой в свойствах навигации. Если вы посмотрите на полученный JSON, он должен иметь свойства "$ id" и "$ ref".
Ответ 6
Альтернативным решением было бы использовать анонимные типы в результате запроса LINQ.
В моем проекте я активно использую ленивую загрузку, и отключить ее было неправильным.
Ответ 7
Альтернативное решение, если нужны только некоторые значения из объектов, - это создать анонимный класс и вернуть его, как показано ниже:
public JsonResult AjaxFindByName(string term)
{
var customers = context.Customers
.Where(c => c.Name.ToUpper().Contains(term.ToUpper())).Take(10)
.AsEnumerable()
.Select(c => new {
value = c.Name,
SSN = String.Format(@"{0:000\-00\-0000}", c.SSN),
CustomerID = c.CustomerID });
return Json(customers, JsonRequestBehavior.AllowGet);
}
Ответ 8
Круговая ссылка происходит, потому что вы используете активную загрузку объекта.
У вас есть несколько методов:
- Отключить загрузку при загрузке запроса (linq или lambda) DbContext.Configuration.ProxyCreationEnabled = false;
- Удалить ключевое слово virtual из Domainmodel
- Отсоедините объекты (= нет загружаемых функций и прокси)
- Repository.Detach(EntityObject)
- DbContext.Entry(entityObject).EntityState = EntityState.Detached
- Клонировать свойства
- Вы можете использовать что-то вроде AutoMapper для клонирования объекта, не используйте интерфейс ICloneable, потому что он также клонирует ProxyProperties в объекте, так что это не сработает.
- Если вы создаете API, попробуйте использовать проект separte с другой конфигурацией (которая не возвращает прокси)
PS. Proxies - это объект, созданный EF при его загрузке из Entity Framework. Короче: это означает, что он содержит исходные значения и обновленные значения, чтобы их можно было обновить позже. Он обрабатывает другие вещи: -)
Ответ 9
Для тех, кто использует классы proxy EF/Linq2SQL, моим решением было просто удалить родительскую ссылку для моих дочерних объектов.
Итак, в моей модели я выбрал связь и изменил ссылку "Родитель" как "Внутренний", а не "Публичный".
Не может быть идеальным решением для всех, но работал у меня.
Ответ 10
Вы можете удалить ключевое слово virtual
:
public virtual Patient Patient { get; set; }
→ public Patient Patient { get; set; }
Имейте в виду, что при удалении ключевого слова virtual, ленивая загрузка будет отключена.
Ответ 11
Я смог решить эту проблему, используя описанный здесь метод:
http://mytechworld.officeacuity.com/index.php/2010/02/serializing-entity-framework-objects-into-json-using-asp-net-mvc/