当我执行context.Users.Create()时,导航属性为null。那是对的吗?

时间:2013-07-30 14:06:42

标签: c# entity-framework entity-framework-4

我正在开发我的第一个类库,它使用Entity Framework Code First作为数据访问层。

我有这堂课:

public class User
{
    public int UserId { get; set; }
    public String Name { get; set; }
    public int Age { get; set; }
    public String City { get; set; }
    public String Country { get; set; }
    public String Email { get; set; }
    public String InterestIn { get; set; }

    public virtual ICollection<User> Friends { get; set; }
    public virtual ICollection<User> FromWhomIsFriend { get; set; }
}

现在我用控制台应用程序测试我的代码:

static void Main(string[] args)
{
    Database.SetInitializer(
        new DropCreateDatabaseAlways<AdnLineContext>());

    insertUsersAndFriends();
}

private static void insertUsersAndFriends()
{
    using (var context = new AdnLineContext())
    {

        var user1 = context.Users.Create();
        user1.Name = "User1";
        user1.Age = 25;
        user1.City = "City1";
        user1.Country = "Country1";
        user1.Email = "email_1@email.com";
        user1.InterestIn = "User1's interests";

        var user2 = context.Users.Create();
        user2.Name = "User2";
        user2.Age = 26;
        user2.City = "City2";
        user2.Country = "Country2";
        user2.Email = "email_2@email.com";
        user2.InterestIn = "User2's interests";

        var user3 = context.Users.Create();
        user3.Name = "User3";
        user3.Age = 27;
        user3.City = "City3";
        user3.Country = "Country3";
        user3.Email = "email_3@email.com";
        user3.InterestIn = "User3's interests";

        context.Users.Add(user1);
        context.Users.Add(user2);
        context.Users.Add(user3);

        user1.Friends.Add(user2);
        user3.Friends.Add(user1);

        context.SaveChanges();
    }
}

我正在测试,因此数据库为空

这是我的UserConfiguration课程:

public UserConfiguration()
{
    Property(d => d.Name).IsRequired();
    Property(d => d.Age).IsRequired();
    Property(d => d.City).IsRequired();
    Property(d => d.Country).IsRequired();
    Property(d => d.Email).IsRequired();
    Property(d => d.InterestIn).IsRequired();

    HasMany(d => d.MessagesSent).WithRequired(l => l.SentByUser).WillCascadeOnDelete(false);
    HasMany(d => d.MessagesReceived).WithRequired(l => l.SentToUser).WillCascadeOnDelete(false);

    HasMany(d => d.Friends).
        WithMany(d => d.FromWhomIsFriend).
        Map(c =>
            {
                c.ToTable("UserFriends");
                c.MapLeftKey("UserId");
                c.MapRightKey("FriendId");
            });
    HasMany(d => d.WantsToDo).
        WithMany(a => a.Users).
        Map(t =>
            {
                t.ToTable("UserActivities");
                t.MapLeftKey("UserId");
                t.MapRightKey("ActivityId");
            });
}

但我在user1.Friends.Add(user2);得到一个空指针异常,因为Friends null

我做错了什么?我该如何解决这个问题?

5 个答案:

答案 0 :(得分:2)

在这种情况下,

实体框架似乎很聪明。您正在向上下文添加新的User

var user1 = context.Users.Create();
//...
context.Users.Add(user1);
//...
user1.Friends.Add(user2);

此后实体处于Added状态。为什么EF运行延迟加载的查询来初始化Friends集合? user1是关系中的主体,因为状态是Added,它在数据库中还不存在,因此数据库中不能有任何可以加载的依赖项。因此,EF根本不会尝试加载集合(这样可以避免不必要的数据库往返)。

