Reputation: 1650
I've been looking for answers for this relatively simple task but with no success. So I thought I'd ask my question here. I have got a simple database with two tables, Books and Authors.
I got my models generated by the ADO.NET entity data model. This is the auto-generated Books model:
public partial class Book
{
public int BookID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int ISBN { get; set; }
public int AuthorID { get; set; }
public virtual Author Author { get; set; }
}
And this is the auto-generated Authors model:
public partial class Author
{
public Author()
{
this.Books = new HashSet<Book>();
}
public int AuthorID { get; set; }
public string Name { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
And this is a part of the controller, the method for getting a list of all the books in JSON format.
// api/books
public IQueryable<Book> GetBooks()
{
// return db.Books.Include(x => x.Authors); Don't work
return db.Books;
}
This is my JS for calling the endpoint:
$.getJSON("api/books")
.done(function (data) {
console.log(data);
})
.fail(function (xhr) { console.log(xhr.responseText) });
Nothing fancy, just trying to make a GET request and receiving a list of all the books and their related authors.
This is a portion of the error message:
{"Message":"An error has occurred.","ExceptionMessage":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.","ExceptionType":"System.InvalidOperationException","StackTrace":null,"InnerException":{"Message":"An error has occurred.","ExceptionMessage":"Self referencing loop detected for property 'Author' with type 'System.Data.Entity.DynamicProxies.Author_5968F94A1BBB745A30D62CD59D0AC5F96A198B3F16F0EA2C2F61575F70034886'. Path '[0].Books[0]'.","ExceptionType":"Newtonsoft.Json.JsonSerializationException","StackTrace":"
I have tried preserving object references in JSON but that mangles the response. Is that is the only option?
Upvotes: 2
Views: 2287
Reputation: 467
It is always best to create your own custom models while returning the data but you just want to use Entity Framework classes then along with ignoring refrenceLoopHandeling you would need disable ProxyCreation from you Entity Model.
Follow these steps :
Step 1 : Like adaam described Put his in WebApiConfig register function:
// Prevent "Self referencing loop detected" error occurring for recursive objects
var serializerSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
config.Formatters.JsonFormatter.SerializerSettings = serializerSettings;
Step 2 : Which is most important in my case with latest EF to disable Proxy from EF Contaxt.
Goto: [FILE].edmx file then your [FILE].Context.cs
and add the line below in your constructor..
Configuration.ProxyCreationEnabled = false;
Now you won't have related class results any more..
Upvotes: 0
Reputation: 7485
adaam's answer is 100% correct but I thought I'd chime in with a bit of advice, but it's too long to fit in a comment so here goes.
Directly serialising Entity Framework objects is generally not a great idea; it's better to use simple DTO-style objects for passing data back to clients.
Of course this is just advice and YMMV :)
The benefits of using DTOs include
Proper decoupling from your Entity Framework
objects in your Controllers (you could create a Repository abstraction and use that in your controller, meaning your controller is free from a dependency on Entity Framework
and thus more testable)
Simpler serialization control - with Entity Framework
you will have difficulty trying to control what public properties are send across the wire to clients when the Entity Framework
proxy is directly serialized; typically in DB First the declarations for these properties are in auto-generated files that are re-written each time your edmx
changes; threfore it becomes painful to have to maintain non-serialization attributes on the properties you don't want sent across the wire (e.g [IgnoreDataMember]
, etc.)
If you're planning on accepting models via POST
, PUT
, etc, then you'll rapidly find that it is a pain (if not impossible) to effectively serialize "inward" directly to the Entity Framework
proxies so you'll have to write mapping code anyway; by using a DTO
approach you accept that you have to map up-front.
Circular references don't happen therefore you never need to worry about it and more importantly, you don't pay the cost of ignoring it (albeit minor, but the serializer has to do some work to avoid these references)
You can easily perform extra transformations, for example flattening, to better suit the client and/or hide details you don't want sent across the wire.
public class BookDTO
{
public int BookID {get;set;}
public string Title {get;set;}
public string Description {get;set;}
public int ISBN {get;set;}
public string AuthorName{get;set;}
}
public HttpResponseMessage GetBooks()
{
//ideally you'd be using a repository abstraction instead of db directly
//but I want to keep this simple.
var books = db.Books.Select(
book=>new BookDTO(){
BookID=book.BookID,
Title=book.Title,
Description=book.Description,
ISBN=book.ISBN,
AuthorName=book.Author.Name //<-flattening
});
return Request.CreateResponse(HttpStatusCode.OK, books);
}
This produces an array of nice, flat objects for the client to consume without having to expose for example the AuthorID
which might to be an internal concept you don't particularly want clients to know.
Once you get the hang of it, you can then look at using something like Automapper which will greatly reduce the maintenance burden, and allow you to perform inline projection in your queries.
Upvotes: 2
Reputation: 3706
If you examine the inner exception it says:
Self referencing loop detected for property 'Author'
This tells you that your Author class references back to the parent (i.e. Books or vice versa).
In your web api config (App_Start/WebApiConfig.cs
), add this:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Prevent "Self referencing loop detected" error occurring for recursive objects
var serializerSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
config.Formatters.JsonFormatter.SerializerSettings = serializerSettings;
}
}
This tells JSON.NET to ignore nested objects referring back to the parent object
Upvotes: 3