0

我正在开发我的第一个类库,它使用 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);因为Friendsnull

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

4

5 回答 5

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- 在您的示例中似乎并非如此)。

于 2013-07-30T21:41:32.887 回答
1

您必须像这样声明您的“List<>”属性:

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; }
}
于 2013-07-30T14:10:16.737 回答
0

您必须像这样初始化成员 Friends:

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();
}
于 2013-07-30T14:12:04.587 回答
0

我认为您可能需要考虑数据库应如何对用户之间的关系进行建模。似乎您希望在用户与其自身之间建立 1:N 的关系(即,一个用户可以有多个其他用户与之关联)。老实说,我知道如何实现这一点的唯一方法是将两个 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; }
}

然后你可以像这样更新你的用户类:

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;

这有点笨拙,但我认为它会达到你的目的。代码优先是一个很好的工具,但您仍然必须概念化数据实际上是如何存储在数据库中的,以便对您需要的内容进行建模。

于 2013-07-30T14:58:14.983 回答
-1

您应该使用Create方法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);

硬编码之类的东西user1.Friends = new List<User>();不是解决方案。

于 2013-07-30T14:37:36.490 回答