Entity framework 6 join on a groupjoin using lambda - entity-framework

I need to set a Join on an GroupJoin. I have googled a lot to find the answer on my problem, but i cannot find it.
In the database I have templates. I select a template with a table joined to that with items. There is also a table with one or multiple rows with files linked to the item, that is the GroupJoin I use. That one works, but now the problem is, that I need to call the table (and that is always 1 not more) that is linked to table with files.
So far I have this with a join in the groupjoin, but that join isn't working at all:
DataBundle = _context.DataTemplates.Join(_context.DataItems, DataTemplates => DataTemplates.Id, DataItems => DataItems.DataTemplateId, (DataTemplates, DataItems) => new { DataTemplates, DataItems })
.GroupJoin(_context.DataItemFiles.Join(_context.DataTemplateUploads, DataItemFiles => DataItemFiles.DataTemplateUploadId, DataTemplateUploads => DataTemplateUploads.Id, (DataItemFiles, DataTemplateUploads) => new { DataItemFiles, DataTemplateUploads }), x => x.DataItems.Id, x => x.DataItemFiles.DataItemId, (x, DataItemFiles) => new { x.DataItems, x.DataTemplates, DataItemFiles })
.Where(x => x.DataTemplates.CallName == CallName).Where(x => x.DataItems.WebsiteLanguageId == WebsiteLanguageId)
.Select(x => new DataBundle()
{
DataItemFiles = x.DataItemFiles, //error
DataItemResources = null,
DataItems = x.DataItems,
DataTemplateFields = null,
DataTemplates = x.DataTemplates,
DataTemplateUploads = x.DataTemplateUploads, //can't find, because DataTemplateUploads is linked to DataItemFiles
}).ToList();
public class DataBundle
{
public IEnumerable<DataItemFiles> DataItemFiles { get; set; }
public IEnumerable<DataItemResources> DataItemResources { get; set; }
public DataItems DataItems { get; set; }
public IEnumerable<DataTemplateFields> DataTemplateFields { get; set; }
public DataTemplates DataTemplates { get; set; }
public IEnumerable<DataTemplateUploads> DataTemplateUploads { get; set; }
}
Someone know how to solve this?

