简体   繁体   中英

Entity Framework - ICollection Property Always Null - getter setter troubles?

I have a .net core 2.0 project using .netcore identity' . I have a class .netcore identity' . I have a class ApplicationUser'(aka Owner) and 3 other classes of media objects Videos, Images, and Audio ("Clips") . So far all is great. where i am hitting a wall is when i try to refactor so that rather than having to hit the DB and say something like:

user=_usermanager.getuserasync(User)
images= _context.Images.Where(i=>i.i.Owner.username== user.username)

I would like to be able to access the same thing by simply saying

user.images

which would hopefully allow me to say things like :'foreach (Audio clip in User.Clips){//do the hokeypokee//}

so i modified my ApplicationUser class to include an ICollection<Type> for each of my media classes. No matter what i do however when i access user.images etc i always get null.(tried virtual nav props as well)

rather than sitting here explaining what i THINK it is ( Something im not doing with the getter and setter perhaps? ) which would be confusing i figure I just let you guys tell me what direction,I should be going in because I've been down many rabbit holes now trying to figure this out. Entity framework seems in order its just something im not doing ... i cant even figure out (if not in EF somewhere) how the setter is initially defined meaning how does it know what images belong to the user without querying the database to see which images have the fk that much the user... anyway the more i type the more i confuse myself LOL.

User Class:

public class ApplicationUser : IdentityUser, ITraits
{
    public int Age { get; set; }
    public Ethnicity Ethnicity { get; set; }
    public Color EyeColor { get; set; }
    public Color HairColor { get; set; }
    public int Height { get; set; }
    public Race Race { get; set; }
    public CuckRole CuckRole { get; set; }
    public BiologicalSex BiologicalSex { get; set; }
    public Sexuality Sexuality { get; set; }
    public Color SkinColor { get; set; }
    public int Weight { get; set; }
    public DateTime BirthDay { get; set; }
    public ICollection<Image> Images { get; set; }
    public ICollection<Audio> Clips { get; set; }
    public ICollection<Video> Videos { get; set; }

    public bool Equals(Traits other) => throw new NotImplementedException();

}

and then my media class (just one all the same just different names)

public class Audio 
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Path { get; set; }
    public ApplicationUser Owner { get; set; }



    //Constructors//
    public Audio()
    {

    }


    public Audio(string path,ApplicationUser owner)
    {
    Path = path;
    Owner = owner;
    }



}

EDIT

ok so i tried to implement the suggested code as best i could and thus far this is what i've come up with :

Media Class :

public class Audio 
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Path { get; set; }
    public ApplicationUser Owner { get; set; }
    public string OwnerId { get; set; }




    //Constructors//
    public Audio()
    {

    }


    public Audio(string path,ApplicationUser owner)
    {
    Path = path;
    Owner = owner;
    }



}

and my ApplicationUser Class now looks like this :

public class ApplicationUser : IdentityUser, ITraits
{
    public int Age { get; set; }
    public Ethnicity Ethnicity { get; set; }
    public Color EyeColor { get; set; }
    public Color HairColor { get; set; }
    public int Height { get; set; }
    public Race Race { get; set; }
    public CuckRole CuckRole { get; set; }
    public BiologicalSex BiologicalSex { get; set; }
    public Sexuality Sexuality { get; set; }
    public Color SkinColor { get; set; }
    public int Weight { get; set; }
    public DateTime BirthDay { get; set; }
    public ICollection<Image> Images { get; set; }
    public ICollection<Audio> Clips { get; set; }
    public ICollection<Video> Videos { get; set; }





    public bool Equals(Traits other) => throw new NotImplementedException();

}

and finally my DBContext :

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
    public DbSet<Cucklist.Models.Image> Images { get; set; }
    public DbSet<Cucklist.Models.Video> Videos { get; set; }
    public DbSet<Cucklist.Models.Audio> Clips { get; set; }
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<Image>().ToTable("Image")
                               .HasOne(Image => Image.Owner)
                               .WithMany(Owner => Owner.Images)
                               .HasForeignKey(Image => Image.OwnerId);
        builder.Entity<Video>().ToTable("Video")
                               .HasOne(Video => Video.Owner)
                               .WithMany(Owner => Owner.Videos)
                               .HasForeignKey(Video =>Video.OwnerId );
        builder.Entity<Audio>().ToTable("Clips")
                               .HasOne(Audio => Audio.Owner)
                               .WithMany(Owner => Owner.Clips)
                               .HasForeignKey(Audio =>Audio.OwnerId );
    }




}

sadly i still get a null when accessing user.media (image,video etc)

In your DbContext-class you can override the OnModelCreating method and configure your relations here. There are some cleaner ways, but this might be good start.

protected override void OnModelCreationg(DbModelBuilder modelBuilder)
{
    // on-to-many relation example
    modelBuilder.Entity<Audio>()
        .HasOptional(audio => audio.Owner)
        .WithMany(owner => owner.Clips)
        .HasForeignKey(audio => audio.OwnerId) // this one is missing in your example
        .WillCascadeOnDelete(false); // maybe true? depending on what should happen if the user gets deleted
}

