2011-07-31 00:00:00

Problem zwrotnych referencji w WCF

Podczas wielu batalii z WCF'em natknąłem się na konieczność umieszczenia w obiekcie będącej elementem jakiejś kolekcji referencji do obiektu posiadającego tą kolekcję. (Przyznam że konieczność ta była związana z faktem iż w przesyłanej klasie była zaszyta część logiki biznesowej a tak się nie powinno robić ale jak mus to mus). Tak więc posiadałem DataContract'y podobne do tych:

    [DataContract]
    public class Parent
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public List<Children> Childrens { get; set; }
    }

    [DataContract]
    public class Children
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public Parent Parent { get; set; }
    }

Próba pobrania obiektu Parent zakończyła się błędem:

WCFBlad1.png

Znalezienie przyczyny błędu było na tyle trudniejsze że pomiędzy buildem a napisaniem DataContractu robiłem całą masę innych rzeczy.

Błąd ten jest spowodowany wyjątkiem StackOverflow po stronie serwera, wynika on z faktu że przy próbie serializacji klasy Children mechanizm docierając do pola Parent nie orientuje się że już ten obiekt serializował albo właśnie go serializuje.

Rozwiązaniem tego problemu jakie znalazłem googlając było ustawienie właściwości IsReference w klasie rodzica i rzeczywiście w tak prostym przypadku rozwiązanie to działa jak należy.

    [DataContract(IsReference=true)]
    public class Children
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public Parent Parent { get; set; }
    }

Moja sytuacja była jednak trochę bardziej złożona. Po pierwsze zarówno Parent jak i Children dziedziczyło po jednym typie bazowym:

    [KnownType(typeof(ChildrenBP))]
    [KnownType(typeof(ParentBP))]
    [DataContract]
    public class SuperBP
    {
        [DataMember]
        public string Name { get; set; }
    }
    
    [DataContract(IsReference=true)]
    public class ParentBP : SuperBP
    {
        [DataMember]
        public List<ChildrenBP> Childrens { get; set; }
    }
	
	[DataContract]
    public class ChildrenBP : SuperBP
    {
        [DataMember]
        public ParentBP Parent { get; set; }
    }

Ustawienie znacznika IsReference na klasie ParentBP i pobranie referencji kończy się błędem:

pictures/WCFBlad2.png

W większości przypadków należy postąpić zgodnie z komunikatem błędu i ustawić IsReference na klasie bazowej. Jeżeli jednak z jakiś powodów nie chcemy tego robić możemy usunąć znacznik DataMember z pola Parent w klasie ChildrenBP i posiłkować się znacznikiem OnDeSerialized.

    [KnownType(typeof(ChildrenBP))]
    [KnownType(typeof(ParentBP))]
    [DataContract]
    public class SuperBP
    {
        [DataMember]
        public string Name { get; set; }
    }
	
    [DataContract]
    public class ParentBP : SuperBP
    {
        [OnDeserialized]
        public void OnDeserialized(StreamingContext context)
        {
            foreach (ChildrenBP child in Childrens)
            {
                child.Parent = this;
            }
        }

        [DataMember]
        public List<ChildrenBP> Childrens { get; set; }
    }
	[DataContract]
    public class ChildrenBP : SuperBP
    {

        public ParentBP Parent { get; set; }
    }

Tagi

C Sharp Programowanie

Komentarze:

2014-10-18 10:48:22

Michal

Drugi dzień walczyłem z tym problemem, dzięki za naprowadzenia na temat. Pozdr.