7

Entity Framework を使用して DB からデータを取得し、シリアル化してクライアントに送信する Web API プロジェクトを C# で作成しています。

私のプロジェクトには、Post と Comment (Post からの外部キー) の 2 つのクラスがあります。

これらは私のクラスです。

投稿クラス:

public partial class Post
{
    public Post()
    {
        this.Attachment = new HashSet<Attachment>();
        this.Comment = new HashSet<Comment>();
    }

    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public System.DateTime Created { get; set; }
    public Nullable<System.DateTime> Modified { get; set; }

    public virtual ICollection<Attachment> Attachment { get; set; }
    public virtual ICollection<Comment> Comment { get; set; }
}

コメントクラス:

public partial class Comment
{
    public int CommentId { get; set; }
    public string Content { get; set; }
    public System.DateTime Posted { get; set; }
    public bool Approved { get; set; }
    public int AnswersTo { get; set; }
    public int PostId { get; set; }

    public virtual Post Post { get; set; }
}

私の問題は、Web API 経由で投稿を取得しようとすると、次のエラーが表示されることです。

Object graph for type 'APIServer.Models.Comment' contains cycles and cannot be serialized if reference tracking is disabled.

そして、Web API 経由でコメントを取得しようとすると、エラーは次のようになります。

Object graph for type 'System.Collections.Generic.HashSet`1[[APIServer.Models.Comment, APIServer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'  contains cycles and cannot be serialized if reference tracking is disabled.

Comment クラスに注釈を付けると、

[DataContract(IsReference = true)]

エラーは消えますが、シリアル化はコメントの ID のみを返し、他のフィールドは無視します。

これを解決する方法について何か提案はありますか?

前もって感謝します、

レスター

4

2 に答える 2

6

ここに2つの解決策があります

解決策 1:

私はこれと同じ問題を抱えていたので、あなたが言及したようにクラスとDataContractメンバーを飾りました。DataMemberただし、ファイルを再生成するたびにやり直す必要があるため、自動生成されたコードを直接編集するのは好きではありません。これを回避するために、MetadataType属性を使用しました。あなたの場合、次のようになります...

まず、自動生成されたエンティティをそのまま保持します。

public partial class Comment
{
    public int CommentId { get; set; }
    public string Content { get; set; }
    public System.DateTime Posted { get; set; }
    public bool Approved { get; set; }
    public int AnswersTo { get; set; }
    public int PostId { get; set; }

    public virtual Post Post { get; set; }
}

次に、別のファイルで別の部分クラスを作成し、次のように装飾します。

[MetadataType(typeof(Metadata))]
[DataContract(IsReference = true)]
public partial class Comment
{
    private class Metadata
    {
        [DataMember]
        public int CommentId { get; set; }
        [DataMember]
        public string Content { get; set; }
        [DataMember]
        public System.DateTime Posted { get; set; }
        [DataMember]
        public bool Approved { get; set; }
        [DataMember]
        public int AnswersTo { get; set; }
        [DataMember]
        public int PostId { get; set; }

        [DataMember]
        public virtual Post Post { get; set; } // you can remove "virtual" if you wish
    }
}

MetadataTypeMetadata基本的に、相棒クラスの属性を同じ名前の属性に追加しますComment(直接ではありませんが、私たちの目的のためには、十分に近いものです... これは別の投稿のトピックです)。もちろん、Commentエンティティが変更された場合は、それに応じてこれを更新する必要があります。

解決策 2:

変更を加えるたびに 2 番目のファイルを編集しなければならないことは、自動生成されたファイルを直接編集する場合よりもわずかに改善されるだけです。幸いなことに、メンテナンスがはるかに簡単な別のアプローチがあります。詳細はこちらで確認できますが、要約すると、OperationContract消費Commentしている を追加の属性で装飾するだけですReferencePreservingDataContractFormat。そのページで提供されているコードには、無限再帰を引き起こすわずかなエラーがあることに注意してください。この投稿で述べたように、修正は非常に簡単です。まったく再帰するのではなく、新しいDataContractSerializer

このアプローチの利点は、どれだけ変更Commentしても、何も更新する必要がないことです。

コードの例として、Comment次のように使用しているとします。

[OperationContract]
Comment FindComment(string criteria);

あなたがする必要があるのは、追加するだけです

[OperationContract]
[ReferencePreservingDataContractFormat]
Comment FindComment(string criteria);

そして、他のどこかで、次ReferencePreservingDataContractFormatのように定義する必要があります。

//From http://blogs.msdn.com/b/sowmy/archive/2006/03/26/561188.aspx and https://stackoverflow.com/questions/4266008/endless-loop-in-a-code-sample-on-serialization
public class ReferencePreservingDataContractFormatAttribute : Attribute, IOperationBehavior
{
    public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
    {
    }

    public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
    {
        IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(description);
        innerBehavior.ApplyClientBehavior(description, proxy);
    }

    public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
    {
        IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(description);
        innerBehavior.ApplyDispatchBehavior(description, dispatch);
    }

    public void Validate(OperationDescription description)
    {
    }

}
class ReferencePreservingDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
{
    public ReferencePreservingDataContractSerializerOperationBehavior(OperationDescription operationDescription) : base(operationDescription) { }
    public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
    {
        return new DataContractSerializer(type, name, ns, knownTypes,
            0x7FFF, //maxItemsInObjectGraph
            false,  //ignoreExtensionDataObject
            true,   //preserveObjectReferences
            null    //dataContractSurrogate
            );
    }

    public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
    {
        return new DataContractSerializer(type, name, ns, knownTypes,
            0x7FFF, //maxItemsInObjectGraph
            false,  //ignoreExtensionDataObject
            true,   //preserveObjectReferences
            null    //dataContractSurrogate
            );
    }
}

以上です!

どちらの方法でも問題なく機能します。自分に合った方法を選択してください。

于 2014-03-04T23:05:44.483 に答える