2

I have two classes in console application.

Person:

 class Person {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public virtual ICollection<Order> Orders { get; set; }

        public override string ToString() {
            return Id + " " + FirstName + " " + LastName; 
        }
    }

and Order:

 class Order {
        //Id to domyslna nazwa na identyfikator -> klucz glowny
        public int Id { get; set; }
        public int Total { get; set; }
        public virtual Person Owner { get; set; }

        public override string ToString() {
            return Id + " " + Total;
        }
    }

My dbContext is:

    class ApplicationDbContext : DbContext {
        public DbSet<Person> Persons { get; set; }
        public DbSet<Order> Orders { get; set; }
        public ApplicationDbContext()
            : base("Olek2") {
                Configuration.LazyLoadingEnabled = true;
                Configuration.ProxyCreationEnabled = true;

        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder) {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        }
       public static ApplicationDbContext Create() {
        return new ApplicationDbContext();

      }
}

I generated fresh database by update-database.

In Main method I try to fetch Person and then access person's orders but I get null pointer exception on p1.Orders.Count(). I made properties virtual, also enabled lazy loading, i don't know where the problem is.

    static void Main(string[] args) {
        ApplicationDbContext context = new ApplicationDbContext(); 
        Person p1 = context.Persons.Find(1);
        Console.WriteLine(p1); // THIS WORKS
        Console.WriteLine(context.Orders.Count()); //THIS IS 1
        Console.WriteLine(p1.Orders.Count()); //EXCEPTION HERE 
        Console.ReadKey();
    }

Also my Seed method:

protected override void Seed(ApplicationDbContext context) {
        Person person = null;

        if (!context.Persons.Any()) {
             person = new Person() {  FirstName = "Alaaaaa", LastName = "Kowlaska" };
            context.Persons.AddOrUpdate(person);
        }
        if (!context.Orders.Any()) {
            context.Orders.AddOrUpdate(new Order() { Total = 100, Owner = person });
        }

        context.SaveChanges();
    }

EDIT:

I got a hunch that it has something to do with public static ApplicationDbContext Create() has 0 references (VS2013 indicates that) in my console application(which fails).

In my ASP.NET-MVC(works fine) it was referenced by:

 public void ConfigureAuth(IAppBuilder app)
        {
            // Configure the db context and user manager to use a single instance per request
            app.CreatePerOwinContext(ApplicationDbContext.Create);

        ....
       } 

EDIT2 I had a hunch it does not seem to be connected anyhow.

haim770
  • 48,394
  • 7
  • 105
  • 133
Yoda
  • 17,363
  • 67
  • 204
  • 344
  • Have you tried adding an `include` clause for orders? – Peter Smith Apr 12 '15 at 10:42
  • @PeterSmith I never had to use them. In other bigger project(ASP.NET-MVC 5) I've made more than 10 model classes with tens of associations, enabled lazyloading and used virtual keyword and I never encountered this problem. What should I change in code I posted? I don't understand reasons of the exception becuase EF should automatically load `order` when I ask for it -> this idea which stands for lazy loading. – Yoda Apr 12 '15 at 17:21
  • Have you checked what's in the database? Your seed method will add an Order with a null Owner if a Person already existed. That said, Person.Orders should be empty rather than null in this case. – Charles Mager Apr 12 '15 at 17:38
  • In your `Main` method, check your `context.Persons.Local`. Does it already have `Person` with id=1 before you're calling `Find(1)`? – haim770 Apr 12 '15 at 17:43
  • @haim770`context.Persons.Local.Count()` returns 0 so it does not `context.Persons.Count()` returns 1 and I have only one person in the database. – Yoda Apr 12 '15 at 18:03
  • You'll also want the DBSet collections to be virtual, i.e, `public virtual DbSet Persons { get; set; }`. – DWright Apr 12 '15 at 18:07
  • 1
    Are your `Person` and `Order` classes marked as `public`? – haim770 Apr 12 '15 at 18:33
  • @haim770 NO :D That was the problem. Why they have to be public to make lazy loading work? Thank you! Please post an answer I'll accept it. Also do the `DbSets` have be `virtual` as @Dwright states? It works great without `virtual` keyword, like this: `public DbSet Persons { get; set; }` – Yoda Apr 12 '15 at 18:35
  • @Yoda, maybe they don't *have* to be virtual, but that's the way EF itself generates them, if it autogenerates this kind of stuff. – DWright Apr 12 '15 at 18:58
  • @DWright I always use code first approach, I guess(after reading your comment)that it generates it virtual when we use schema or database first approach? Or is there a 3rd option to generate `DbSets` implicitly? – Yoda Apr 12 '15 at 19:03
  • Right that's how it does it non-code first, so probably a good practice in code first to. – DWright Apr 12 '15 at 19:12
  • 1
    @Yoda, There is no reason to mark `DbSet` properties as `virtual` unless you're going to override them in some derived type. – haim770 Apr 12 '15 at 19:23

1 Answers1

2

Since your entities are not marked with the public modifier (which means by default the compiler will make them internal), EF will not be able to generate proxies for the entity type.

Looking at the source code of EntityProxyFactory (line 577):

private static bool CanProxyType(EntityType ospaceEntityType)
{
    TypeAttributes access = ospaceEntityType.ClrType.Attributes & TypeAttributes.VisibilityMask;

    ConstructorInfo ctor = ospaceEntityType.ClrType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, null, Type.EmptyTypes, null);
    bool accessableCtor = ctor != null && (((ctor.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public) ||
                                          ((ctor.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Family) ||
                                          ((ctor.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.FamORAssem));

    return (!(ospaceEntityType.Abstract ||
             ospaceEntityType.ClrType.IsSealed ||
             typeof(IEntityWithRelationships).IsAssignableFrom(ospaceEntityType.ClrType) ||
             !accessableCtor) &&
             access == TypeAttributes.Public);
}

You can clearly notice the last line of CanProxyType checking whether the entity type is indeed public.

Now, since you don't initialize Person.Orders (in the constructor or anywhere else) and no proxy is involved to intercept the call to Orders (initialize it and detect the association to Order.Person), you end up with Orders being null and NRE is thrown.

haim770
  • 48,394
  • 7
  • 105
  • 133
  • Great answer. If I initilized Person.Orders in the constructor of `ApplicationDbContext` then it would also work? – Yoda Apr 12 '15 at 19:12
  • 1
    @Yoda, Partially. You would simply get an empty `List` (or `HashSet`) with no related `Order` in it. It's only the Proxy that is able to detect that relationship for you and fill the data in it. See http://stackoverflow.com/a/20773057 for more info – haim770 Apr 12 '15 at 19:17