您可以应用技巧使其工作 - 通过附加新用户,然后再将其添加到上下文中:

    var user1 = context.Users.Create();
    //...

    var user2 = context.Users.Create();
    //...

    var user3 = context.Users.Create();
    //...

    user1.UserId = 1;
    context.Users.Attach(user1);
    user2.UserId = 2;
    context.Users.Attach(user2);
    user3.UserId = 3;
    context.Users.Attach(user3);
    // ...because we cannot attach three users with the same key

    user1.Friends.Add(user2);
    user3.Friends.Add(user1);
    // Lazy loading will run twice here based on the `UserId` which is 1,2,3
    // and returns nothing, but the Friends collection will be initialized
    // as empty collection

    // This MUST be AFTER accessing the Friends collection
    context.Users.Add(user1);
    context.Users.Add(user2);
    context.Users.Add(user3);
    // the three "dummy UserIds" are ignored because state is Added now

    context.SaveChanges();

现在,再次忘记这个解决方案。强制延迟加载(=昂贵的数据库查询)来创建空集合是无稽之谈。 C#有new运算符:

    var user1 = context.Users.Create();
    //...

    var user2 = context.Users.Create();
    //...

    var user3 = context.Users.Create();
    //...

    user1.Friends = new List<User>();
    user1.Friends.Add(user2);

    user3.Friends = new List<User>();
    user3.Friends.Add(user1);

    context.Users.Add(user1);
    context.Users.Add(user2);
    context.Users.Add(user3);

    context.SaveChanges();

您也可以在此场景中使用var user1 = new User(),只需在上下文中添加新实体。创建动态代理在这里没有任何好处(除非您将任何外键属性设置为其他现有实体,并希望在调用SaveChanges后访问其相应的导航属性 - 在您的示例中似乎不是这种情况)。

答案 1 :(得分:1)

您必须声明“List&lt;&gt;”像这样的属性:

public virtual ICollection<User> Friends { get; set; }

如果您不使用虚拟关键字,EF将不会为您初始化集合。 在您的情况下,您正在创建新对象,使用私有属性为新对象初始化它:

private ICollection<User> _friends;
public ICollection<User> Friends { 
   get { return _friends ?? (_friends = new List<User>()); }
   set { _friends = value; }
}

答案 2 :(得分:0)

你必须像这样初始化会员朋友:

using (var context = new AdnLineContext())
{
    context.Users.Add(user1);
    context.Users.Add(user2);
    context.Users.Add(user3);

    user1.Friends = new List<User>();
    user1.Friends.Add(user2);
    user3.FromWhomIsFriend.Add(user1);

    context.SaveChanges();
}

答案 3 :(得分:0)

我认为您可能想要考虑数据库应如何建模用户之间的关系。看起来你想在用户和它自己之间建立一个1:N的关系(IE,一个用户可以有多个其他用户与之关联)。说实话,我知道如何实现这一目标的唯一方法是将两个UserId关联在一起的查找表。你可以在Entity Code First中这样做:

public class FriendDefinition
{
    [ForeignKey("User")]
    public int UserId { get; set; }
    [ForeignKey("Friend")]
    public int FriendUserId { get; set; }

    public virtual User User { get; set; }
    public virtual User Friend { get; set; }
}

然后您可以像这样更新您的User类:

public class User
{
    public int UserId { get; set; }
    public String Name { get; set; }
    public int Age { get; set; }
    public String City { get; set; }
    public String Country { get; set; }
    public String Email { get; set; }
    public String InterestIn { get; set; }

    public virtual ICollection<FriendDefinition> Friends { get; set; }
}

最后,您现在可以按如下方式使用它:

var user = db.context.Users.First();
var firstFriend = user.Friends.First().Friend;

它有点笨重,但我认为这符合你的目的。代码优先是一个很好的工具,但你仍然必须概念化数据如何实际存储在数据库中以模拟你需要的东西。

答案 4 :(得分:-1)

您应该使用Create的{​​{1}}方法 - 它会为您提供完全初始化的代理。

DbSet

var user1 = context.Users.Create(); user1.Name = "User1"; user1.Age = 25; user1.City = "City1"; user1.Country = "Country1"; user1.Email = "email_1@email.com"; user1.InterestIn = "User1's interests"; //.... context.Users.Add(user1); 这样的硬编码很难解决问题。