You need to tell EF how to manage your relations - you will need a foreign key (Audio.OwnerId).

Another key-word for further investigations might by be ObjectEntityConfiguration .

Hey Guys thanks again for your help (@nilsK) - so the answer ended up being an entity framework "Thing".

Basically lazy loading isn't supported in Entity Framework Core until v2.1 which is in preview now (not sure if i missed making sure to specify it was entity framework core but i guessed that all assumed since it is a .net core project and you have to use Ef Core for a .net core project.) at any rate even with the Microsoft.EntityFrameworkCore updated and installed 2.1.0-previewfinal-2 it still doesn't work .

In order for it to "work" In Other words- not pull back a null when trying to access related entities you must also install the package Microsoft.EntityFramworkCore.Proxies.

Actually this is the easiest way(According to MS) to "enable" lazy loading in ef core; using the proxies. Although two other methods are available that are much more involved to implement

here is the link to the article that finally answered this question once and for all : https://docs.microsoft.com/en-us/ef/core/querying/related-data

so the short and skinny is once you installed the packages you have to (should for above option ) make all call to .UseLazyLoadingProxies() option in your DI container -- go to startup.cs and modify the DBContext options adding .UseLazyLoadingProixes() before the .UseSqlServer(...) and that's it....

now when you instantiate an entity with related entities they will be loaded on first access to the property... so basically It will stop returning a null!!!

otherwise you'd have to use implicit include statements etc... eager loading isnt and option but rather per query declarations...

Summary:

Step 1: Install Microsoft.EntityFrameworkCore preview 2.1.xx Step 2: Install Microsoft.EntityFrameworkCore.Proxies Step 3: Modify Services container/Startup.cs (Configure services) DBContext to include the option .UseLasyLoadingProxies So your old code might look like : services.AddDbContext(options =>

services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("LocalConnection"))

now it might look like this :

services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("LocalConnection"))
                       .UseLazyLoadingProxies());

Step 4 - Modify your DBContext Class to lasy load the related entities (Yes they must all be listed which is probably not optimal but im sure MS will address this)

builder.Entity<Image>().HasOne(u => u.ApplicationUser)
                       .WithMany(i => i.Images)
                       .HasForeignKey(u => u.ApplicationUserId)
                       .HasConstraintName
                            (
                              "FK_Image_AspNetUsers_ApplicationUserId"
                            );
builder.Entity<Video>().HasOne(u => u.ApplicationUser)
                       .WithMany(p => p.Videos)
                       .HasForeignKey(u => u.ApplicationUserId)
                       .HasConstraintName
                                 (
                                  "FK_Video_AspNetUsers_ApplicationUserId"
                                 );
builder.Entity<Audio>().HasOne(u => u.ApplicationUser)
                       .WithMany(p => p.Audios)
                       .HasForeignKey(u => u.ApplicationUserId)                                    
                       .HasConstraintName
                             (
                               "FK_Audio_AspNetUsers_ApplicationUserId"
                             );

you'll notice that im using Identity Core - in the table "AspNetUsers" there is no ApplicationUserId column so you must have the foreign key constraint in place which should already be there if you've modified your class appropriately which i will show below: Step 5 Make sure you're classes look like this -->

ApplicationUser Class

public class ApplicationUser : IdentityUser, ITraits
{




    public int Age { get; set; }
    public Ethnicity Ethnicity { get; set; }
    public Color EyeColor { get; set; }
    public Color HairColor { get; set; }
    public int Height { get; set; }
    public Race Race { get; set; }
    public CuckRole CuckRole { get; set; }
    public BiologicalSex BiologicalSex { get; set; }
    public Sexuality Sexuality { get; set; }
    public Color SkinColor { get; set; }
    public int Weight { get; set; }
    public DateTime BirthDay { get; set; }

    public virtual ICollection<Image> Images { get; set; }<--must be virtual
    public virtual ICollection<Video> Videos { get; set; }<--must be virtual
    public virtual ICollection<Audio> Audios { get; set; }<--must be virtual



    public bool Equals(Traits other)
    {
        throw new NotImplementedException();
    }
}

one of the media classes :

Images:

public class Image
{
    //Fields and Properties//
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }
    public string Path { get; set; }
    public virtual ApplicationUser ApplicationUser { get; set; }<-be virtual
    public virtual string ApplicationUserId { get; set; }<--must be virtual


    public Image()
    {

    }

    public Image(string path) => Path = path;


}

Step 6 in a view all you need to do is have a ApplicationUser and you can pull the current user using the httpcontext like so :

in your controller preferably although you can do this in the view

ApplicationUser user = await _usermanager.GetUserAsync(HttpContext.User);

then pass the user to the view as

return View(user)

Step 7

in your view make user the model is applicationuser

somewhere in your view try and access the media classes via the user object so

foreach (Audio clip in Model.Clips)
{
       <img src="@clip.path" />
}

viola !

**Edit just a helpful read about the topic and proxies etc - ef core 2-1 whats new with lazy loading

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM