Eager loading of 2 levels in entity framework using Lambda Expression - entity-framework

I am working with MS entity-framework core and I am trying to do a eager loading query. For that I have the following method:
public FieldSet GetFieldSetById(int id)
{
return _context.FieldSet.Include("FieldSetFields.Field").SingleOrDefault(fs => fs.FieldSetId == id);
}
this code works fine, but I was wondering how can I achieve it using the lambda syntax (System.Func).
So far I have this one that works as well, but does not include the "Field".
return _context.FieldSet.Include(e => e.FieldSetFields).SingleOrDefault(fs => fs.FieldSetId == id);
FieldSetFields is a list, and then I cannot call .Field. How can I achieve this?

Use ThenInclude:
return _context.FieldSet
.Include(e => e.FieldSetFields)
.ThenInclude(fsf => fsf.Field)
.SingleOrDefault(fs => fs.FieldSetId == id);

Related

EF Core: Serializing composite objects with polymorphic associations

In my design, I have a Challenge aggregate root that maintains a list of strategies that collectively determine if the challenge is complete.
There are several types of strategies that examine the challenge submission in their own way. Each type of strategy inherits from the base ChallengeFullfilmentStrategy, as shown in the diagram.
When a submission is made, I load the challenge along with its strategies with the following code:
return _dbContext.Challenges
.Where(c => c.Id == challengeId)
.Include(c => c.Solution)
.Include(c => c.FulfillmentStrategies)
.ThenInclude(s => (s as BasicMetricChecker).ClassMetricRules)
.ThenInclude(r => r.Hint)
.Include(c => c.FulfillmentStrategies)
.ThenInclude(s => (s as BasicMetricChecker).MethodMetricRules)
.ThenInclude(r => r.Hint)
.Include(c => c.FulfillmentStrategies)
.ThenInclude(s => (s as BasicNameChecker).Hint)
.FirstOrDefault();
This setup is a consequence of the polymorphism introduced by the strategy hierarchy. Recently, I've tried to add a more sophisticated strategy (ProjectChecker, marked in red on the diagram). Through an intermediate class, it introduces a composite relationship, where now a Strategy has a list of Strategies (through the SnippetStrategies class).
This change severely complicates the data model, as now the code should look something like this:
return _dbContext.Challenges
.Where(c => c.Id == challengeId)
.Include(c => c.Solution)
.Include(c => c.FulfillmentStrategies)
.ThenInclude(s => (s as BasicMetricChecker).ClassMetricRules)
.ThenInclude(r => r.Hint)
.Include(c => c.FulfillmentStrategies)
.ThenInclude(s => (s as BasicMetricChecker).MethodMetricRules)
.ThenInclude(r => r.Hint)
.Include(c => c.FulfillmentStrategies)
.ThenInclude(s => (s as BasicNameChecker).Hint)
.Include(c => c.FulfillmentStrategies)
.ThenInclude(s => (s as ProjectChecker).SnippetStrategies)
.ThenInclude(snippetStrats => snippetStrats.Strategies)
.ThenInclude(s => (s as BasicMetricChecker).MethodMetricRules)
//More code here to include the other children, not sure if this can even work.
.FirstOrDefault();
I'm not sure if I've hit a limitation of EF Core or if I'm not aware of some mechanism that can solve this issue.
If it's the former, I was considering serializing my Challenge aggregate into a JSON object (I'm using postgresql) and removing this part of the relationship model. While it makes sense from a domain perspective (I either need only the challenge header or all of it - including all the strategies etc.), my research so far has revealed that System.Text.Json suffers from the same limitation and that I'll need to write a custom JSONConverter to enable serialization of this data structure.
The cleanest option I've found so far is the use of Newtonsoft along with the JsonSubtypes library, but I wanted to check if I'm missing something that would help solve my issue without introducing these dependencies (especially since JsonSubtypes seems to be less active).
This is a static helper method I created for when I had the same problem, utilizing reflection.
Imagine it like a beefed up .Find(). It loops through all the properties of a class and loads them in the same fashion as you would with the .Include() and .ThenInclude() pattern that you follow in your code.
This does have an extra content in the first if/else block where it finds the DbContext Model from an Interface being passed in for the Type parameter. As in our case we used quite a few interfaces to classify our Model classes (figured it could be useful). This has some hefty overhead, so be careful. We use the FullFindAsync for very specific use cases, and typically try to do .Includes().
You could also add further depth searching to the foreach loop at the end if you want to dive deeper in what gets loaded. Again, this has some hefty overhead as well, so make sure to test it on large datasets.
public static async Task<object> FullFindAsync(this DbContext context, Type entityType, params object[] keyValues)
{
object entity = null;
//handle an Interface as a passed in type
if (entityType.IsInterface)
{
//get all Types of Models in the DbContext that implement the Interface
var implementedModels = context.Model.GetEntityTypes()
.Select(et => et.ClrType)
.Where(t => entityType.IsAssignableFrom(t))
.ToList();
//loop through all Models and try to find the object via the parameters
foreach (var modelType in implementedModels)
{
entity = await context.FindAsync(modelType, keyValues);
if (entity != null) break;
}
}
else
//find the object, via regular Find
entity = await context.FindAsync(entityType, keyValues);
//didnt find the object
if (entity == null) return null;
//loop through all navigation properties
foreach (var navigation in entity.GetType().GetProperties())
{
//skip NotMapped properties (to avoid context collection error)
if (context.Entry(entity).Metadata.FindNavigation(navigation.Name) == null)
continue;
//check and load reference and collection properties
if (typeof(IDatabaseObject).IsAssignableFrom(navigation.PropertyType))
context.Entry(entity).Reference(navigation.Name).Load();
if (typeof(IEnumerable<object>).IsAssignableFrom(navigation.PropertyType))
context.Entry(entity).Collection(navigation.Name).Load();
}
return entity;
}
//***USAGES***
var fullModel = await _dbContext.FullFindAsync(typeof(MyModelObject), model.Id);
var correctModel = await _dbContext.FullFindAsync(typeof(IModelInterface), someRandomId);

