0

I'm having a problem very similar to the ones mentioned in these questions:

Why is Entity Framework navigation property null?

Why EF navigation property return null?

The plot twist in my case is that the navigation collection properties are populated by EF, but only after I've queried DbSet<T> properties of the dependent types in the DbContext. To make my situation clearer, here's how my model is set up:

[Table(nameof(Composer))]
internal class ComposerRelationalDto : RelationdalDtoBase
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id { get; set; }

    public virtual ICollection<NameRelationalDto> LocalizedNames { get; set; } = new HashSet<NameRelationalDto>();

    public virtual ICollection<ArticleRelationalDto> Articles { get; set; } = new HashSet<ArticleRelationalDto>();
}

[Table(nameof(ComposerName))]
internal class NameRelationalDto : RelationdalDtoBase
{
    [Key]
    public long Id { get; set; }

    [Required]
    [ForeignKey(nameof(Composer))]
    public Guid Composer_Id { get; set; }

    public ComposerRelationalDto Composer { get; set; }
}

[Table(nameof(ComposerArticle))]
internal class ArticleRelationalDto : RelationdalDtoBase
{
    [Key]
    public long Id { get; set; }

    [Index]
    public Guid StorageId { get; set; }

    [Required]
    [ForeignKey(nameof(Composer))]
    public Guid Composer_Id { get; set; }

    public ComposerRelationalDto Composer { get; set; }

    [Required]
    [MaxLength(5)]
    public string Language { get; set; }
}

In the corresponding repository I filter ComposerRelationalDto objects by their name:

DbContext.Set<NameRelationalDto>().Where(nameWhereClause).GroupBy(n => n.Composer_Id).Select(group => group.FirstOrDefault().Composer)

The set of ComposerRelationalDtos has empty collections for the Articles and LocalizedNames properties, even though the data has been correctly persisted in the database. However, if I load all DTOs of type ArticleRelationalDto and NameRelationalDto in a QuickWatch while debugging, then the same filter no longer returns empty collections and all relevant objects are present in the collection properties.

What I've tried so far was to

  1. enable lazy loading and the creation of proxies explicitly

  2. configure the one-to many-relationships manually:

    modelBuilder.Entity<ComposerRelationalDto>().HasMany(c => c.LocalizedNames).WithRequired(n => n.Composer).HasForeignKey(n => n.Composer_Id);
    modelBuilder.Entity<ComposerRelationalDto>().HasMany(c => c.Articles).WithRequired(a => a.Composer).HasForeignKey(a => a.Composer_Id);
    
  3. and finally I just tried fiddling with the DbQuery<T>.Include() method DbContext.Set<ComposerRelationalDto>().Include(c => c.Articles) which unfortunately throws an ArgumentNullException from one of the internal methods it calls.

Basically, whatever fixes or workarounds I've tried haven't helped, so I must ask for more help.

Edit: I modified the dependent types' Composer property to be virtual. However, the problem persists.

After using .Select(group => group.FirstOrDefault().Composer).Include(c => c.Articles).Include(c => c.LocalizedNames) I now no longer get an ArgumentNullException (maybe I was getting the ArgumentNullException because I was initially using .Include() in a QuickWatch?), but rather a MySqlException: Unknown column 'Join2.Id' in 'field list'; the Data dictionary contains Key: "Server Error Code" Value: 1054. Also the generated SQL is ridiculously large and barely legible.

  • Hmm.. That's quite a pickle. I have some guesses, but I have to say that the fact that .Include() throws an exception seems like a big lead in what's going on. It simply shouldn't. Consider updating your question with any more relevant information in that error, if any. It would also be interesting to see what something like DbContext.Set().SelectMany(c => c.Articles).Load() would yield. – Classe Jul 03 '18 at 20:57
  • 1
    Can you include the full exception text? Having an exception /w a .Include() statement would potentially reveal something about the mapping. One detail is that the Composer properties should also be Virtual to allow EF to associate proxies. Also, is that the complete mapping? no modelBuilder for the Composer side? On a side note the suffix "Dto" is rather misleading for an entity. DTO implies data transfer which would be a basic POCO class to relay information from your domain to an external consumer (API client, View, etc.) outside of the scope of the DbContext. – Steve Py Jul 03 '18 at 21:32
  • @Classe using .Load() on the article DTO set causes the Articles property to be populated, just like it did when invoking .ToArray() – unintelligible Jul 03 '18 at 22:43
  • @StevePy I updated the question with more information. Also, the only mapping in the modelBuilder related to the Composer DTO is already included. The articles and names don't have any additional Fluent API configurations. (And I actually do have a relational DB model separate from the domain model; hence the Dto suffix is appropriate in the context of my solution.) – unintelligible Jul 03 '18 at 22:48
  • So... How exactly do you know that they're empty? With Lazy Loading enabled, EF won't perform any queries against the database until (pretty much) explicitly told to. Is there a possibility that you might actually have data in there if you, for example, call .ToList() on one of your collections? .ToList() will invoke an action on the query prepared for you and require it to execute it and return a response that it will retrieve from the database. (You probably still have some mapping errors, and the grouping seems.. Weird. But if the Load was ok, it should be out of scope) – Classe Jul 03 '18 at 22:52
  • An additional note on the SQL generated: It's not really that bad, but it isn't really optimal. Although a common strategy for distinct selections, a more proper solution considering EF might generate nasty queries would perhaps be: ctx.Set().Where(comp => ctx.Set().Select(c => c.Composer_Id).Distinct().Contains(comp.Id)); // ( .ToList() to execute immidiately ) – Classe Jul 03 '18 at 23:22
  • Agree with @StevePy. It is really misleading to suffix those with DTO and then have entity model attributes, DbSet, virtual, etc. – Steve Greene Jul 04 '18 at 03:35
  • @Classe I know the collections are empty, because an exception is thrown further down in the application workflow when the app assumes there's a name that corresponds to a given composer; when I inspect the the stack trace at the moment of the exception, I see that neither my domain entities, nor the DTOs contain anything related to names or articles. Unless, of course, I happen to load everything from a dependent DbSet in advance, but I think it's going to lead to performance issues once there's more data. – unintelligible Jul 04 '18 at 05:50
  • @unintelligible Yeah, I understand that concern. One problem with EF is that when you start worrying about performance, it's not really "plug and play" anymore. You will always have to guard against either overloading or SQL N+1 problems. I've personally stopped lazy loading because N+1 performance simply made it harder to use it in a satisfying way. It's still a great query generation tool, but I've had to sort of accept that I still have to specify which data I need to load. If interested, here's a good doc on EF performance: https://msdn.microsoft.com/en-us/library/hh949853(v=vs.113).aspx – Classe Jul 04 '18 at 17:38

1 Answers1

0

I figured it out. It was the internal access modifier on class declarations. A shame, because I really wanted to make the rest of the solution entirely database-agnostic (hence the unusual use of DTOs for code first, instead of the actual entities, as was already pointed out in the comments) and I wanted to enforce this in a strict manner.

Anyway, I played around some more with access modifiers and I could only manage restricting the DB object's visibility by making them public with internal protected constructors. Any other combination of class and ctor visibility involving internal caused the problem to reappear. No luck with InternalsVisibleTo, either.

This question - Entity Framework Code First internal class - is it possible? - seems to suggest that using an internal class shouldn't be a problem for EF, but it appears it is, after all, somewhat of a problem. If it wasn't then (Julie Lerman's answer dates back to 2011), it is now. I'm using EF 6.2.0 at the moment.