c# - 与 Entity Framework 和 MVC 的多对多关系与排序

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

我正在使用带有 Entity Framework 4 的 POCO 的 Code First 来创建多对多关系。正确创建了 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

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

最佳答案

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

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

  • 一个网页可以分配有许多小部件,并且这些小部件必须是可订购/可订购的。
  • 同时一个Widget可以分配给许多不同的网页。
  • 小部件必须为它们附加到的每个不同网页维护不同的顺序。


  • 背景

    我在这个例子中使用了 4 个实体,尽管这个解决方案取决于使用 1 个实体作为症结。该实体被映射到它自己的表并提供 2 个连接表,这些连接表用于我最初想要多对多关系的 2 个实体的表。

    实体如下:

    索引网页小部件:这是重要的实体。下面的两个具体实体都与该实体具有多对一的关系。

    基本单元: Webpage 和 Widget 的抽象基类 - (包含 Id、Title 和 Content 属性)

    网页:具体的 BaseUnit 子类,包含一个额外的属性(IndexedWebpageWidget 列表)。

    小工具:与网页相同,还包含一个额外的属性(也是 IndexedWebpageWidget 的列表)。

    实体

    基本单元:
    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; }
        }
    

    小部件和网页:
    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; }
    }
    

    索引网页小部件:
    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] 属性是告诉 Entity Framework 哪个端是哪个端。这可能不需要,因为知道一对多的哪一端是主体。主键是外键。

    测试

    我创建了一个快速的 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 个 DBSet。我这样做是为了很容易在 Initialiser 中播种数据库。

    请注意,map5 和 map6 为网页 C 的小部件分配不同的顺序...

    还值得注意的是,Entity Framework 仅创建 3 个表 - dbo.IndexedWebpageWidgets , dbo.网页 , 和 dbo.Widgets . IndexedWebpageWidgets 的表的行为与单个连接表完全一样,但添加了一个 order 字段。

    _

    最后,为了快速查看,我修改了我的家庭 Controller :
    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 中看到小部件 3 位于小部件 2 之前,这取决于它的 order 属性(显示在每个小部件标题旁边的括号中)... 将一个小部件的顺序与网页交换不会影响它对另一个小部件的排序网页。
    因此,所有要求都已达到。

    这种方法唯一真正的缺点是你有一个额外的实体。这实际上派上了用场,尽管它可以在它绑定(bind)在一起的 2 个对象之间进行遍历。我不确定可以做到这一点的另一种方式。关系的每一侧都需要映射,因此我可以将所有导航属性添加到 BaseUnit 类中。

    _

    无论如何,我希望这会有所帮助。

    干杯,

    关于c# - 与 Entity Framework 和 MVC 的多对多关系与排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6590289/

    相关文章:

    c# - EF 6.0 代码优先 : How to Synchronize an existing database with existing domain classes

    c# - 无需检查 web.config 文件即可进行身份验证

    c# - Windows 10 操作系统上的 Windows 窗体图形问题

    c# - 为什么枚举权限经常有 0、1、2、4 值?

    c# - 如何在 ASP.Net 和 C# 网页中将 Reporting Services 报告显示为内联 PDF?

    c# - 创建和写入 XML 文件抛出异常 -> 系统内存不足异常?

    c# - 如何在 C# windows 应用程序中以编程方式修改设置文件 (app.config) 的信息?

    c# - 我应该如何 "Cancel"ConcurrentDictionary 中的 AddOrUpdate?

    .net - 插入符号 (‘^’ ) 在 C++/CLI 中是什么意思?

    entity-framework - Entity Framework 端点多重性