Jakub Kuszneruk
Jakub Kuszneruk

Reputation: 1240

WCF crashes after sending EF object with it's references

I have database with several tables, including:

CREATE TABLE [dbo].[Exam]
(
    [Id] INT NOT NULL PRIMARY KEY identity, 
    [Author id] INT NULL, 
    ...
    CONSTRAINT [FK_Exam_Author] FOREIGN KEY ([Author id]) REFERENCES [User]([Id]), 
)

CREATE TABLE [dbo].[User]
(
    [Id] INT NOT NULL PRIMARY KEY identity, 
    ...
)

I have ADO.NET generated model of my database.

public partial class Exam
{
    public Exam()
    {
        ...
    }

    public int Id { get; set; }
    public Nullable<int> Author_id { get; set; }
    ...

    public virtual User User { get; set; }
    ...
}

public partial class Exam
{
    public User()
    {
        this.Exam = new HashSet<Exam>();
        ...
    }

    public int Id { get; set; }
    ...

    public virtual ICollection<Exam> Exam { get; set; }
    ...
}

I have also GetExam function in my Wcf service, which returns one exam from database.

public Exam GetExam()
{
    var context = new GDBDatabaseEntities();
    context.Configuration.ProxyCreationEnabled = false;
    var exams = context.Exam;
    var exam = exams.FirstOrDefault();
    return exam;
}

Disabling ProxyCreationEnabled was necessary to send Exam through Wcf.

Unfortunately above code returns me an exam with empty User field (EF generated this field automatically as a response for FK_Exam_Author)...

I've tried to load User attribute with Include function:

var exams = context.Exam.Include("User");

I've got following Wcf's error:

Failed to invoke the service. Possible causes: The service is offline or inaccessible; the client-side configuration does not match the proxy; the existing proxy is invalid. Refer to the stack trace for more detail. You can try to recover by starting a new proxy, restoring to default configuration, or refreshing the service.

Just before return from Wcf debugger shows that Exam object looks properly (has loaded User dependency).

I think it could be caused by fact, that loaded User has loaded his Exams list, which has loaded User... (circular dependence).

I've exact the same problem with User - Wcf works for User with empty Exams[] property, but when I've loaded list of User's Exams, Wcf has crashed...

How can I properly load relationship and send it via Wcf?

Additional question is how Wcf know how to serialize my objects (when I've used LINQ to SQL it generated classes with DataContract and DataMember attributes, but Entity Framework just don't do it.

UPDATE

It's the problem with circular dependences, because

public Exam GetExam()
{
    var context = new GDBDatabaseEntities();
    context.Configuration.ProxyCreationEnabled = false;
    var exams = context.Exam.Include("User");
    var exam = exams.FirstOrDefault();
    exam.User.Exam = null;
    return exam;
}

works.

Could anyone explain me why Include function is necessary for load 1-level dependences, which i need, but other dependences are loading for infinity? It's radiculous...

UPDATE

According to the answers and Yahoo's Best Practices for Speeding Up Your Web Site first rule I decided to create new object for every single Page in my Windows 8 application, which will reduce amount of sending data, number of requests and will let me avoid sending redundant data.

Upvotes: 1

Views: 974

Answers (1)

BradleyDotNET
BradleyDotNET

Reputation: 61369

Having tried this several times, I quickly abandoned using EF objects as my data contracts. As you found, the circular dependency breaks the DataContractSerializer very quickly. Additionally, your database objects likely contain information that doesn't need to be sent across the wire to clients.

Instead, create similar DataContract objects to the EF generated ones for use with WCF (without the circular dependencies and other problems), and translate between the two. For example:

[DataContract]
public class User
{
    [DataMember]
    public IEnumerable<Exam> Exams {get; set;}
}

[DataContract]
public class Exam
{
   [DataMember]
   public int Score {get; set;}
}

Then you just need a loader function like:

IEnumerable<Contracts.User> GetAllUsers()
{
   foreach (DB.User in context.Users)
   {
       Contracts.User wcfUser = new Contracts.User();
       wcfUser.Exams = new List<Contracts.Exam>();
       foreach (DB.Exam exam in wcfUser.Exams)
          wcfUser.Exams.Add(new Contracts.Exam() { Score = exam.Score };

       yield return wcfUser;
   }
}

Obviously this expands your code-base quite a bit, but avoids having your clients needing to know about your database objects, helps keep your network traffic down, and results in a better separation of concerns.

Hopefully that answers your question! Let me know if I can clarify anything.

Upvotes: 3

Related Questions