EntityFramework多对多关系错误或奇怪的设计

时间:2014-02-23 02:10:32

标签: entity-framework many-to-many

我想创建一个聊天系统,它有免费聊天和付费聊天。付费聊天与免费聊天不同,只有一个属性(“价格”)。所以付费聊天继承了免费聊天,并为其添加了一个属性。此外,我不想以未设定的价格进行付费聊天。所以我创建了一个自定义公共构造函数并保留私有默认构造函数(因此EF将能够实现付费聊天实例)。

以下是代码:

namespace EfTest
{
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    using System.Data.Entity.Migrations;
    using System.Diagnostics;

    public class User
    {
        public string Id { get; set; }
        public virtual List<Chat> Chats { get; set; }
    }

    public class Chat
    {
        public string Id { get; set; }
        public virtual List<User> Users { get; set; }

        public Chat()
        {
            this.Users = new List<User>();
        }
    }

    public class PaidChat : Chat
    {
        public int Price { get; set; }

        /* PRIVATE MODIFICATOR CAUSES THE ISSUE */
        private PaidChat() { }

        public PaidChat(int price)
        {
            Price = price;
        }
    }

    public class TestContext : DbContext
    {
        public DbSet<Chat> Chats { get; set; }
        public DbSet<User> Members { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<PaidChat>().ToTable("PaidChat");
            base.OnModelCreating(modelBuilder);
        }
    }

    class Program
    {
        static void Main()
        {
            const string ChatId = "1";

            using (var dbContext = new TestContext())
            {
                var user1 = new User { Id = "1" };
                var user2 = new User { Id = "2" };
                dbContext.Members.AddOrUpdate(m => m.Id, user1, user2);

                var paidChat = new PaidChat(50) { Id = ChatId };
                paidChat.Users.AddRange(new[] { user1, user2 });
                dbContext.Chats.AddOrUpdate(c => c.Id, paidChat);

                dbContext.SaveChanges();
            }

            using (var dbContext = new TestContext())
            {
                var queryResult = (from chat in dbContext.Chats
                                   where chat.Id == ChatId
                                   select new
                                   {
                                       Chat = chat,
                                       Users = chat.Users
                                   }).Single();

                Debug.Assert(queryResult.Users.Count == 2);
                Debug.Assert(queryResult.Chat.Users.Count == 2);    // !!! HERE WE HAVE A PROBLEM !!!
            }
        }
    }
}

PaidChat的私人默认控制器阻止EF接收用户请求。

即使从DB检索到适当数量的用户:

Debug.Assert(queryResult.Users.Count == 2);

我无法从聊天实例访问它们:

Debug.Assert(queryResult.Chat.Users.Count == 2);    // !!! HERE WE HAVE A PROBLEM !!!

我对EF 5.0和EF 6.0.2的问题。

这是一个错误吗?或者也许EF团队以这种方式实施它的原因有哪些?

任何解释都将受到高度赞赏!

2 个答案:

答案 0 :(得分:1)

在您使用延迟加载时,Entity Framework会在运行时创建一个动态代理类,它将派生自您的实体类型。

代理将覆盖实体的导航属性,并添加一些逻辑以使延迟加载成为可能。这就是必须将导航属性声明为虚拟的原因。

延迟加载工作的所有要求都可以是found on msdn

正如您所看到的,私有构造函数不起作用,因此将禁用该类的延迟加载。

修复方法是改为构造函数protected,因此只有子类(动态代理)可以调用它。

public class PaidChat : Chat
{
    public int Price { get; set; }

    /* PROTECTED VISIBILITY FIXES THE ISSUE */
    protected PaidChat() { }

    public PaidChat(int price)
    {
        Price = price;
    }
}

答案 1 :(得分:1)

请注意,我已经投了Peter的答案,因为我相信这是你第一个问题的正确答案。我的回答仅适用于您的第二个问题以及您的代码段的意图:

为了跟踪多对多关联,上下文需要链接表中未包含在实体本身中的其他数据。虽然使用Include()的查询将检索此类附加数据并将其置于上下文中,但实体和导航属性的简单投影将不会。

在您的示例中进行投影后,您可以深入查看ObjectContext API并使用像RelatedEnd.Attach这样的内容来反映数据库中已存在的内存中图形中的关联,从而避免标记的关联补充说。但是我会争辩说,当你需要控制级别来加载多对多关联的过滤子集时,可能还有其他更实用的解决方案:

  1. 在另一边开始查询并使用include:例如在您的情况下,而不是加载聊天并包括用户,尝试仅查询您感兴趣的用户(您甚至可以使用过滤器来引用用户参与where子句的聊天,如果这是什么你需要)然后使用一个简单的include来加载与这些用户相关的所有聊天。

  2. 替换与真实实体的多对多关联:这样的实体将代表用户参与聊天,例如UserInChat。这可能需要编写添加额外代码来管理新实体,但会为您提供最大程度的控制,以便随时删除,添加和查询关联。