我正在尝试使用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”。我真的不明白问题出在哪里。这样做的正确方法是什么?
答案 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。