Search code examples
entity-frameworkef-code-firstcode-firstentity-framework-5

My collections / model look fine after saving, but when loading entity I do not get the data back out. What am I doing wrong?


Here is my source code for my model:

public class User
{
    public User()
    {
        GUID = Guid.NewGuid();
        Account = new Account();
        Location = new Location();
    }

    public long UserID { get; set; }
    public Guid GUID { get; set; }
    public string DisplayName { get; set; }
    public Location Location { get; set; }
    public virtual Account Account { get; set; }
}

public class UserConfiguration : EntityTypeConfiguration<User>
{
    public UserConfiguration()
    {
        HasKey(x => x.UserID);
    }
}

[ComplexType]
public class Location
{
    [MaxLength(2)]
    public string CountryCode { get; set; }
    [MaxLength(2)]
    public string StateCode { get; set; }
    public string City { get; set; }
}

public class Account
{
    public Account()
    {
        if (EmailAddresses == null) EmailAddresses = new Collection<EmailAddress>();
    }

    [ForeignKey("User")]
    public long AccountID { get; set; }

    public ICollection<EmailAddress> EmailAddresses { get; set; }

    public virtual User User { get; set; }
}

public class AccountConfiguration : EntityTypeConfiguration<Account>
{
    public AccountConfiguration()
    {
        HasKey(x => x.AccountID);
        HasMany(x => x.EmailAddresses).WithRequired(x => x.Account);
    }
}

public class EmailAddress
{
    [Key]
    public string Email { get; set; }

    public EmailTypes Type { get; set; }

    public long AccountID { get; set; }
    public virtual Account Account { get; set; }
}

public class EmailAddressConfiguration : EntityTypeConfiguration<EmailAddress>
{
    public EmailAddressConfiguration()
    {
        HasKey(x => x.Email);
        HasRequired(x => x.Account).WithMany(x => x.EmailAddresses).HasForeignKey(x => x.AccountID);
    }
}

And here is my Entity Class:

public class MyEntities : DbContext
{
    public MyEntities()
    {
        Database.SetInitializer<MyEntities>(new DropCreateDatabaseAlways<MyEntities>());
    }

    public DbSet<User> Users { get; set; }
    public DbSet<Account> Accounts { get; set; }
    public DbSet<EmailAddress> EmailAddresses { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new UserConfiguration());
        modelBuilder.Configurations.Add(new AccountConfiguration());
        modelBuilder.Configurations.Add(new EmailAddressConfiguration());

        base.OnModelCreating(modelBuilder);
    }
}

And finally my code that runs in a test console application:

static void Main(string[] args)
    {
        var id = CreateUser();

        using (MyEntities db = new MyEntities())
        {
            var a = db.Users.Find(id);
            var b = a.Account.EmailAddresses;
            var c = db.Accounts.Find(id);
            var d = db.EmailAddresses.Where(x => x.Account.AccountID == id).ToList();
        }

    }

    private static long CreateUser()
    {
        using (MyEntities db = new MyEntities())
        {
            var u = new User();
            u.DisplayName = "TEST";

            u.Location.CountryCode = "US";
            u.Location.StateCode = "HI";
            u.Location.City = "Kauai";

            EmailAddress e = new EmailAddress();
            e.Email = DateTime.UtcNow.Ticks + "@microsoft.com";
            e.Type = EmailTypes.Current;

            u.Account.EmailAddresses.Add(e);

            db.Users.Add(u);

            var cnt = db.SaveChanges();
            // Here I get a return of the 4 entities saved, and my model looks correct.

            return u.UserID;
        }
    }

Once the model was saved (CreateUser), I was able to navigate the model and everything looked perfect.

The issue arises when I try to pull the data back out.
My variables:

a -- navigating to email adderess shows 0 records.

b -- this too shows 0 records in the collection.

c -- navigating to email adderess shows 0 records.

d -- here I can get email addresses (but not by navigating the model)


Solution

  • Your test code to access the navigation properties relies on lazy loading. But your Account.EmailAddresses collection is not marked as virtual:

    public ICollection<EmailAddress> EmailAddresses { get; set; }
    

    Navigation properties must be virtual (like your User.Account property) in order to make lazy loading possible.

    As a side note: I recommend to remove the instantiation of the Account navigation property...

    Account = new Account();
    

    ...from the User constructor. This is a known source for trouble:

    What would cause the Entity Framework to save an unloaded (but lazy loadable) reference over existing data?

    EF 4.1 Code First: Why is EF not setting this navigation property?

    Instantiating Location is fine because it's a complex type and not a navigation property.