web-dev-qa-db-ja.com

オブジェクトをJSONにシリアル化中に循環参照が例外を検出しました

this postで述べたように、Entity Framework Proxyのシリアル化中にJsonシリアル化エラーが発生します。

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

しかし、違いは、Ido n'tエンティティに循環参照があり、それは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; }
}

例外をスローするPurchaseOrderControllerのGetCurrentアクション:

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.ProxyCreationEnabledfalseに設定できますが、遅延読み込みが失われます。 EFエンティティのシリアル化をサポートするJson.NETを使用することを強くお勧めします。

ADO.NET Entity Frameworkサポートが誤ってJson.NETに追加されました

。NET用の一般的な高性能JSONフレームワーク

オプション1(推奨)

DbContextでプロキシオブジェクトの作成をオフにしてみてください

DbContext.Configuration.ProxyCreationEnabled = false;

通常、このシナリオは、アプリケーションがPOCOオブジェクト(T4生成またはCode-First)を使用しているためです。この問題は、Entity FrameworkがPOCOオブジェクトに組み込まれていないオブジェクトの変更を追跡する場合に発生します。これを解決するために、EFはPOCOオブジェクトに属性がなく、シリアル化できないプロキシオブジェクトを作成します。

このアプローチを推奨する理由。 Webサイトを使用するということは、おそらくEntity Frameworkオブジェクトの変更追跡(ステートフル)が必要ないことを意味します。変更追跡が無効になっているため、メモリとCPUが解放され、すべてのオブジェクトで同じように一貫して動作します。

オプション2

カスタマイズしてオブジェクトをシリアル化できるシリアライザー(ASP.Net 4に既に含まれている JSON.Net など)を使用します。

このアプローチを推奨しない理由は、最終的にカスタムオブジェクトシリアル化ロジックがプロキシオブジェクトをotherオブジェクトタイプとしてシリアル化する必要があるためです。これは、結果を下流に配信するためにロジックに依存していることを意味します。オブジェクトの変更とはロジックの変更を意味し、ASP.Net MVCプロジェクト(任意のバージョン)では、ビューを変更するだけでなく、ロジックを最初に書いた人以外には知らない変更があります。

オプション3(Entity Framework 5.x +)

。AsNoTracking() を使用すると、特定のクエリでプロキシオブジェクトが無効になります。変更の追跡を使用する必要がある場合、これにより、ソリューション#1に対するNice中間ソリューションが可能になります。

43
Erik Philips

私は、ウェブ全体に散らばっているさまざまな解決策のすべてを試みるために数え切れないほどの時間を費やしました。

  • [JsonIgnore]
  • 内部ゲッター
  • LazyLoadingEnabledおよびProxyCreationEnabledの無効化
  • ReferenceLoopHandlingを「無視」に設定する
  • 必要に応じて明示的な読み込みを慎重に使用する

これらはすべて、最終的に私にとって実りのないものでした。プロパティを無視すると、1つのクエリは役立ちましたが、他の3つのクエリは傷つきました。 whack-a-moleに相当するプログラミングのように感じました。

私の問題のコンテキストは、アプリケーションから出て行くデータは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)
    }
);

そして出来上がり、自己参照ループはありません。

エンティティとオブジェクトが変更される可能性があることを考えると、この状況はすべての場合に適切ではないかもしれないことを理解しています。しかし、他のすべてが失敗した場合、確かにいくつかの検討に値します。

12
pim

他のクラスの参照を持つクラスはすべて、このような属性を追加するだけです

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

今、すべてがスムーズに動作します

2
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]

dBエンティティ参照の属性。これがあなたの問題により関連すると思われる場合は、こちらの投稿を参照してください。

ASP.NET Web APIシリアル化JSONエラー:「自己参照ループ」

1
Freestyle076

私は同じ問題を抱えていました、私がやったことは、表示するために必要な列のみを渡したことです、私の場合は。のみ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);
1
BJ Patel

私は同じ問題を抱えており、Reference ManagerのプロジェクトExtensionsで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ファイルでは、インストールされたバージョンは6.0.5でしたが、OLDER(6.0.0.0)バージョンを参照するように強制されていたことに注意してください。

それが役に立てば幸い!

0
Marco

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

次の3つの方法があります。

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

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

0
NicoJuicy