简体   繁体   English

如何使用asp .net web api,实体框架和json(代码优先)来关联对象?

[英]How to round trip an object with relation using asp .net web api, entity framework and json (code first)?

Using a .NET client app, I am trying to post an object A that includes a collection of objects B via asp .net web api JSON, have that create a LocalDB database, and store the data. 使用.NET客户端应用程序,我试图通过asp .net web api JSON发布一个包含对象B集合的对象A,创建一个LocalDB数据库并存储数据。 Then fetch object A again. 然后再次获取对象A.

The app includes 3 projects. 该应用程序包括3个项目。 A asp .net mvc 4 web api project, a .Net console app and a .Net class library. 一个asp .net mvc 4 web api项目,一个.Net控制台应用程序和一个.Net类库。 Both the asp .net app and console app reference the class library, which includes the class definitions for objects A and B. asp .net应用程序和控制台应用程序都引用类库,其中包括对象A和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 App: 控制台应用:

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: 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: It seems JSON doesn't like the circular reference, however if I don't add the collection to both objects, EF5 does not create a MoviesActors table to hold the reference. 问题#1:似乎JSON不喜欢循环引用,但是如果我不将集合添加到两个对象,EF5不会创建一个MoviesActors表来保存引用。

Problem #2: Even if I add the reference in the controller, when I return that object, it doesn't return it with the Actors. 问题#2:即使我在控制器中添加引用,当我返回该对象时,它也不会使用Actors返回它。 Eg I expected something like 我估计有类似的东西

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

But instead, Actors is just null. 但相反,Actors只是空的。

Update : Here is the self-referencing exception: 更新 :这是自引用异常:

ExceptionMessage=Self referencing loop detected with type 'System.Data.Entity.DynamicProxies.Movie_157D88BDC89E46A7CE4875C2970C7BBFB893972095EFA0745C2261AACC007969'. ExceptionMessage =使用类型'System.Data.Entity.DynamicProxies.Movie_157D88BDC89E46A7CE4875C2970C7BBFB893972095EFA0745C2261AACC007969'检测到的自引用循环。 Path '[0].Actors[0].Movies'. 路径'[0] .Actors [0] .Movies'。

I managed to work around this exception using the method at How did I solve the Json serializing circular reference error? 我设法使用如何解决Json序列化循环引用错误的方法解决此异常 and just disabling the proxy. 并且只是禁用代理。 That solves problem #1. 这解决了问题#1。 However when I get the Movies, they still come back with no Actors, even using Include("Actors"). 然而,当我收到电影时,即使使用Include(“演员”),他们仍然会回来没有演员。 I can see the reference has been created correctly in the intermediate table in the LocalDb. 我可以看到在LocalDb的中间表中正确创建了引用。

Update 2 更新2

FINALLY figured this out, answer below. 最后想出来了,回答如下。

Thanks so much! 非常感谢!

After much searching I was finally able to resolve my issue. 经过多次搜索,我终于能够解决我的问题。 Originally I tried adding a [ScriptIgnore] tag to the Movies set, however I was using EF5 final which changed the default serializer to JSON .NET. 最初我尝试将[ScriptIgnore]标签添加到Movies集中,但是我使用EF5 final将默认序列化程序更改为JSON .NET。 I finally found that [ScriptIgnore] didn't work and I had to set [JsonIgnore] like this: 我终于发现[ScriptIgnore]没有用,我必须像这样设置[JsonIgnore]:

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 that combined with the .Include("Actors") eager loading in the Get method finally solved my issue. 在Get方法中结合.Include(“Actors”)的热切加载最终解决了我的问题。

In summary I needed to: 总之,我需要:

  1. Have the virtual ICollection references on both objects to create the intermediate table. 在两个对象上都有虚拟ICollection引用以创建中间表。
  2. Add [JsonIgnore] to the Movies collection reference to solve the circular reference. 将[JsonIgnore]添加到Movies集合引用以解决循环引用。
  3. Turn off the proxy to solve my 500 error. 关闭代理以解决我的500错误。
  4. Eager load the Actors in the GetMovies/GetMovie method using .Include("Actors") 使用.Include(“Actors”)在GetMovies / GetMovie方法中加载Actors

Additionally I found for subsequent PUTs of the Movie object, where the Actors collection had changed, I had to manually change the RelationShip state on each child Actor to either Added/Deleted to make sure the related collection was updated. 另外我找到了Movie对象的后续PUT,其中Actors集合已经改变,我不得不手动将每个子Actor上的RelationShip状态更改为Added / Deleted以确保相关集合已更新。

About your comment: 关于你的评论:

Also, I'd be completely happy to remove the public virtual ICollection<Movie> Movies { get; set; } 此外,我很乐意删除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 in doing that, EF wasn't generating the intermediate table in the LocalDb. public virtual ICollection<Movie> Movies { get; set; }从Actor类,然而,在这样做,EF是不产生在的LocalDB中间表。

If you remove one of the collections EF assumes by convention that your relationship is a one-to-many relationship (which doesn't have an intermediate table, but only a foreign key in one of the tables) in contrast to the model with two collections where EF creates a many-to-many relationship (that has an intermediate table). 如果删除其中一个集合,EF假定您的关系是一对多关系(没有中间表,但只有一个表中的外键)与两个模型形成对比EF创建多对多关系(具有中间表)的集合。

However, you can override this convention using Fluent API and tell EF explicitly that the remaining collection belongs to a many-to-many relationship instead of a one-to-many. 但是,您可以使用Fluent API覆盖此约定,并明确告知EF其余集合属于多对多关系而不是一对多关系。 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
            });
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM