与实体框架和MVC的多对多关系与订购

时间:2011-07-06 00:17:05

标签: c# .net entity-framework many-to-many

我正在使用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

我可以手动创建附加列,但这会在架构更改时中断表的自动创建。

2 个答案:

答案 0 :(得分:3)

我只需要做类似的事情。这是我想到的第一个解决方案,我运行了一个快速项目来对其进行原型设计。它有效,虽然我几乎没有花时间在它上面,所以有些东西告诉我,考虑到更多的想法,整个事情可以变得更有效率。但它非常灵活。

我在这个解决方案中使用了两个术语网页和Widget,为了澄清,我期待以下行为:

  • 网页可以分配许多小部件,这些小部件必须是有序/可订购的。
  • 同时可以将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