dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.76k stars 3.18k forks source link

Include :: NRE for some multi-level include queries #1363

Closed maumar closed 9 years ago

maumar commented 9 years ago

Consider the following:

    public class City
    {
        // non-integer key with not conventional name
        public string Name { get; set; }
        public string Location { get; set; }
    }

    public class CogTag
    {
        // auto generated key (identity for now)
        public Guid Id { get; set; }
        public string Note { get; set; }

        public string GearNickName { get; set; }
        public int? GearSquadId { get; set; }
        public virtual Gear Gear { get; set; }
    }

    public class Gear
    {
        public Gear()
        {
            Weapons = new List<Weapon>();
            Reports = new List<Gear>();
        }

        // composite key
        public string Nickname { get; set; }
        public int SquadId { get; set; }

        public string FullName { get; set; }

        public string CityOrBirthName { get; set; }
        public virtual City CityOfBirth { get; set; }

        public MilitaryRank Rank { get; set; }

        public virtual CogTag Tag { get; set; }
        public virtual Squad Squad { get; set; }

        // TODO: make this many to many - not supported at the moment
        public virtual ICollection<Weapon> Weapons { get; set; }

        public string LeaderNickname { get; set; }
        public int LeaderSquadId { get; set; }

        // 1 - many self reference
        public virtual ICollection<Gear> Reports { get; set; }
    }

    public enum MilitaryRank
    {
        Private,
        Corporal,
        Sergeant,
        Lieutenant,
        Captain,
        Major,
        Colonel,
        General,
    }

    public class Squad
    {
        public Squad()
        {
            Members = new List<Gear>();
        }

        // non-auto generated key
        public int Id { get; set; }
        public string Name { get; set; }

        // auto-generated non-key (sequence)
        public int InternalNumber { get; set; }

        public virtual ICollection<Gear> Members { get; set; }
    }

    public class Weapon
    {
        // auto generated key (sequence) TODO: make nullable when issue #478 is fixed
        public int Id { get; set; }
        public string Name { get; set; }

        // 1 - 1 self reference
        public int? SynergyWithId { get; set; }
        public virtual Weapon SynergyWith { get; set; }

        public string OwnerNickname { get; set; }
        public int OwnerSquadId { get; set; }
        public Gear Owner { get; set; }
    }

    public class GearsOfWarContext : DbContext
    {
        public DbSet<Gear> Gears { get; set; }
        public DbSet<Squad> Squads { get; set; }
        public DbSet<CogTag> Tags { get; set; }
        public DbSet<Weapon> Weapons { get; set; }
        public DbSet<City> Cities { get; set; }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<City>().Key(c => c.Name);

            builder.Entity<Gear>(b =>
            {
                b.Key(g => new { g.Nickname, g.SquadId });
                b.ManyToOne(g => g.CityOfBirth).ForeignKey(g => g.CityOrBirthName);
                b.OneToMany(g => g.Reports).ForeignKey(g => new { g.LeaderNickname, g.LeaderSquadId });
                b.OneToOne(g => g.Tag, t => t.Gear).ForeignKey<CogTag>(t => new { t.GearNickName, t.GearSquadId });
            });

            builder.Entity<CogTag>(b =>
            {
                b.Key(t => t.Id);
                b.Property(t => t.Id).GenerateValueOnAdd();
            });

            builder.Entity<Squad>(b =>
            {
                b.Key(s => s.Id);
                b.OneToMany(s => s.Members, g => g.Squad).ForeignKey(g => g.SquadId);
                b.Property(t => t.Id).GenerateValueOnAdd();
            });

            builder.Entity<Weapon>(b =>
            {
                b.OneToOne(w => w.SynergyWith).ForeignKey<Weapon>(w => w.SynergyWithId);
                b.ManyToOne(w => w.Owner, g => g.Weapons).ForeignKey(w => new { w.OwnerNickname, w.OwnerSquadId });
            });
        }

        protected override void OnConfiguring(DbContextOptions options)
        {
            options.UseSqlServer(new SqlConnectionStringBuilder { DataSource = ".", InitialCatalog = "MultiLevelIncludeBashDb", MultipleActiveResultSets = true, IntegratedSecurity = true }.ToString());
        }
    }

    public class GearsOfWarModelInitializer
    {
        public static void SeedAsync(GearsOfWarContext context)
        {
            // TODO: only delete if model has changed
            context.Database.EnsureDeleted();
            if (context.Database.EnsureCreated())
            {
                var deltaSquad = new Squad
                {
                    Name = "Delta",
                    Members = new List<Gear>(),
                };

                context.Squads.Add(deltaSquad);
                context.SaveChanges();

                var kiloSquad = new Squad
                {
                    Name = "Kilo",
                    Members = new List<Gear>(),
                };

                context.Squads.Add(kiloSquad);
                context.SaveChanges();

                var jacinto = new City
                {
                    Location = "Jacinto's location",
                    Name = "Jacinto",
                };

                var ephyra = new City
                {
                    Location = "Ephyra's location",
                    Name = "Ephyra",
                };

                var hanover = new City
                {
                    Location = "Hanover's location",
                    Name = "Hanover",
                };

                context.Cities.Add(jacinto);
                context.Cities.Add(ephyra);
                context.Cities.Add(hanover);

                context.SaveChanges();

                var marcusLancer = new Weapon
                {
                    Name = "Marcus' Lancer",
                };

                var marcusGnasher = new Weapon
                {
                    Name = "Marcus' Gnasher",
                    SynergyWith = marcusLancer,
                };

                var domsHammerburst = new Weapon
                {
                    Name = "Dom's Hammerburst",
                };

                var domsGnasher = new Weapon
                {
                    Name = "Dom's Gnasher",
                };

                var colesGnasher = new Weapon
                {
                    Name = "Cole's Gnasher",
                };

                var colesMulcher = new Weapon
                {
                    Name = "Cole's Mulcher",
                };

                var bairdsLancer = new Weapon
                {
                    Name = "Baird's Lancer",
                };

                var bairdsGnasher = new Weapon
                {
                    Name = "Baird's Gnasher",
                };

                var paduksMarkza = new Weapon
                {
                    Name = "Paduk's Markza",
                };

                context.Weapons.Add(marcusLancer);
                context.Weapons.Add(marcusGnasher);
                context.Weapons.Add(domsHammerburst);
                context.Weapons.Add(domsGnasher);
                context.Weapons.Add(colesGnasher);
                context.Weapons.Add(colesMulcher);
                context.Weapons.Add(bairdsLancer);
                context.Weapons.Add(bairdsGnasher);
                context.Weapons.Add(paduksMarkza);
                context.SaveChanges();

                var marcusTag = new CogTag
                {
                    Id = Guid.NewGuid(),
                    Note = "Marcus's Tag",
                };

                var domsTag = new CogTag
                {
                    Id = Guid.NewGuid(),
                    Note = "Dom's Tag",
                };

                var colesTag = new CogTag
                {
                    Id = Guid.NewGuid(),
                    Note = "Cole's Tag",
                };

                var bairdsTag = new CogTag
                {
                    Id = Guid.NewGuid(),
                    Note = "Bairds's Tag",
                };

                var paduksTag = new CogTag
                {
                    Id = Guid.NewGuid(),
                    Note = "Paduk's Tag",
                };

                var kiaTag = new CogTag
                {
                    Id = Guid.NewGuid(),
                    Note = "K.I.A.",
                };

                context.Tags.Add(marcusTag);
                context.Tags.Add(domsTag);
                context.Tags.Add(colesTag);
                context.Tags.Add(bairdsTag);
                context.Tags.Add(paduksTag);
                context.Tags.Add(kiaTag);
                context.SaveChanges();

                var dom = new Gear
                {
                    Nickname = "Dom",
                    FullName = "Dominic Santiago",
                    SquadId = deltaSquad.Id,
                    Rank = MilitaryRank.Corporal,
                    CityOrBirthName = ephyra.Name,
                    Tag = domsTag,
                    Reports = new List<Gear>(),
                    Weapons = new List<Weapon> { domsHammerburst, domsGnasher }
                };

                var cole = new Gear
                {
                    Nickname = "Cole Train",
                    FullName = "Augustus Cole",
                    SquadId = deltaSquad.Id,
                    Rank = MilitaryRank.Private,
                    CityOrBirthName = hanover.Name,
                    Tag = colesTag,
                    Reports = new List<Gear>(),
                    Weapons = new List<Weapon> { colesGnasher, colesMulcher }
                };

                var paduk = new Gear
                {
                    Nickname = "Paduk",
                    FullName = "Garron Paduk",
                    SquadId = kiloSquad.Id,
                    Rank = MilitaryRank.Private,
                    Tag = paduksTag,
                    Reports = new List<Gear>(),
                    Weapons = new List<Weapon> { paduksMarkza },
                };

                var baird = new Gear
                {
                    Nickname = "Baird",
                    FullName = "Damon Baird",
                    SquadId = deltaSquad.Id,
                    Rank = MilitaryRank.Corporal,
                    Tag = bairdsTag,
                    Reports = new List<Gear>() { paduk },
                    Weapons = new List<Weapon> { bairdsLancer, bairdsGnasher }
                };

                var marcus = new Gear
                {
                    Nickname = "Marcus",
                    FullName = "Marcus Fenix",
                    SquadId = deltaSquad.Id,
                    Rank = MilitaryRank.Sergeant,
                    CityOrBirthName = jacinto.Name,
                    Tag = marcusTag,
                    Reports = new List<Gear>() { dom, cole, baird },
                    Weapons = new List<Weapon> { marcusLancer, marcusGnasher },
                };

                context.Gears.Add(marcus);
                context.Gears.Add(dom);
                context.Gears.Add(cole);
                context.Gears.Add(baird);
                context.Gears.Add(paduk);

                context.SaveChanges();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using (var ctx = new GearsOfWarContext())
            {
                GearsOfWarModelInitializer.SeedAsync(ctx);
            }

            using (var ctx = new GearsOfWarContext())
            {
                var query = ctx.Weapons.Include(w => w.SynergyWith.Owner.Tag.Gear.Reports);
                var result = query.ToList();
            }
        }
    }

throws:

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object. at lambda_method(Closure , Weapon ) at Microsoft.Data.Entity.Relational.Query.QueryMethodProvider.<_IncludeCollection>d__01.MoveNext() in D:\k\EntityFramework\src\EntityFramework.Relational\Qu ery\QueryMethodProvider.cs:line 58 at Microsoft.Data.Entity.Query.LinqOperatorProvider.<_TrackEntities>d__02.MoveNext() in D:\k\EntityFramework\src\EntityFramework.Core\Query\LinqOperatorProvider.cs:line 29 at Microsoft.Data.Entity.Query.EntityQueryExecutor.EnumerableExceptionInterceptor1.EnumeratorExceptionInterceptor.MoveNext() in D:\k\EntityFramework\src\EntityFramework.Core\Query\EntityQueryExecutor.cs:line 185 at System.Collections.Generic.List1..ctor(IEnumerable`1 collection)

maumar commented 9 years ago

this works now, most likely fixed by multi-level include refactoring