我正在尝试首先使用 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
集合,访问项目的属性,对于标记,您可以访问 ItemWithTags
的 Tags
属性。
代码的另一个问题是,ItemsEntities DbContext
在代码中打开,但永远不会关闭。您可以使用VS MVC模板生成一个控制器,该控制器可以正确处理DbContext
打开和关闭!
您可以使用像 MVC Mini Profiler 这样的工具来检查针对数据库执行的命令。此堆栈溢出问题演示如何使用 EF Code First 设置 MVC 迷你探查器。