The DataItemFiles variable here
(x, DataItemFiles) => new { x.DataItems, x.DataTemplates, DataItemFiles }
is actually IEnumerable<anonymous_type> where anonymous_type is the result of the previous Join operator new { DataItemFiles, DataTemplateUploads } (btw, you should use singular form for most of the names, it's really hard to follow which one is single and which one is sequence).
Hence to get the individual parts you need to use projection (Select):
.Select(x => new DataBundle()
{
DataItemFiles = x.DataItemFiles.Select(y => y.DataItemFiles),
// ...
DataTemplateUploads = x.DataItemFiles.Select(y => y.DataTemplateUploads),
// ...
}

Related

How to spread object values into itself in EF Core Select()?

In my project, that uses EF Core 6, I have an entity that looks like this:
public class Animal
{
public int Id { get; set; }
public string Name { get; set; } = null!;
// ... many other fields that exist in the database
virtual public bool IsRelatedToGoldenCity { get; set; }
}
With IsRelatedToGoldenAnimal being a value that will be calculated in a sub-query. Since there are many fields in this class, I would like to not have to write all them down in the select where the sub-query will happen. Something like:
var query = Context.Animal
.Include(x => x.Whatever)
.Select(x => new Animal
{
...x, // mimicking JavaScript's spread operator here
IsRelatedToGoldenCity = Context.Cities.Select(...).Where(...).Any(),
});
return await query.ToListAsync();
Is there a way to do something like this?
You can use ToList and ForEach
var animals = await Context.Animal
.Include(x => x.Whatever)
.ToListAsync();
animals.ForEach(x => x.IsRelatedToGoldenCity = "");

EF core - parent.InverseParent returns null for some rows

I have a Category table and it has a Parent Category, I try to iterate over all the categories and get the parents categories with it's Inverse Parent but some of them returns without the inverse parents from unknown reason.
Categories.cs
public partial class Categories
{
public Categories()
{
InverseParent = new HashSet<Categories>();
}
public int Id { get; set; }
public int? ParentId { get; set; }
public DateTime CreateDate { get; set; }
public bool? Status { get; set; }
public virtual Categories Parent { get; set; }
public virtual ICollection<Categories> InverseParent { get; set; }
}
This is how I try to iterate them to create a select list items:
var parentCategories = await _context.Categories.
Include(x => x.Parent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
So the problem is that some of my parent categories returns all their children categories and some don't and I don't why.
There are several issues with that code, all having some explaination in the Loading Related Data section of the documentation.
First, you didn't ask EF Core to include InverseParent, so it's more logically to expect it to be always null.
What you get is a result of the following Eager Loading behavior:
Tip
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.
Second, since the query is changing it's initial shape (Select, Disctinct), it's falling into Ignored Includes category.
With that being said, you should build the query other way around - starting directly with parent categories and including InverseParent:
var parentCategories = await _context.Categories
.Include(x => x.InverseParent)
.Where(x => x.InverseParent.Any(c => c.Status == true)) // to match your query filter
.ToListAsync();
While you are including Include(x => x.Parent), you don't seem to do the same for InverseParent. This might affect your results exactly the way you describe. Would including it fix it?
parentCategories = await _context.Categories.
Include(x => x.Parent).
Include(x => x.InverseParent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
UPD: Since you are selecting x => x.Parent anyway it might be necessary to use ThenInclude() method instead.

ASP.NET Core cannot sort grouped items

I have the following model in my ASP.NET Core application:
public class LocationTypeGroup {
public string Name { get; set; }
public IEnumerable<LocationType> LocationTypes { get; set; }
}
public class LocationType
{
[Key]
public int LocationTypeID { get; set; }
public string Name { get; set; }
public string IntExt { get; set; }
}
I am trying to run a query that groups them by IntExt, and sorts by Name within each group.
The following works, but doesn't sort:
public async Task<List<LocationTypeGroup>> GetGroupedLocationTypes()
{
return await _context.LocationTypes
.GroupBy(p => p.IntExt)
.Select(g => new LocationTypeGroup
{
Name = g.Key,
LocationTypes = g.Select(x => x)
})
.OrderBy(x=>x.Name)
.ToListAsync();
}
If I change to this:
LocationTypes = g.Select(x => x).OrderBy(x => x)
Then I still do not get a sorted result.
What am I doing wrong?
It's possible that EF can't build SQL query.
So you need simplify it manually. and split to 2 queries:
var groups = await context.LocationTypes
.GroupBy(p => p.IntExt)
.ToListAsync();
return groups.Select(g => new LocationTypeGroup
{
Name = g.Key,
LocationTypes = g.Select(x => x)
})
.OrderBy(x=>x.Name);
The first query loads simply groups, and the second sorts them and converts to LocationTypeGroup.
May be it caused by too old version of Entity Framework Core. Try this approach, moreover it will be less expensive:
//data is loaded into memory
var data = await _context.LocationTypes.ToListAsync();
//data's transform
var answer = data.GroupBy(x => x.IntExt)
.Select(x => new LocationTypeGroup
{
Name = x.Key,
LocationTypes = x.AsEnumerable()
}).OrderBy(x => x.Name).ToList();

EF7 Load Child of entity in List

I have an entity relationship as follows:
public class Incident
{
[Key]
public int Id { get; set; }
public List<Equipment> EquipmentAssignments { get; set; }
}
public class Equipment
{
public int EquipmentId { get; set; }
public Incident Incident { get; set; }
public Designator Designator { get; set; }//I WANT TO INCLUDE THIS
}
I am trying to include "Designator" for every equipment in "EquipmentAssignments" and return this list. I am trying the following:
Incident tmp = _context.Incidents
.Where(x => x.Id == incid)
.Include(x => x.EquipmentAssignments)
.ThenInclude(x => x.Select(s => s.Designator)).First();
But I get the following error:
Additional information: The properties expression 'x => {from
Equipment s in x select [s].Designator}' is not valid. The expression
should represent a property access: 't => t.MyProperty'. When
specifying multiple properties use an anonymous type: 't => new {
t.MyProperty1, t.MyProperty2 }'.
I have tried using an anonymous type .ThenInclude(x => x.Select(s => new { s.Designator}) to no avail and am not sure how to accomplish what I need. Thank you for any help.
You cannot pass a select in ThenInclude. This is the ThenInclude syntax:
public static IIncludableQueryable<TEntity, TProperty> ThenInclude<TEntity, TPreviousProperty, TProperty>(this IIncludableQueryable<TEntity, ICollection<TPreviousProperty>> source, Expression<Func<TPreviousProperty, TProperty>> navigationPropertyPath) where TEntity : class;
Just remove the select and everything will working fine!
var tmp = myConext.Incidents
.Where(x => x.Id == 1)
.Include(x => x.EquipmentAssignments)
.ThenInclude(x => x.Designator).First();

How to maintain an ordered list in Entity Framework?

Changing order of elements in a simple list, doesn't stick in Entity Framework. The reason is pretty simple as the ordering information is never stored in the database.
Has anyone come across a generic implementation of ordered list which would work along with Entity Framework?
The requirement is that the user is allowed to reorder list of selected items, and the ordering of items need to be preserved.
Overview
Although there doesn't seem to be any 'magic' to implement this, there is a pattern that we have used to solve this problem, especially when dealing with hierarchies of objects. It boils down to three key things:
Build an Entity model separate from your Domain model. This has the benefit of providing a good separation of concerns, effectively allowing your domain model to be designed and changed without getting bogged down by persistence details.
Use AutoMapper to overcome the hassle of mapping between the Entity and Domain models.
Implement custom value resolvers to map the list in both directions.
The Magic
Because models often include hierarchical and cyclical references between objects, the following Map<>() method can be used to avoid StackOverflow errors during the custom mapping
private class ResolveToDomain : IValueResolver
{
ResolutionResult Resolve(ResolutionResult rr)
{
//...
((MappingEngine) rr.Context.Engine).Map<Product, ProductEntity>(rr.Context, subProduct)
//...
}
}
The Code
Domain Model. Note that the Subproducts list order is important.
class Product
{
public Product ParentProduct { get; set; }
public string Name { get; set; }
public IList<Product> Subproducts { get; set; }
}
Entity Model
class ProductEntity
{
public int Id { get; set; }
public ProductEntity ParentProduct { get; set; }
public string Name { get; set; }
public IList<ProductSubproductEntity> Subproducts { get; set; }
}
class ProductSubproductEntity
{
public int ProductId { get; set; }
public ProductEntity Product { get; set; }
public int Order { get; set; }
public ProductEntity Subproduct { get; set; }
}
Entity Framework Context
class Context : DbContext
{
public DbSet<ProductEntity> Products { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ProductEntity>()
.HasOptional(e => e.ParentProduct);
modelBuilder.Entity<ProductSubproductEntity>()
.HasKey(e => new {e.ProductId, e.Order})
.HasRequired(e => e.Product)
.WithMany(e => e.Subproducts)
.HasForeignKey(e => e.ProductId);
base.OnModelCreating(modelBuilder);
}
}
AutoMapper configuration
class Mappings : Profile
{
protected override void Configure()
{
Mapper.CreateMap<Product, ProductEntity>()
.ForMember(m => m.Subproducts, a => a.ResolveUsing<ProductSubproductResolver>());
Mapper.CreateMap<ProductEntity, Product>()
.ForMember(m => m.Subproducts, a => a.ResolveUsing<ProductSubproductEntityResolver>());
base.Configure();
}
}
class ProductSubproductResolver : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult rr)
{
var result = new List<ProductSubproductEntity>();
var subproductsSource = ((Product) rr.Context.SourceValue).Subproducts;
if (subproductsSource == null) return rr.New(null);
for (int i = 0; i < subproductsSource.Count; i++)
{
var subProduct = subproductsSource[i];
result.Add(new ProductSubproductEntity()
{
Product = (ProductEntity)rr.Context.DestinationValue,
Order = i,
Subproduct = ((MappingEngine) rr.Context.Engine).Map<Product, ProductEntity>(rr.Context, subProduct)
});
}
return rr.New(result);
}
}
class ProductSubproductEntityResolver: IValueResolver
{
public ResolutionResult Resolve(ResolutionResult rr)
{
var subproductEntitiesSource = ((ProductEntity) rr.Context.SourceValue).Subproducts;
if (subproductEntitiesSource == null) return rr.New(null);
var result = subproductEntitiesSource.OrderBy(p => p.Order).Select(p =>
((MappingEngine) rr.Context.Engine).Map<ProductEntity, Product>(rr.Context, p.Subproduct))
.ToList();
return rr.New(result);
}
}
Usage
private static IList<Product> CreateDomainProducts()
{
var result = new List<Product>();
var mainProduct1 = new Product()
{
Name = "T-Shirt"
};
var subProduct1 = new Product()
{
ParentProduct = mainProduct1,
Name = "T-Shirt (Medium)",
};
var subProduct2 = new Product()
{
ParentProduct = mainProduct1,
Name = "T-Shirt (Large)",
};
mainProduct1.Subproducts = new []
{
subProduct1,
subProduct2
};
var mainProduct2 = new Product()
{
Name = "Shorts"
};
result.Add(mainProduct1);
result.Add(mainProduct2);
return result;
}
static void Main(string[] args)
{
Mapper.Initialize(a => a.AddProfile<Mappings>());
Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
var products = CreateDomainProducts();
var productEntities = Mapper.Map<IList<ProductEntity>>(products);
using (var ctx = new Context())
{
ctx.Products.AddRange(productEntities);
ctx.SaveChanges();
}
// Simulating a disconnected scenario...
using (var ctx = new Context())
{
var productEntity = ctx.Products
.Include(p => p.Subproducts)
.Include(p => p.Subproducts.Select(p2 => p2.Subproduct))
.OrderBy(p=>p.Name)
.ToList();
var productsResult = Mapper.Map<IList<Product>>(productEntity);
// Should be 'T-Shirt (Medium)'
Console.WriteLine(productsResult[1].Subproducts[0].Name);
// Should be 'T-Shirt (Large)'
Console.WriteLine(productsResult[1].Subproducts[1].Name);
}
}
Voila. Hope that helps!
No magic here. If you want to persist a specific order of items in a list (other than a reproducible order by e.g. name) you must store a sequence number in the database.
There wont be an implementation of this for reordering on the database. The data in the database is physically ordered by default by the clustered index which is in essence ordering by the primary key.
Why do you want to do this? EF encourages all ordering to be done via LINQ queries.
If you are looking to optimize lookups you can create additional non-clustered indexes on the database by modifying the code generated for Migrations :
CreateTable(
"dbo.People",
c => new
{
ID = c.Int(nullable: false, identity: true),
Name = c.String()
})
.PrimaryKey(t => t.ID)
.Index(t => t.Name); // Create an index
Note that this will not impact the physical ordering in the database but will speed lookups, although this need to be balanced by slower writes/updates.
to find a solution for this challenge I faced to an article by the following link:
User-defined Order in SQL
this article analyzed different approaches for generating order index value during changing the order of the list. I found the algorithm mentioned in this article so performant by minimum limitation. this algorithm called True Fractions and it generates order index like the following figure:
I have prepared a code sample that I implement this approach by EF Core and InMemory database.
.NET Fiddle Code Sample