web-dev-qa-db-ja.com

EF 4.1-コードファースト-JSON循環参照シリアル化エラー

私の知る限り、循環参照はありませんが、循環参照のシリアル化エラーが発生しています。データベースからOrderのセットを取得し、JSONとしてクライアントに送信しています。すべてのコードを以下に示します。

これはエラーです:

エラー

タイプ 'System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812'のオブジェクトのシリアル化中に循環参照が検出されました。説明:現在のWebリクエストの実行中に未処理の例外が発生しました。エラーの詳細とコードのどこで発生したかについては、スタックトレースを確認してください。

例外の詳細:System.InvalidOperationException:タイプ 'System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812'のオブジェクトのシリアル化中に循環参照が検出されました。

ソースエラー:

現在のWeb要求の実行中に、未処理の例外が生成されました。例外の発生元と場所に関する情報は、以下の例外スタックトレースを使用して特定できます。

私のクラスは次のとおりです。

注文

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

}

注:アドレスクラスIS編集中の新しい追加

アドレス

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

ありがとう

47
Guido Anselmi

すべてのナビゲーションプロパティから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);
}

前の投稿 を参照すると、オブジェクトグラフを遅延的にロードする仮想プロパティが導入されているため、アプリケーションは遅延ロードに依存していないように見え、シリアル化の問題が発生する可能性があります。

編集

ナビゲーションプロパティからvirtualキーワードを削除する必要はありません(これにより、モデルの遅延読み込みが完全に不可能になります)。シリアル化など、プロキシが邪魔をする特定の状況では、プロキシの作成を無効にするだけで十分です(遅延読み込みも無効になります)。

ppEFContext.Configuration.ProxyCreationEnabled = false;

これにより、特定のコンテキストインスタンスppEFContextのみのプロキシ作成が無効になります。

(私が見たところでは、@ WillCはすでにここでそれについて言及しました。この編集に対する賛成は彼の答えにお願いします。)

48
Slauma

特定のコンテキストからシリアル化する必要があることがわかっている場合、以下のようにその特定のクエリのプロキシ作成を無効にできます。これは私にとっては有効で、モデルクラスを修正するよりも優れていました。

using (var context = new MeContext())
{
    context.Configuration.ProxyCreationEnabled = false;
    return context.cars.Where(w => w.Brand == "Ferrari")
}

このアプローチは、コンテキストのこの特定のインスタンスのプロキシオブジェクトタイプを取り去るため、返されるオブジェクトは実際のクラスであるため、シリアル化は問題になりません。

すなわち:

{Models.car} 

の代わりに

{System.Data.Entity.DynamicProxies.car_231710A36F27E54BC6CE99BB50E0FE3B6BD4462EC‌​A19695CD1BABB79605296EB} 
41
WillC

問題は、エンティティフレームワークで生成されたプロキシオブジェクトを実際にシリアル化していることです。残念ながら、これはJSONシリアライザーで使用するといくつかの問題があります。 JSONの互換性のために、エンティティを特別な単純なPOCOクラスにマッピングすることを検討してください。

9
Christoph

Entity Frameworkオブジェクトに追加する属性があります

[ScriptIgnore]

これにより、コードは循環参照を実行しません。

8
bdparrish

彼らは最新バージョンでこれを修正したと思います。

JSONのシリアライズとデシリアライズ->シリアライゼーションとオブジェクト参照の保持」セクションの下の help docs を確認してください。

JSON.Net Serializerを初期化するときにこの設定を設定します。

PreserveReferencesHandling = PreserveReferencesHandling.Objects;

したがって、例は次のようになります。

var serializerSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects };

string json = JsonConvert.SerializeObject(people, Formatting.Indented, serializerSettings);

これが私のコードファーストソリューションと、ナビゲーションプロパティの循環参照で機能することを確認しました。結果のJSONを見ると、どこにでも「$ id」および「$ ref」プロパティがあります。

7
John Bubriski

別の解決策は、LINQクエリの結果として匿名型を使用にすることです。

私のプロジェクトでは、遅延読み込みを広範囲に使用していますが、それを無効にすることは正しいことではありません。

6
abdelkarim

次の例のように、オブジェクトの一部の値のみが必要な場合の代替ソリューションは、匿名クラスを構築して返すことです。

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

循環参照が発生するのは、オブジェクトに積極的な読み込みを使用するためです。

いくつかの方法があります:

  • クエリ(linqまたはラムダ)を読み込むときに、積極的な読み込みをオフにしますDbContext.Configuration.ProxyCreationEnabled = false;
  • Domainmodelから仮想キーワードを削除します
  • オブジェクトを切り離します(=積極的な読み込み機能なし、プロキシなし)
    • Repository.Detach(entityObject)
    • DbContext.Entry(entityObject).EntityState = EntityState.Detached
  • プロパティを複製します
    • AutoMapperのようなものを使用してオブジェクトを複製できますが、ICloneableインターフェイスを使用しないでください。オブジェクト内のProxyPropertiesも複製するため、動作しません。
  • APIを構築している場合は、異なる構成(プロキシを返さない)でseparteプロジェクトを使用してみてください

PS。プロキシは、Entity Frameworkから読み込むときにEFによって作成されるオブジェクトです。つまり、元の値と更新された値を保持して、後で更新できることを意味します。それは他のものを処理します;-)

2
NicoJuicy

virtualキーワードを削除できます:

public virtual Patient Patient { get; set; }-> public Patient Patient { get; set; }

仮想キーワードを削除すると、遅延読み込みがオフになることに注意してください。

0
Rabolf

ここで説明した方法を使用して、この問題を解決できました。

http://mytechworld.officeacuity.com/index.php/2010/02/serializing-entity-framework-objects-into-json-using-asp-net-mvc/

0
steveareeno

プロキシEF/Linq2SQLクラスを使用している場合、私のソリューションは、子エンティティの親参照を単に削除することでした。

そのため、私のモデルでは、リレーションシップを選択し、親参照をパブリックではなく内部に変更しました。

すべての人にとって理想的なソリューションではないかもしれませんが、私にとってはうまくいきました。

0
Anthony Main