ASP MVC多对多关系 - 从关联表中检索数据

时间:2012-07-05 03:28:35

标签: asp.net-mvc entity-framework many-to-many code-first

我正在尝试使用EF代码创建一个具有多对多关系的数据库。

public class Item
{
    public int ItemId { get; set; }
    public String Description { get; set; }
    public ICollection<Tag> Tags { get; set; }

    public Item()
    {
        Tags = new HashSet<Tag>();
    }
}

public class Tag
{
    public int TagId { get; set; }
    public String Text { get; set; }
    public ICollection<Item> Presentations { get; set; }

    public Tag()
    {
        Presentations = new HashSet<Item>();
    }
}

public class ItemsEntities : DbContext
{
    public DbSet<Item> Items { get; set; }
    public DbSet<Tag> Tags { get; set; }
}

之后我将一个Item添加到数据库中

var tag = new Tag { Text = "tag1" };
var item = new Item
{
    Description = "description1",
    Tags = new List<Tag>()
};
item.Tags.Add(tag);
using (var db = new ItemsEntities())
{
    db.Items.Add(item);
    db.SaveChanges();
}

问题是我无法输出带有相关标签的项目。控制器看起来像这样:

public ActionResult Index()
{
    ItemsEntities db = new ItemsEntities();
    return View(db.Items.ToList());
}

并且视图页面包含以下代码:

@foreach (var item in Model) 
{
   <tr>
    <td>
        @Html.DisplayFor(model => item.Description)
    </td>
    <td>
        @foreach (var tag in item.Tags)
        {
            @tag.Text
        }
    </td>
</tr>
}

我希望该表包含“description1”和“tag1”,但我只得到“description1”。我真的不明白问题出在哪里。这样做的正确方法是什么?

2 个答案:

答案 0 :(得分:2)

您的导航属性需要标记为virtual

public class Item
{
    public int ItemId { get; set; }
    public String Description { get; set; }
    public virtual ICollection<Tag> Tags { get; set; }

    public Item()
    {
        Tags = new HashSet<Tag>();
    }
}

public class Tag
{
    public int TagId { get; set; }
    public String Text { get; set; }
    public virtual ICollection<Item> Presentations { get; set; }

    public Tag()
    {
        Presentations = new HashSet<Item>();
    }
}

答案 1 :(得分:1)

要使代码正常工作,您可以将集合属性标记为@danludwig所述的virtual。通过将集合属性标记为virtual,EF代码首先将在迭代视图中的项目时延迟加载这些属性。使用此方法遇到SELECT N + 1问题。我们来检查您的视图代码:

@foreach (var item in Model) 
{
   <tr>
     <td>
       @Html.DisplayFor(model => item.Description)
     </td>
     <td>
       @foreach (var tag in item.Tags)
       {
         @tag.Text
       }
     </td>
   </tr>
}

在此foreach循环中,您将遍历模型中使用EF数据上下文选择的所有项目。

db.Items.ToList()

这是你的第一个选择。但是在上面的视图中,每次访问项目的Tags属性时,都会执行另一个选择。重要的是 FOR EVERY ITEM 。这意味着如果Items db.Items中有100 DbSet,您将执行101次选择。这对大多数系统来说都是不可接受的。

更好的方法是预先为每个项目选择标签。一种方法是使用Include或者将与项目相关的标签选择为专用对象。

public class ItemWithTags 
{
    public Item Item { get;set; }
    public IEnumerable<Tag> Tags { get;set; }
}

public ActionResult Index()
{
    ItemsEntities db = new ItemsEntities();

    var itemsWithTags = db.Items.Select(item => new ItemWithTags() { Item = item, Tags = item.Tags});
    return View(itemsWithTags.ToList());
}

在您的视图中,您可以遍历itemsWithTags集合,访问项目的属性以及您访问Tags ItemWithTags属性的代码。

您的代码的另一个问题是, ItemsEntities DbContext在您的代码中打开但从未关闭。您可以使用VS MVC模板生成一个控制器,以正确处理DbContext打开和关闭!

您可以使用MVC Mini Profiler之类的工具来检查针对数据库执行的命令。这个Stackoverflow Question显示了如何使用EF Code First设置MVC Mini Profiler。