EF 6 - One To Many Mapping Always Null
Asked Answered
F

5

8

I can't seem to resolve my mapping issue; The relationship is one user may have many venues, A venue must have a user.

My venue class looks like:

public class Venue : BaseObject, IBaseObject
{
    [Required]
    public virtual User Owner { get; set; }

    [Required]
    [MaxLength(50)]
    public string Name { get; set; }
}

My User class looks like:

public class User : BaseObject, IBaseObject
{

    [Required]
    public string Name { get; set; }

    [Required]
    [DisplayName("Email")]
    public string EmailAddress { get; set; }

    [Required]
    public string Password { get; set; }

    public bool Active { get; set; }


    public virtual ICollection<Venue> Venues { get; set; } 
}

DbContextClass as requested

 public class SystemContext : DbContext, IDbContext
{
    public SystemContext() :
        base("SystemContext")
    {
        Database.SetInitializer<SystemContext>(null);
        Configuration.ProxyCreationEnabled = false;
        Configuration.LazyLoadingEnabled = true;
    }


    public SystemContext(string connectionstringname = "SystemContext") :
        base(connectionstringname)
    {
        Database.SetInitializer<SystemContext>(null);
        Configuration.ProxyCreationEnabled = false;


    }
    public new IDbSet<T> Set<T>() where T : class
    {
        return base.Set<T>();
    }


    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<User>().HasMany(x=>x.Venues);
    }



    public DbSet<PublicQueries> PublicQueries { get; set; }
    public DbSet<Venue> Venues { get; set; }
    public DbSet<User> Users { get; set; }
}

When I load the User class from the database Venues always seems to be null?

I've done something similar before but can't remember how I resoled this.

Thanks

Fifty answered 23/2, 2014 at 15:10 Comment(11)
Did you try using the Include method?Marseillaise
possible duplicate of One-to many relationship not working - Entity FrameworkAnatola
Is there no way it should do it automaticly ?Fifty
You are positive your database has venues for the user you are specifying? Are you seeding test data improperly?Mirisola
Yes one user and one venue and it still not showing. Is this enough to make lazy load happen?Fifty
The question that it's possible duplicated is relevant, as I want to use the Virtual property to allow lazy loadingFifty
Seams to me your Venue class is missing the User foreign key mapping.Unconventionality
Can you so me how yo add itFifty
@PeterKiss EF is usually smart enough to figure that out for itself (depending on how the configuration is done). I had pretty much this exact problem last week, and it turned out to be down to lazy loading. Worth checking both, though.Mood
This shouldn't a lazy loading issue. Can you show us your DbContext Class, and also, how you initialize User / Venues. For example, when creating a new User, the default constructor can not be used. You need a constructor that initializes the Collection: this.Venues = new Collection<Venue>();Postfix
Db Context class addedFifty
M
13

Pretty sure this is a lazy-load issue. If you load an object with navigation properties such as your ICollection<Venues> then they won't be included by default, because they might have more navigation properties linking to more objects, which may have more navigation properties... and before you know it you're loading half the database. This is a particular problem when you've disposed of the context the object came out of by the time you go to access that navigation property, because then it doesn't have a database connection to load them from even if it did realise that it should be doing so.

The fix is to tell Entity Framework that you want that property to be populated, by adding .Include(u => u.Venues); when you get them from the DbSet. You'll need to include System.Data.Entity to get that particular overload of Include().

Mood answered 23/2, 2014 at 15:47 Comment(4)
No, this isn't a lazy-load issue. If he marks the property as virtual, it will lazy-load properly. Include is not necessary.Postfix
Yeah, I'm aware that it should, but this happened in my code recently, and it didn't. If you dispose the context before trying to access that property, then by the time it realises it needs that property, it has nowhere to load the property from. Therefore you have to ask it to load that property while the context is still around so that it'll be there when you try to use it.Mood
Ah, yes, you would be correct - if it disposes the context first, then lazy loading, in any form, may run into issues - I haven't personally had any, but you can most definitely be right. I think it's more of an initializing issue - the Collection needs to be initialized in a blank constructor - in my experience. Otherwise, EF won't be able to load it properly.'Postfix
The main case where this can be a problem is when you have very tight transactional data access, so your contexts have a lifespan of a handful of lines at a time (usually in short using() blocks) and once you return the object from a function (for example) the context is gone and lazy loading won't function.Mood
A
3

You venue class should also have a field called OwnerId.

You can take this link as reference as a start for lazy loading.

public class Venue : BaseObject, IBaseObject
{
    public (int/guid/or other type if you want) OwnerId{get;set;}

    [Required]
    public virtual User Owner { get; set; }

    [Required]
    [MaxLength(50)]
    public string Name { get; set; }
}

then also make sure your User class has some Id field which will then be used as foreign Key by EF

This is what I would do,

public class Venue : BaseObject, IBaseObject
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id{get;set;}

    public int OwnerId{get;set;}

    [Required]
    public virtual User Owner { get; set; }

    [Required]
    [MaxLength(50)]
    public string Name { get; set; }
}

public class User : BaseObject, IBaseObject
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id {get;set;}

    [Required]
    public string Name { get; set; }

    [Required]
    [DisplayName("Email")]
    public string EmailAddress { get; set; }

    [Required]
    public string Password { get; set; }

    public bool Active { get; set; }


    public virtual ICollection<Venue> Venues { get; set; } 
}
Arytenoid answered 23/2, 2014 at 16:32 Comment(0)
F
3
   public SystemContext() :
        base("SystemContext")
    {
        Database.SetInitializer<SystemContext>(null);
        Configuration.ProxyCreationEnabled = true;
        Configuration.LazyLoadingEnabled = true;
    }

Setting "ProxyCreationEnabled" to true within my context seemes to have solved this issue.

After reading EF 4 - Lazy Loading Without Proxies which quotes:

When using POCO entities with the built-in features of Entity Framework, proxy creation must be enabled in order to use lazy loading. So, with POCO entities, if ProxyCreationEnabled is false, then lazy loading won't happen even if LazyLoadingEnabled is set to true.

Fifty answered 24/2, 2014 at 8:48 Comment(0)
B
1

check you database,find the forginKey in Venue table,see is it can be null.
if can be null,that is 0..N,if can not be null,that is 1..N
i think you want a 1..N

1..N config with flunt api,use code like

modelBuilder.Entity<User>().HasMany(x=>x.Venues).WithRequired(x=>x.Owner);

if you has a property to save the userid,you can use

modelBuilder.Entity<User>().HasMany(x=>x.Venues).WithRequired(x=>x.Owner).HasForeignKey(x=>x.userid);

or ef will create a db field

these config User,and you can config Venues too

modelBuilder.Entity<Venues>().HasRequired(x=>x.Owner).WithMany(x=>x.Venues).HasForeignKey(x=>x....)

it do same thing

and if you want a 0..N,you can change the HasRequired/WithRequired to HasOptional/WithOptional
the db field can be null

Boz answered 24/2, 2014 at 2:52 Comment(0)
A
0

I'm still not sure why this happens (it's probably for better performance...) but you would have to load the property yourself.

So in your case, it would look like this:

// Load the user from the database
User user = await UserDataContext.Users.SingleOrDefaultAsync(u => u.Id == "UserID");

// Then load in all the venues
await UserDataContext.Entry(user).Collection(u => u.Venues).LoadAsync();

// Now you could access the venues
foreach (var venue in user.Venues) Console.WriteLine(venue);

In a one to one mapping situation, simply load the property using the Reference method:

await UserDataContext.Entry(user).Reference(u => u.Venue).LoadAsync();
Airframe answered 21/4, 2019 at 19:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.