0

I'm developing my first class library that uses Entity Framework Code First as Data access layer.

I have this class:

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; }
}

And now I testing my code with a Console application:

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();
    }
}

I'm testing, so the database is empty.

This is my UserConfiguration class:

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");
            });
}

But I get a null pointer exception here user1.Friends.Add(user2); because Friends is null.

What am I doing wrong? How can I fix this problem?

VansFannel
  • 45,055
  • 107
  • 359
  • 626

5 Answers5

2

Entity Framework seems to be smart in this case. You are adding a new User to the context:

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

The entity is in Added state after that. Why should EF run a query with lazy loading to initialize the Friends collection? user1 is the principal in the relationships and because the state is Added it cannot exist yet in the database, hence there can't be any dependent in the database refering to it that could be loaded. So, EF does not try to load the collection at all (which is good in order to avoid unnecessary database roundtrips).

You could apply tricks to make it work - by attaching the new users before you add them to the context:

    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();

Now, just forget this solution again. It's nonsense to force lazy loading (= expensive database query) to create an empty collection. C# has the new operator for this:

    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();

You can also just use var user1 = new User() in this scenario where you only add new entities to the context. Creating dynamic proxies has no benefit here (unless you would set any foreign key properties to other existing entities and want to access their corresponding navigation properties after calling SaveChanges - which doesn't seem to be the case in your example).

Slauma
  • 175,098
  • 59
  • 401
  • 420
1

You must declare your "List<>" Properties like this:

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

If you don't use virtual keyword EF will not initialize collection for you. In your case, you are creating new object, use private properties to initialize it for new objects:

private ICollection<User> _friends;
public ICollection<User> Friends { 
   get { return _friends ?? (_friends = new List<User>()); }
   set { _friends = value; }
}
Roberto Conte Rosito
  • 2,080
  • 12
  • 22
0

You have to initialize the member Friends like this:

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();
}
Adel Khayata
  • 2,717
  • 10
  • 28
  • 46
0

I think that you might want to consider how the database should model the relationship between users. It seems like you want to have a 1:N relationship between users and itself (IE, one user can have multiple other users associated with it). To be honest the only way I know how to achieve this is a lookup table that associates two UserIds together. You could do this in Entity Code First like this:

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; }
}

Then you could update your User class like so:

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; }
}

Finally, you would now use this as follows:

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

It's a little clunky, but I think it would serve your purpose. Code first is an excellent tool but you still have to conceptualize how the data is actually being stored in the database to model what you need.

Michael Fox
  • 611
  • 3
  • 9
-1

You should use the Create method of DbSet - it will give you a fully initialised proxy.

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);

Hard coding things like user1.Friends = new List<User>(); is hack not a solution.

qujck
  • 14,388
  • 4
  • 45
  • 74
  • Thanks for your answer. I have test it and it doesn't work. `user1.Friends` is **null**. – VansFannel Jul 30 '13 at 15:17
  • @VansFannel Then you have an issue with proxy generation. Can you confirm you get a proxy back from the call to `Create()`? – qujck Jul 30 '13 at 15:30
  • I get this `{System.Data.Entity.DynamicProxies.User_8358C5A4514FD8DAEC815D9BD301A3B592EAF38A479C388C7BE9E9D899563CCF}`, and all the vars, Name, City, Friends, ..., are null. – VansFannel Jul 30 '13 at 15:37
  • @VansFannel it's a bit of a longshot but does adding the line `context.ChangeTracker.DetectChanges()` before the line `user1.Friends.Add(user2);` make any difference? – qujck Jul 30 '13 at 15:53
  • I believe that the mapping may be wrong and this is why EF is not populating the collection. – qujck Jul 30 '13 at 15:57
  • By the way, the database is empty. This is why the navigation property is not filled, isn't it? – VansFannel Jul 30 '13 at 18:10
  • I can tell you from the additional information that the mapping is the issue but I've not got enough experience to tell you why sorry. I suspect that the problem is around this code `HasMany(d => d.Friends).WithMany(d => d.FromWhomIsFriend)....` – qujck Jul 30 '13 at 18:35
  • But, not only Friends is null, all String vars are null also. – VansFannel Jul 30 '13 at 18:43