0

I want to configure my DbContext to eager-load certain properties when calling Set<T>(). Given the following classes:

public class Thing
{
    public int Id { get; set; }
    public int OtherThingId { get; set; }
    public OtherThing OtherThing { get; set; }
}

public class OtherThing
{
    public int Id { get; set; }
}

public class MyContext : DbContext
{
    protected override void OnModelCreating( ModelBuilder modelBuilder )
    {
        modelBuilder.Entity<Thing>();
        modelBuilder.Entity<OtherThing>();
    }
}

How can I configure EF Core so that when I call:

var myThing = new MyContext().Set<Thing>().First();

then myThing.OtherThing is not null?

I know that if I have a context then I can use the Include / ThenInclude methods to eager load particular properties when I know the type of the thing I am querying for. But I want to be able to inject a generic repository interface which knows nothing about EF, something like:

public interface IRepository
{
    Task<T> GetAsync<T>( int id );
}

I want to be able to implement this interface which uses MyContext, and then configure EF separately so that using just Set<T>() will include whichever properties I have configured without calling those extension methods/making my interface API EF-specific?

There don't seem to be methods on ModelBuilder which I can use to achieve this. Essentially all I want is:

modelBuilder.Entity<Thing>().Include(x => x.OtherThing);
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
thisextendsthat
  • 1,266
  • 1
  • 9
  • 26
  • This might help: https://msdn.microsoft.com/en-us/library/jj574232%28v=vs.113%29.aspx?f=255&MSPPError=-2147217396 – Rui Jarimba Jul 14 '18 at 12:54
  • @RuiJarimba no it doesn't, in those examples you know the type of `T` and are using `DbSet` properties on the Context. I want to achieve the same effect but having configured the includes separately and then only using `Set()`. I think maybe my question is unclear. – thisextendsthat Jul 14 '18 at 12:57
  • I think I understand now what you're trying to do, but you might be complicating things a little bit. If you want to abstract the usage of DbContext etc create concrete repositories, instead of a generic one. It's much easier to use and manage because different entities have different fields and relationships, and they might even have different primary keys! Just a suggestion, though :) – Rui Jarimba Jul 14 '18 at 13:09
  • Anyway, I found something else that might work for you: https://stackoverflow.com/a/46285123/558486 – Rui Jarimba Jul 14 '18 at 13:15
  • @RuiJarimba `create concrete repositories, instead of a generic one` - that is exactly the problem, yes. My question is given that do I want a single generic repository. `different entities have different fields and relationships, and they might even have different primary keys` - why can't I assume and enforce that they all have `int` PKs and then do this typed Configuration in my `DbContext`. – thisextendsthat Jul 14 '18 at 13:15
  • @RuiJarimba that link is helpful, thank you. I guess the answer is that EF does not provide this out of the box so you must use reflection to do it yourself. – thisextendsthat Jul 14 '18 at 13:19
  • Sure, you can enforce the usage of a primary key with a particular name and type. In that case, you might want to create an interface that all entities will implement, such as `public interface IEntity { int Id { get; set; } }` – Rui Jarimba Jul 14 '18 at 13:21
  • Just to finish, I used EF a few years ago, and I wanted to do something generic like what you're trying to do. At the end I did go for the concrete repositories solution because IMO it was simpler to implement and maintain the code – Rui Jarimba Jul 14 '18 at 13:24
  • I am a bit confused, why can you not use *lazy loading* here? Do this: `public **virtual** OtherThing OtherThing { get; set; }` and configure [lazy loading](https://learn.microsoft.com/en-us/ef/core/querying/related-data). – CodingYoshi Jul 14 '18 at 14:41
  • @CodingYoshi yep, that's perfect. Bears the overhead of having to make *all* navigation properties virtual but I think that's worth it. Thank you. – thisextendsthat Jul 14 '18 at 15:04
  • By the way, your question is asking about eager loading but it is actually about lazy loading. Perhaps you do not clearly understand the terms and if I were you, I would study them. Also, if you are going to use lazy loading, study what performance implications lazy loading has. I am not saying do not use it, I am just saying it is good to know what it is doing so you can avoid any pitfalls. – CodingYoshi Jul 14 '18 at 15:14

1 Answers1

0

Like this ?

 var dbSet = DbContext.Set<T>();

    var properties = typeof(T)
                     .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                     .Where(p=> p.SetMethod !=null);

    var queryable = default(IQueryable<T>);
    properties.ForEach(p => { queryable = (queryable ?? dbSet).Include(p.Name); });

    return queryable;
Yves Israel
  • 408
  • 3
  • 5