0

I have a problem with EF. May I misunderstand something.... So i have a simple model:

public class Image
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string ImgPath { get; set; }
}

public class UserExtendedInfo
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string UserEmail { get; set; }
    public Image Avatar { get; set; }
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<UserExtendedInfo>().HasRequired(r => r.AvatarPath);
}

After creating new Image object and adding it to context and save it, I can see new row in Images and UserExtendedInfoes tables . In user table I can see column Avatar_id that has id of needed row of Images table. But when I try to load UserExtendedInfo from database - avatar field is null ....

nzrytmn
  • 6,193
  • 1
  • 41
  • 38
Student
  • 1
  • 1
  • 1
  • Have you tried using public virtual Image Avatar { get; set; } instead of public Image Avatar { get; set; } ? – Conrad Oct 15 '19 at 09:27
  • @Conrad, but one more question, why it should be virtual ? – Student Oct 15 '19 at 09:30
  • 1
    It allows the Entity Framework to create a proxy around the virtual property so that the property can support lazy loading and more efficient change tracking Kindly see https://stackoverflow.com/questions/8542864/why-use-virtual-for-class-properties-in-entity-framework-model-definitions/8542939 – Conrad Oct 15 '19 at 09:32

1 Answers1

0

In entity framework, the columns of the tables are represented by the non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)

Now it depends on what you want: can two user extended infos have the same Avatar image, and if you change this Avatar image, is the image or all users with this Avatar changed?

If so, There is a one-to-many relation between Image and UserExtendedInfo: Every Images is used by zero or more UserExtendeInfos; every UserExtendedInfo has exactly one Image.

If you follow the Entity Framework Coding conventions, the following will be enough for entity framework to detect this one to many relation, and to name the columns, and to detect the primary keys and the foreign keys.

public class Image
{
    public int Id { get; set; }          // by convention will become the primary key
    public string ImgPath { get; set; }

    // every Images is used by zero or more UserExtendedInfo (one-to-many)
    public virtual ICollection<UserExtendedInfo> UserExtendedInfos {get; set;}
}

public class UserExtendedInfo
{
    public int Id { get; set; }          // by convention will become the primary key

    public string FirstName { get; set; }
    ... // other non-virtual properties

    // Every UserExtendedInfo has exactly one Image, using foreign key
    public int AvatarId {get; set;}
    public virtual Image Avatar { get; set; }
}

For completeness the DbContext:

public class MovieDbContext : DbContext
{
    public DbSet<Image> Images {get; set;}
    public DbSet<UserExtendedInfo> UserExtendedInfos {get; set;}
}

This is enough for entity framework to detect the tables, the columns in the tables and the relation between the tables. No need for data annotations nor fluent API.

There is a small deviation from the conventions:

// Every UserExtendedInfo has exactly one Image, using foreign key
public int AvatarId {get; set;}
public virtual Image Image{ get; set; }

If you really want to use Avatar as property, it might be that you might need fluent API in OnModelCreating:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // one-to-many relation between Image and UserExtendedInfo:
    modelBuilder.Entity<Image>()
                .HasMany(image => image.UserExtendedInfos)
                .WithRequired(userExtendedInfo => userExtendedInfo.Avatar)
                .HasForeignKey(userExtendedInfo => userExtendedInfo.AvatarId);

The code above says that every Image in the Image table has zero or more UserExtendedInfos. The many can be accessed using propertie Image.UserExtendedInfos. Every UserExtendedInfo belongs to exactly one Image (not zero, not two, exactly one: required). This one Image is accessed via property UserExtendedInfo.Avatar. The foreign key to the Image that the UserExtendedInfo belongs to is UserExtendedInfo.AvatarId.

Now whenever you add a new UserExtendedInfo, you can't SaveChanges without giving either the foreign key a proper value, or the Image:

var mickeyMouseImageId = myDbContext.Images
                                 .Where(image => image.Name == "Mickey Mouse)
                                 .Select(image => image.Id)
                                 .FirstOrDefault();
// TODO: check that the image really exists, so result not zero

// Add a new UserExtendedInfo that uses mickey Mouse as Avatar:
var addedUserExtendedInfo = myDbContext.UserExtendedInfos.Add(new UserExtendedInfo()
{
     // fill the foreign key:
     AvatarId = mickeyMouseId,

     ... // other properties; don't fill the primary Key in Id
});

If you want to add a new Image and UserExtendedInfo in one step:

var addedUserExtendedInfo = myDbContext.UserExtendedInfos.Add(new UserExtendedInfo()
{
    // don't fill the primary key nor the foreign key, you don't know their values yet
    // add also a new Avatar
    Avatar = new Image()
    {
        // don't fill the primary key, you don't know its value yet
        ... // other Image properties

        // no need to fill the collection of Images
    },
});

Entity framework detects that primary keys are zero, and thus the objects are added to the database. Because of the virtual properties, entity framework knows that foreign keys must be filled.

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116