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



我正在尝试首先使用 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; }
}

之后,我将一个项目添加到数据库中

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>
}

我希望该表包含"描述 1"和"标签 1",但我只得到"描述 1"。我真的不明白问题出在哪里。正确的方法是什么?

您的导航属性需要标记为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>();
    }
}

若要使代码正常工作,可以将集合属性标记为@danludwig所述的virtual。通过将集合属性标记为virtual EF Code First 将在循环访问视图中的项时延迟加载这些属性。使用此方法时,您会遇到 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属性时,都会执行另一个选择。重要的是每个项目。这意味着如果你有 100 个Items db.Items 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集合,访问项目的属性,对于标记,您可以访问 ItemWithTagsTags 属性。

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

您可以使用像 MVC Mini Profiler 这样的工具来检查针对数据库执行的命令。此堆栈溢出问题演示如何使用 EF Code First 设置 MVC 迷你探查器。

最新更新