What is the difference between creating a new object inside select LINQ clause and inside a method

I have an entity class which is mapped to an SQL table:
public class EntityItem {
public virtual ICollection<EntityItem2> SomeItems { get; set; }
}
And I have the following two snippets:
var items = _repository.Table.Where(x => x.Id == id)
.Select(x => new ItemModel {
Items = x.SomeItems.Select(y => new SomeItem { //mapping is here...}).ToList()
});
And
var items = _repository.Table.Where(x => x.Id == id).Select(x => someModelMapper.BuildModel(x));
//inside a mapper
public ItemModel BuildModel(EntityType entity){
var model = new ItemModel();
model.Items = entity.SomeItems.Select(x => anotherMapper.BuildModel(x));
return model;
}
As a result, I am getting different SQL queries in both cases. Moreover, the second snippet is working much slower than the first one. As I can see in SQL profiler the second snipper is generating many SQL queries.
So my questions:
Why is that happening?
How to create new objects like in the second snippet but to avoid
lots of SQL queries?
The likely reason you are seeing a difference in performance is due to EF Core materializing the query prematurely. When a Linq statement is compiled, EF attempts to translate it into SQL. If you call a function within the expression, EF6 would have raised an exception to the effect that the method cannot be converted to SQL. EF Core tries to be clever, and when it encounters a method it cannot convert, it executes the query up to the point it could get to, and then continues to execute the rest as Linq2Object where you method can run. IMO this is a pretty stupid feature and represents a huge performance landmine, and while it's fine to offer it as a possible option, it should be disabled by default.
You're probably seeing extra queries due to lazy loading after the main query runs, to populate the view models in the mapping method.
For instance if I execute:
var results = context.Parents.Select(x => new ParentViewModel
{
ParentId = x.ParentId,
Name = x.Name,
OldestChildName = x.Children.OrderByDescending(c => c.BirthDate).Select(c => c.Name).FirstOrDefault() ?? "No Child"
}).Single(x => x.ParentId == parentId);
That would execute as one statement. Calling a method to populate the view model:
var results = context.Parents
.Select(x => buildParentViewModel(x))
.Single(x => x.ParentId == parentId);
would execute something like:
var results = context.Parents
.ToList()
.Select(x => new ParentViewModel
{
ParentId = x.ParentId,
Name = x.Name,
OldestChildName = x.Children.OrderByDescending(c => c.BirthDate).Select(c =>
c.Name).FirstOrDefault() ?? "No Child"
}).Single(x => x.ParentId == parentId);
at worst or:
var results = context.Parents
.Where(x => x.ParentId == parentId)
.ToList()
.Select(x => new ParentViewModel
{
ParentId = x.ParentId,
Name = x.Name,
OldestChildName = x.Children.OrderByDescending(c => c.BirthDate).Select(c =>
c.Name).FirstOrDefault() ?? "No Child"
}).Single();
... at best. These are due to the Extra .ToList() call prior to the Select which is roughly what the premature execution will do automatically. The issue with these queries compared to the first one is that when it comes to loading the child's name. In the first query, the SQL generated pulls the parent and related child's details in one query. In the alternative cases the query will execute to pull the parent's details, but getting the child details will constitute a lazy load call to get more details since that will be executed as Linq2Object.
The solution would be to use Automapper and it's built in ProjectTo method to populate your view model. This will place the mapping code in automatically so that it works like the first scenario without you needing to write out all of the mapping code.

Entity Framework eager loading in many nested entities

I have six classes as shown below -
Class A{
int ValA;
ICollection<B> AllBs;
}
Class B{
int ValB;
ICollection<C> AllCs;
}
Class C{
int ValC;
ICollection<D> AllDs;
}
Class D{
int ValD;
ICollection<E> AllEs;
}
Class E{
int ValE;
ICollection<F> AllFs;
}
Class F{
int ValF;
}
I have to eager load an A entity with specific id and I want the result must load all the nested collections eagerly.
I wrote the following code to achieve the same -
var entity = _context.A.Where(a => a.Id == 1)
.Include(a => a.AllBs
.Select(b => b.AllCs
.Select(c => c.AllDs
.Select(d => d.AllEs
.Select(e => e.AllFs)))))
.FirstOrDefault();
Have I written the include statements correctly ? All the nested collections are not getting loaded properly. They are getting loaded upto AllBs, AllCs and further collections are not loaded.
Kindly help.
If this is EF Core you will want to use
_context.A
.Include(a => a.AllBs)
.ThenInclude(b => b.AllCs)
.ThenInclude(c => c.AllDs)
.ThenInclude(d => d.AllEs)
.ThenInclude(e => e.AllFs)
.Single(a => a.Id == 1)
Intellisense may give you a bit of grief diving through collections but it does work through.
For EF6, your statement should work, so if not I would look specifically at your mappings to ensure your FKs are resolving correctly. I'd also remove the .Where() & .FirstOrDefault() and instead use a .Single() at the end. It should not matter in the query generation, but Include statements are best placed before any filtering. Use an SQL Profiler to capture the SQL being sent to the server to see what tables are being requested and how they are joining. This might highlight some FK resolution errors.

Loop/reflect through all properties in all EF Models to set Column Type

My client has a standard of storing SQL Server decimals with a decimal(13,4) specification. As a result, in a very large and still-growing schema, I have nearly a hundred statements like these:
builder.Entity<MyObject>()
.Property(x => x.MyField1)
.ForSqlServerHasColumnType("decimal(13,4)");
builder.Entity<MyObject>()
.Property(x => x.MyField2)
.ForSqlServerHasColumnType("decimal(13,4)");
builder.Entity<MyObject2>()
.Property(x => x.MyField1)
.ForSqlServerHasColumnType("decimal(13,4)");
If there is a feature where I can tell EF directly that all decimals should be decimal(13,4) by default, I would like to use that. If not, can I use reflection to loop through every object/property in the model so I can do this in a couple statements?
Something like:
foreach(var efObj in EntityFrameWorkObjects)
{
foreach (var objProperty in efObj)
{
if (objProperty is decimal || objProperty is decimal?)
{
builder.Entity<efObj>()
.Property(x => x.efObj)
.ForSqlServerHasColumnType("decimal(13,4)");
}
}
}
Reflection seems like a great way to go, because then I can implement some of our other conventions where, if an object has a Name and Description, the Name is required and limited to 256 chars.
Update:
I followed the link in Ivan's comment and adapted it to this, which works for me:
foreach (var p in builder.Model
.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p =>
p.ClrType == typeof(decimal) ||
p.ClrType == typeof(decimal?)))
{
p.SqlServer().ColumnType = "decimal(13,4)";
}
Soon after, he provided a full answer, which I changed slightly to work with both decimal and nullable decimal:
foreach (var pb in builder.Model
.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p =>
p.ClrType == typeof(decimal) ||
p.ClrType == typeof(decimal?))
.Select(p =>
builder.Entity(p.DeclaringEntityType.ClrType)
.Property(p.Name)))
{
pb.ForSqlServerHasColumnType("decimal(13,4)");
}
Both approaches work!
Update 2: I had to have my objects declared as DbSet<> in the context for the above to work. This didn't seem to be required when I was setting properties line by line.
In EF Core v1.1.0 you can use something like this:
foreach (var pb in modelBuilder.Model
.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?))
.Select(p => modelBuilder.Entity(p.DeclaringEntityType.ClrType).Property(p.Name)))
{
pb.ForSqlServerHasColumnType("decimal(13,4)");
}
Update (EF Core 2.x): Starting from EF Core 2.0, the model is built separately for each database provider, so HasAbcXyz methods are replaced with common HasXyz. The updated code (which also skips the explicitly configured properties) looks like this:
foreach (var property in modelBuilder.Model.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
{
if (property.Relational().ColumnType == null)
property.Relational().ColumnType = "decimal(13,4)";
}
Update (EF Core 3.x): With EF Core 3.0 metadata API changes (Relational() extensions removed, properties replaced with Get / Set method pair), the code is as follows:
foreach (var property in modelBuilder.Model.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
{
if (property.GetColumnType() == null)
property.SetColumnType("decimal(13,4)");
}
Update (Entity Framework Core 6): EF Core 6 includes convention model configuration that can be used to achieve this to all types.
The advantage over looping through entities manually, is that this conventions already ignore certain types (like Ignore(), or properties that have Converters).
public class SomeDbContext : DbContext
{
protected override void ConfigureConventions(
ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.Properties<decimal>()
.HavePrecision(19, 4);
}
}
EF Core 6.0
It's now simple to configure defaults for every decimal property (or string, etc.):
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.Properties<decimal>()
.HavePrecision(19, 4);
}
More info: pre-convention model configuration
The approach mentioned above does not work when I'm working on EFCore 5.0.1 DB-First. The method below on MS document works:
[Column(TypeName = "decimal(18, 4)")]
public decimal Numeric { get; set; }
New feature will be introduced in EF Core 5.0
modelBuilder
.Entity<Blog>()
.Property(b => b.Numeric)
.HasPrecision(16, 4);
Reference : https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew#preview-4

Passing func as parameter in Linq to Entities and 'Internal .NET Framework Data Provider error 1025' error

We have a class called Task:
public partial class Task : EntityObject
{
public EntityCollection<TaskUser> TaskUsers { get {...} set{...} }
}
It has navigation property called TaskUsers, which contains users attached to this taks:
public partial class TaskUser : EntityObject
{
public User User { get {...} set { } }
}
Every TaskUser object has User object.
We are given IQueryable<Task> tasks. We want to find tasks assigned to user with ID = 1. When we use
tasks.Where(t => t.TaskUsers.Any(a => a.User.ID == 1))
everything works fine. When we use
Func<TaskUser, bool> function = a => a.User.ID == 1;
return tasks.Where(t => t.TaskUsers.Any(function));
we get nice 'Internal .NET Framework Data Provider error 1025' error. Why? I want to build much more complicated filters using Expression class, but if I can't pass simple Func, this can't be done. What should I do?
EDIT
Maybe
Func<TaskUser, bool> function = a => a.User.ID == 1;
return tasks.Where(t => t.TaskUsers.Any(function));
doesn't work, but
Expression<Func<TaskUser, bool>> expression = a => a.User.ID == 1;
return tasks.Where(t => t.TaskUsers.AsQueryable().Any(expression));
works! That is all I needed.
Well the EF can only translate Expressions, not functions.
i.e. it can translate this:
Expression<Func<TaskUser,bool>>
but not this:
Func<TaskUser,bool>
As for how to merge expressions (in pseudo code):
Expression<Func<TaskUser, bool>> expression = a => a.User.ID == 1;
return tasks.Where(t => t.TaskUsers.Any(expression));
There are probably some Expression guru's who can help with that.
I suggest a followup question focused on that particular problem
Alex