我想创建一个聊天系统,它有免费聊天和付费聊天。付费聊天与免费聊天不同,只有一个属性(“价格”)。所以付费聊天继承了免费聊天,并为其添加了一个属性。此外,我不想以未设定的价格进行付费聊天。所以我创建了一个自定义公共构造函数并保留私有默认构造函数(因此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团队以这种方式实施它的原因有哪些?
任何解释都将受到高度赞赏!
答案 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这样的内容来反映数据库中已存在的内存中图形中的关联,从而避免标记的关联补充说。但是我会争辩说,当你需要控制级别来加载多对多关联的过滤子集时,可能还有其他更实用的解决方案:
在另一边开始查询并使用include:例如在您的情况下,而不是加载聊天并包括用户,尝试仅查询您感兴趣的用户(您甚至可以使用过滤器来引用用户参与where子句的聊天,如果这是什么你需要)然后使用一个简单的include来加载与这些用户相关的所有聊天。
替换与真实实体的多对多关联:这样的实体将代表用户参与聊天,例如UserInChat。这可能需要编写添加额外代码来管理新实体,但会为您提供最大程度的控制,以便随时删除,添加和查询关联。