How to get around an object with a relation using asp.net web api, framework entity and json (code first)?

Using the .NET client application, I am trying to send an A object containing a collection of B objects via asp.net web api JSON, create a LocalDB database and save the data. Then select object A.

The application includes 3 projects. The asp.net mvc 4 web api project, the .NET console application and the .NET class library. The asp.net application and the console application reference a class library that includes class definitions for objects A and B.

Class library:

public class Actor { public Actor() { this.Movies = new HashSet<Movie>(); } public int ActorID { get; set; } public string Name { get; set; } public virtual ICollection<Movie> Movies { get; set; } } public class Movie { public Movie() { this.Actors = new HashSet<Actor>(); } public int MovieID { get; set; } public string Name { get; set; } public virtual ICollection<Actor> Actors { get; set; } } 

Console Application:

 Movie movie = new Movie() { Name = "Dr. No" }; Actor actor = new Actor() { Name = "Sean Connery" }; movie.Actors.Add(actor); using (HttpClient client = new HttpClient()) { client.BaseAddress = new Uri("http://localhost:3654"); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var response = client.PostAsJsonAsync<Movie>("api/movies", movie).Result; response.EnsureSuccessStatusCode(); response = client.GetAsync("api/movies/1").Result; response.EnsureSuccessStatusCode(); Movie newMovie = response.Content.ReadAsAsync<Movie>().Result; } 

asp.net mvc DbContext:

 public class MediaContext : DbContext { public MediaContext() { this.Configuration.LazyLoadingEnabled = false; } public DbSet<Movie> Movies { get; set; } public DbSet<Actor> Actors { get; set; } } 

Problem # 1: JSON doesn't seem to like a circular link, however, unless I add a collection to both objects, EF5 does not create a MoviesActors table to hold the link.

Problem No. 2: Even if I add a link to the controller when I return this object, it will not return it using the Actors. For instance. I was expecting something like

 Movie { MovieID = "1", Name = "???", Actors[] = { 1 } } 

But instead, the actors are simply nil.

Update . Here is an exception to self-regulation:

ExceptionMessage = Self-reference loop detected using the type "System.Data.Entity.DynamicProxies.Movie_157D88BDC89E46A7CE4875C2970C7BBFB893972095EFA0745C2261AACC007969". Path '[0] .Actors [0] .Movies'.

I managed to get around this exception using the method. How did I resolve the Json ring reference error? and just turned off the proxy. This solves the problem number 1. However, when I receive films, they still return without Actors, even using Include ("Actors"). I see that the link was created correctly in the staging table in LocalDb.

Update 2

FINALLY figured it out, answer below.

Thank you very much!

+4
source share
2 answers

After a long search, I was finally able to solve my problem. I initially tried to add the [ScriptIgnore] tag to the movie set, however I used the final version of EF5, which changed the default serializer to JSON.NET. Finally, I found that [ScriptIgnore] was not working, and I had to install [JsonIgnore] as follows:

 public class Actor { public int ActorID { get; set; } public string Name { get; set; } [JsonIgnore] public virtual ICollection<Movie> Movies { get; set; } } public class Movie { public int MovieID { get; set; } public string Name { get; set; } public virtual ICollection<Actor> Actors { get; set; } } 

Doing this in conjunction with the active InInlude function ("Actors") in the Get method finally solved my problem.

In the end, I needed:

  • Have virtual ICollection links for both objects to create a staging table.
  • Add [JsonIgnore] to the movie collection link to solve the circular link.
  • Disable the proxy to resolve my 500 error.
  • Animated loading of Actors in the GetMovies / GetMovie method using .Include ("Actors")

In addition, I found that for subsequent PUTs of the Movie object where the Actors collection was changed, I had to manually change the RelationShip state on each child Actor to add / remove to ensure that the associated collection was updated.

+8
source

About comments:

In addition, I would be completely happy to remove public virtual ICollection<Movie> Movies { get; set; } public virtual ICollection<Movie> Movies { get; set; } public virtual ICollection<Movie> Movies { get; set; } from the Actor class, however, EF does not generate a staging table in LocalDB.

If you delete one of the collections, EF assumes that your relationship is a one-to-many relationship (which does not have an intermediate table, but only a foreign key in one of the tables), unlike the two-collection model, where EF creates the relationship many to many "(having an intermediate table).

However, you can override this convention using the Fluent API and explicitly tell EF that the remaining collection is for many-to-many relationships instead of one-to-many relationships. Having this model ...

 public class Actor { //... public int ActorID { get; set; } public string Name { get; set; } } public class Movie { //... public int MovieID { get; set; } public string Name { get; set; } public virtual ICollection<Actor> Actors { get; set; } } 

... you can define a many-to-many relationship this way:

 public class MediaContext : DbContext { //... protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Movie>() .HasMany(m => m.Actors) .WithMany() .Map(a => { a.MapLeftKey("MovieID"); a.MapRightKey("ActorID"); a.ToTable("MovieActors"); // intermediate table }); } } 
+1
source

All Articles