3

I am loading an entity (DataLayer) from a database using Entity Framework. This entity has a reference in its model class to another entity (DataLayerStyle) as a list Styles.

This second entity also references back to the original entity (DataLayer). So, when I load the original entity, it gets loaded in the second entity (DataLayerStyle) in a recursive fashion. I don't want to do that in this particular instance because the data size that is transferred from server to client is too big and takes too long for the client to load.

I have tried using Select to only select certain parameters in the second entity's class before loading all of the original entity into a variable, but I get an error:

Lambda expression used inside Include is not valid

This is also not really the ideal solution, because each layer has a different style class, so when I tried to use Select in this way, I was only limited to the most generic version of the style class.

var list = dbContext.DataLayer.Where(x => x.LayerGroupId == groupId)
                        .Include(x => x.Permissions)
                        .Include(x => x.LayerGroup)
                        .ThenInclude(x => x.DataManager)
                        .ThenInclude(x => x.Project)
                        .Include(x => x.Styles
                        .Select(x => new { x.Visible, x.ImageURL, x.DataLayerId, x.DataLayerStyleId, x.UserId }))
                        .OrderBy(x => x.Name).AsNoTracking().ToList();

I also tried adding in AsNoTracking() but that did not help, either.

I am looking for something that would be the opposite of Include, like DoNotInclude, but that does not seem to exist.

public class DataLayer
{
    #region Properties
    public int DataLayerId { get; set; }
    public virtual LayerGroup LayerGroup { get; set; }
    public int LayerGroupId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public DateTime DateCreated { get; set; }
    public string DataLayerType { get; set; }

    public virtual DataLayerDocumentManager DocumentManager { get; set; }
    public virtual List<DataFeature> DataFeatures { get; set; } = new List<DataFeature>();
    public virtual List<DataItemSummary> DataItemSummaries { get; set; } = new List<DataItemSummary>();
    public virtual List<DataField> DataFields { get; set; } = new List<DataField>();
    public virtual List<DataLayerStyle> Styles { get; set; } = new List<DataLayerStyle>();
    public virtual List<DataLayerPermission> Permissions { get; set; } = new List<DataLayerPermission>();
    public virtual List<DataLayerTag> Tags { get; set; } = new List<DataLayerTag>();
    public virtual List<DataLayerLogEntry> LogEntries { get; set; } = new List<DataLayerLogEntry>();

    public string UserId { get; set; }
    #endregion 
}

public class DataLayerStyle
{
    #region Properties
    public int DataLayerStyleId { get; set; }
    public virtual DataLayer DataLayer { get; set; }
    public int DataLayerId { get; set; }
    public bool Visible { get; set; } = false;
    public string UserId { get; set; }
    public string ImageURL { get; set; }
    #endregion
}

var list = dbContext.DataLayer.Where(x => x.LayerGroupId == groupId)
                    .Include(x => x.Permissions)
                    .Include(x => x.LayerGroup).ThenInclude(x => x.DataManager).ThenInclude(x => x.Project)
                    .Include(x => x.Styles)
                    .OrderBy(x => x.Name).AsNoTracking().ToList();
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
dmrobotix
  • 125
  • 1
  • 10
  • Take a look at this: https://stackoverflow.com/questions/24022957/entity-framework-how-to-disable-lazy-loading-for-specific-query – fiji3300 Jun 21 '21 at 22:05

1 Answers1

4

The quick fix is to disable lazy loading for that query. I.e:

using (var dbContext = new AppDbContext())
{
    dbContext.Configuration.LazyLoadingEnabled = false;
    // dbContext.ChangeTracker.LazyLoadingEnabled = false; // For EF Core.

    var list = dbContext.DataLayer.Where(x => x.LayerGroupId == groupId)
                    .Include(x => x.Permissions)
                    .Include(x => x.LayerGroup)
                    .ThenInclude(x => x.DataManager)
                    .ThenInclude(x => x.Project)
                    .Include(x => x.Styles)
                    .OrderBy(x => x.Name).AsNoTracking().ToList();
    // ...
}

if dbContext is an injected Context or scoped outside of this call:

dbContext.Configuration.LazyLoadingEnabled = false;
// dbContext.ChangeTracker.LazyLoadingEnabled = false; // For EF Core.
var list = dbContext.DataLayer.Where(x => x.LayerGroupId == groupId) // ...

dbContext.Configuration.LazyLoadingEnabled = true;
// dbContext.ChangeTracker.LazyLoadingEnabled = true; // For EF Core.

This will avoid the recursion by leaving the unincluded references (cyclic) null.

The better solution to avoid issues like this is to avoid sending Entities and instead defining view models to send to the view. This avoids the need to worry about eager loading or tripping lazy loads, and minimizes the data sent over the wire. Typically a view does not need all of the data and much of the data from related entities can be flattened. (I.e. just need the LayerGroupId and LayerGroupName as fields in a view model, DataLayerViewModel.LayerGroupName vs. DataLayer.LayerGroup.LayerGroupName) ViewModels can be populated manually using .Select() or composed as part of the IQueryable expression /w Automapper's ProjectTo<DataLayerViewModel>() which can handle the flattening or required hierarchy of view models.

Steve Py
  • 26,149
  • 3
  • 25
  • 43
  • Thank you so much for the advice. I am going to look into this. Also, your answer is pretty much what helped me, but for some reason, dbContext.Configuration didn't work, so I had to use dbContext.ChangeTracker. If it makes to do so, maybe you could add that to your answer and then I will mark this as the best answer. Thanks!! – dmrobotix Jun 22 '21 at 01:09
  • What version of EF are you using? (I.e. EF6 or EF Core?) – Steve Py Jun 22 '21 at 02:23
  • Package Manager says 3.1.5. I inherited this code from someone else in our research group. – dmrobotix Jun 22 '21 at 03:06
  • Yeah, that would be EF Core 3.1. To disable the lazy loading would be `dbContext.ChangeTracker.LazyLoadingEnabled = false;` – Steve Py Jun 22 '21 at 03:34