我正在使用Code First和POCO与Entity Framework 4来创建多对多关系。正确创建JOIN表以链接两个实体。但是,我需要在JOIN表中添加一个额外的列来创建关系中的排序。有什么办法存档吗?如何修改POCO对象以包含JOIN表中的附加索引列以允许排序? Code First可以实现这一点吗?如果没有,我该怎么办?
public class Product
{
public Guid ID { get; set; }
public virtual IList<Navigation> Navigations { get; set; }
}
public class Navigation
{
public Guid ID { get; set; }
public virtual IList<Product> Products { get; set; }
}
自动创建一个包含以下列的表,我需要一个额外的列来进行排序:
NavigationProducts
Navigation_ID
Product_ID
我可以手动创建附加列,但这会在架构更改时中断表的自动创建。
答案 0 :(得分:3)
我只需要做类似的事情。这是我想到的第一个解决方案,我运行了一个快速项目来对其进行原型设计。它有效,虽然我几乎没有花时间在它上面,所以有些东西告诉我,考虑到更多的想法,整个事情可以变得更有效率。但它非常灵活。
我在这个解决方案中使用了两个术语网页和Widget,为了澄清,我期待以下行为:
我在这个例子中使用了4个实体,虽然这个解决方案取决于使用1个实体作为关键。这个实体被映射到它自己的表并提供了2个连接表 - 这些连接表转到我最初想要多对多关系的2个实体的表中。
实体如下:
IndexedWebpageWidget:这是重要的实体。以下两个具体实体与此实体具有多对一关系。
BaseUnit: 网页和 Widget 的抽象基类 - (包含Id,标题和内容属性)
网页:具体 BaseUnit 子类,包含一个额外属性( IndexedWebpageWidget 列表)。
小部件:与网页相同,还包含一个额外属性(也是 IndexedWebpageWidget 列表)。
<强>的BaseUnit:强>
public abstract class BaseUnit
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required, MaxLength(300)]
public string Title { get; set; }
[Required, AllowHtml]
public string Content { get; set; }
}
小工具&amp;网页:强>
public class Widget : BaseUnit
{
[InverseProperty("Widget")]
public virtual List<IndexedWebpageWidget> Webpages { get; set; }
}
public class Webpage : BaseUnit
{
[InverseProperty("Webpage")]
public virtual List<IndexedWebpageWidget> Widgets { get; set; }
}
<强> IndexedWebpageWidget:强>
public class IndexedWebpageWidget
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public int Order { get; set; }
[Required]
[InverseProperty("Widgets")]
public virtual Webpage Webpage { get; set; }
[Required]
[InverseProperty("Webpages")]
public virtual Widget Widget { get; set; }
}
这里的[InverseProperty]属性是告诉实体框架哪些结束是哪个。这可能是不需要的,因为知道一对多的哪一端是委托人。主键是外键。
我创建了一个快速的DbContext和一个DBInitialiser,并使用它来填充数据库。 这些看起来像这样:
public class WebWidgetTestContext : DbContext
{
public DbSet<Webpage> Webpages { get; set; }
public DbSet<Widget> Widgets { get; set; }
public DbSet<IndexedWebpageWidget> WebpageWidgets { get; set; }
}
public class WebWidgetInitialiser : DropCreateDatabaseIfModelChanges<WebWidgetTestContext>
{
protected override void Seed(WebWidgetTestContext context)
{
//
// create 3 pages
//
var webpageA = new Webpage
{
Title = "Webpage A",
Content = "Content for Webpage A",
Widgets = new List<IndexedWebpageWidget>()
};
var webpageB = new Webpage
{
Title = "Webpage B",
Content = "Content for Webpage B",
Widgets = new List<IndexedWebpageWidget>()
};
var webpageC = new Webpage
{
Title = "Webpage C",
Content = "Content for Webpage C",
Widgets = new List<IndexedWebpageWidget>()
};
//
// create 3 widgets
//
var widget1 = new Widget
{
Title = "Widget 1",
Content = "Content for Widget 1",
Webpages = new List<IndexedWebpageWidget>()
};
var widget2 = new Widget
{
Title = "Widget 2",
Content = "Content for Widget 2",
Webpages = new List<IndexedWebpageWidget>()
};
var widget3 = new Widget
{
Title = "Widget 3",
Content = "Content for Widget 3",
Webpages = new List<IndexedWebpageWidget>()
};
// now match them up
var map1 = new IndexedWebpageWidget
{
Webpage = webpageA,
Widget = widget1,
Order = 1
};
var map2 = new IndexedWebpageWidget
{
Webpage = webpageA,
Widget = widget2,
Order = 2
};
var map3 = new IndexedWebpageWidget
{
Webpage = webpageB,
Widget = widget1,
Order = 1
};
var map4 = new IndexedWebpageWidget
{
Webpage = webpageB,
Widget = widget3,
Order = 3
};
var map5 = new IndexedWebpageWidget
{
Webpage = webpageC,
Widget = widget2,
Order = 2
};
var map6 = new IndexedWebpageWidget
{
Webpage = webpageC,
Widget = widget3,
Order = 1
};
// add
context.WebpageWidgets.Add(map1);
context.WebpageWidgets.Add(map2);
context.WebpageWidgets.Add(map3);
context.WebpageWidgets.Add(map4);
context.WebpageWidgets.Add(map5);
context.WebpageWidgets.Add(map6);
// save
context.SaveChanges();
}
}
_
你可以看到我在上下文中添加了3个DBSets。我这样做了,所以我很容易在Initialiser中播种数据库。
请注意,map5和map6为网页C的小部件分配不同的排序......
值得注意的是,Entity Framework仅创建了3个表 - dbo.IndexedWebpageWidgets , dbo.Webpages 和 dbo.Widgets 。 IndexedWebpageWidgets的表与单个连接表完全相同,并添加了一个订单字段。
_
最后,为了快速查看我修改了我的家庭控制器:
public class HomeController : Controller
{
private WebWidgetTestContext db = new WebWidgetTestContext();
public ActionResult Index()
{
var model = db.Webpages.ToList();
return View(model);
}
}
在我的观点中:
@model List<WebpageWidgetTest.Models.Webpage>
@{
ViewBag.Title = "Home Page";
}
<h2>@ViewBag.Message</h2>
<div>
@{
foreach (var webpage in Model)
{
<div>
<h4>@webpage.Title</h4>
<p>@webpage.Content</p>
<p>Applicable Widgets:</p>
@foreach (var widget in webpage.Widgets.OrderBy(w => w.Order))
{
<div>
<h5>@widget.Widget.Title (@widget.Order)</h5>
<p>@widget.Widget.Content</p>
</div>
}
</div><hr /><br /><br />
}
}
</div>
在上面的Razor代码中,ViewModel是所有网页的列表。我遍历它们并写出标量属性。然后,对于Widgets属性中的每个IndexedWebpageWidget,我按顺序循环并写出小部件。交换db中order属性的值将导致它们以不同的顺序出现。
-
这基本上产生以下HTML:
Webpage A
Content for Webpage A
Applicable Widgets:
Widget 1 (1)
Content for Widget 1
Widget 2 (2)
Content for Widget 2
Webpage B
Content for Webpage B
Applicable Widgets:
Widget 1 (1)
Content for Widget 1
Widget 3 (3)
Content for Widget 3
Webpage C
Content for Webpage C
Applicable Widgets:
Widget 3 (1)
Content for Widget 3
Widget 2 (2)
Content for Widget 2
-
你可以在网页C中看到Widget 3在widget 2之前出现,基于它的order属性(在括号中每个widget的标题旁边显示)...交换一个小部件对网页的顺序不会影响它订购另一个网页。 因此,所有要求都已实现。
这种方法唯一真正的缺点是你有一个额外的实体。这实际上派上用场,虽然它可以在它连接在一起的两个物体之间穿越。我不确定另一种方法可以做到这一点。需要映射关系的每一面,因此我可以将所有导航属性添加到BaseUnit类中。
_
无论如何,我希望这有帮助。
干杯,
本
答案 1 :(得分:1)
我认为这是T4 templates派上用场的地方。另请查看Customizing Entity Classes in VS 2010。