WCF CirWczoraj mój najlepszy przyjaciel przedstawił mi problem związany referencją cykliczną i WCF.

Mój kolega wie ,że pisałem wcześniej aplikacje w WCF i spodziewał się ,że znam już odpowiedź na to pytanie. Oczywiście tak nie było ,ale ponieważ mam większe doświadczenie w tej technologii znalezienie odpowiedzi nie trwało długo.

Na czym dokładnie polega problem no cóż przyjrzyjmy się samej usłudze WCF. Oto dwie klasy, które reprezentują obiekty, które będziemy przesyłać.

Klasa “Person” zawiera w sobie listę elementów typu “Child”. Typ Child przechowuje referencje do swojego ojca, czyli do obiektu, w którym znajduje się na liście. Mamy właśnie tutaj referencje cykliczną.

Obie klasy posiadają dodatkowo pole “Name”.

using System.Collections.Generic;
using System.Runtime.Serialization;

namespace Circular_ReferenceWCF
{
    [DataContract]
    public class Person
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public IList<Child> MyChilds { get; set; }
    }

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

        [DataMember]
        public Person Father { get; set; }
    }
}

Usługa sieciowa napisana w WCF będzie zawierać dwie metody. W jednej metodzie spróbujemy przesłać listę elementów typu “Person” ,a w drugiej prześlemy prosty komunikat  tekstowy “Hello”.

Jeśli usługa nie działa wogóle to obie metody powinny nas zawieść.

using System.Collections.Generic;
using System.ServiceModel;

namespace Circular_ReferenceWCF
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        List<Person> GetData();

        [OperationContract]
        string GetHello();

    }
}

W metodzie GetData() tworze więc listę typów “Person”. Jak widać dzieci  zawierają referencje do swojego ojca.

public class Service1 : IService1
{
    public List<Person> GetData()
    {
        var p1 = new Person {Name = "Brad"};

        p1.MyChilds = new List<Child>()
                          {
                              new Child() {Father = p1,Name="Josh"},
                              new Child() {Father = p1,Name="Adam"},
                              new Child() {Father = p1,Name="Sophie"}
                          };

        var person = new List<Person>() {p1};

        return person;
    }

    public string GetHello()
    {
        return "Hello";
    }
}

Po stronie klienta napisałem prostą aplikacje konsolową. Gdy użytkownik wciśnie “W” wtedy spróbujemy pobrać z usługi sieciowej listę obiektów typu “Person”.  Gdy użytkownik wciśnie cokolwiek innego wtedy pobierzemy z usługi sieciowej prosty napis “Hello World”. Klawisz “Q” zamyka konsole.

static void Main(string[] args)
{
    bool check = true;

    while (check)
    {
        var key = Console.ReadKey().Key;
        check = key != ConsoleKey.Q;

        if (key == ConsoleKey.W)
        {
            try
            {
                var client = new CircularReferenceService.Service1Client();

                var list = client.GetData();

                foreach (var person in list)
                {
                    Console.WriteLine(person.Name);

                    foreach (var child in person.MyChilds)
                    {
                        Console.WriteLine("\t" + child.Name);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
        else
        {
            var client = new CircularReferenceService.Service1Client();
            Console.WriteLine(client.GetHello());
        }
    }
}

Jak widać aplikacje konsolowa nie problem z pobraniem napisu “Hello”. Jednak przy pobraniu listy otrzymujemy wyjątek. Wiem ,że usługa sieciowa działa więc problem jest innego rodzaju.

image

WCF Test Client zwraca ten sam wyjątek więc problem nie leży po stronie aplikacji konsolowej jakikolwiek on by nie był.

image

Gdzie leży więc problem.

Otóż WCF domyślnie nie wie jak serializować obiekty, które mają cykliczne związki miedzy sobą. Usługa sieciowa wykrywa problem i przerywa połączenie z klientem. Trzeba przyznać ,ze w takim wypadku przydałby się bardziej szczegółowy wyjątek, gdyż zerwanie połączenia nie wiele nam mówi.

image

Jak ten problem naprawić. Kiedyś to był duży problem ,ale od .NET 3.5 i WCF 3.5 ten problem można rozwiązać przy pomocy dodania właściwości “IsReference = true” do atrybutu  klasy reprezentującego rodzica.

[DataContract(IsReference = true)]
public class Person

Po zmianach nie zapomnij zbudować od nowa usługi sieciowej ,a następnie zaktualizować referencji po stronie klienta.

image

Jak widać po dodaniu atrybutu obiekty są prawidłowo serializowane i przesyłane.

image

Mój najlepszy przyjaciel zapewne dzisiaj rozwiązał ten problem dzięki mnie. Chociaż był on bardzo bliski rozwiązania. Niestety on dodawał właściwość atrybutu “IsReference” do dziecka ,a nie do rodzica i dosłownie wczoraj się zatrzymał na tym problemie.

Czuje się szczęśliwy ,że mogłem pomóc. Kto wie może to jest esencja życia. Jutro czeka mnie poważny egzamin i dobrze jest wiedzieć ,że bez względu na wszystko mogę polegać na niego ,a on na mnie.