I'm using EF Core for my project. And I have a problem with nested query in EF Core.
I have 2 classes:
public class PermissionGroupDefinitionEntity : IEntity
{
public string Name { get; set; }
public string NormalizedName { get; set; }
public string DisplayName { get; set; }
public virtual ICollection<PermissionDefinitionEntity> PermissionDefinitions { get; set; }
}
public class PermissionDefinitionEntity : IEntity
{
public string Name { get; set; }
public string NormalizedName { get; set; }
public string DisplayName { get; set; }
public bool IsEnabled { get; set; }
public virtual string GroupName { get; set; }
public virtual PermissionGroupDefinitionEntity Group { get; set; }
public virtual ICollection<PermissionDefinitionEntity> Children { get; set; }
}
and this is the ApplicationDbContext:
builder.Entity<PermissionDefinitionEntity>().HasOne(r => r.Group).WithMany(r => r.PermissionDefinitions).OnDelete(DeleteBehavior.Cascade);
builder.Entity<PermissionDefinitionEntity>().HasOne(r => r.Parent).WithMany(r => r.Children).OnDelete(DeleteBehavior.Cascade);
I want query all PermissionGroupDefinitionEntity included PermissionDefinitionEntity and self referencing of PermissionDefinitionEntity.
Can I do that with EF Core?
You need to recursively load PermissionDefinitions that placed in the PermissionGroupDefinitionEntity.
First, you should load all PermissionGroupDefinitionEntities including its children using the following query :
var query = _dbContext.PermissionGroupDefinitionEntity
.AsNoTracking()
.Include(p => p.PermissionDefinitions )
.ThenInclude(p => p.Children)
.ToListAsync();
Since every PermissionGroupDefinitionEntity has a list of PermissionDefinition you need a nested loops like this code :
foreach (var PermissionGroupDefinitionEntity in PermissionGroupDefinitionEntities)
{
foreach (var PermissionDefinitions in PermissionDefinitions)
{
}
}
Then in the inner loop you should call your recursive function.
See following link (sample for get all children recursively in Entity Framework Core)
https://patrickdesjardins.com/blog/how-to-load-hierarchical-structure-with-recursive-with-entity-framework-5
This way has terrible performance and I don't recommend that.
In this case it's seems you must write a stored procedure in SQL for better performance.
You can use .ThenInclude(i => ...) like so
var query = _context.PermissionGroupDefinitionEntity
.AsNoTracking()
.Include(i => i.PermissionDefinitions)
.ThenInclude(i => i.Group)
.AsQueryable();
Edit:
var query = _context.PermissionGroupDefinitionEntity
.AsNoTracking()
.Include(i => i.PermissionDefinitions)
.ThenInclude(i => i.Children)
.AsQueryable();
Related
I'm having an issue with a series of Include/ThenInclude in a query.
Here is my EntityFrameworkCore Query :
var fund = await funds.Where(x => x.Id == fundId)
.Include(f => f.Compositions.Where(compo => compo.Date == compositionDate))
.ThenInclude(c => c.CompositionItems)
.ThenInclude(item => item.Asset)
.FirstOrDefaultAsync(token)
?? throw new NotFoundException(nameof(Fund), fundId);
I recieve a 'CompositionDate does not exists' error.
As you can see the CompositionDate property is at the Compositions Level.
When I check the SQL generated I get this in a subquery Select statement :
SELECT f1."CompositionFundId", f1."CompositionDate", f1."AssetId", f1."Amount", a."Id", a."CountryCode", a."Currency", a."FundCompositionDate", a."FundCompositionFundId", a."Isin", a."Name", a."SecurityType", a."Ticker", a."Coupon", a."GicsSector", a."InvestmentCase", a."IpoDate", a."Theme"
FROM "FundCompositionItem" AS f1
INNER JOIN "Asset" AS a ON f1."AssetId" = a."Id"
Those 2 properties a."FundCompositionDate", a."FundCompositionFundId" doesn't exists at the 'Asset' level.
They exists in the parent (at the 'Where' level on the first Include).
I'm using Postgres provider for EFcore. Could this be the issue?
Should I be using the select anonymous type .Select(x => new { Fund = x, Compo = x.Compo.Where(...), etc... }?
I would like to preserve the navigation properties if possible. (accessing assets from compositionItems)
Any help would be much appreciated.
Edit:
Models as requested by Atiyar:
public class Portfolio : AuditableEntity
{
public Guid Id { get; set; }
public Guid Name{ get; set; }
}
public class Fund : Portfolio
{
// Irrelevant properties
public IList<FundComposition> Compositions { get; } = new List<FundComposition>();
}
public class FundComposition
{
public Fund Fund { get; set; }
// Primary key / Foreign key
public Guid FundId { get; set; }
// Primary Key
public DateTime Date { get; set; }
public List<FundCompositionItem> CompositionItems { get; set; } = new();
}
public class FundCompositionItem
{
public FundComposition Composition { get; set; }
// Primary Key
public Guid CompositionFundId { get; set; }
// Primary Key
public DateTime CompositionDate { get; set; }
public Asset Asset { get; set; }
// Primary Key
public Guid AssetId { get; set; }
public double Amount { get; set; }
}
public class Asset : BaseEntity
{
// Primary Key
public Guid Id { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
// more properties
}
In my experience, I've applied the Include() and ThenInclude() first and then applied the any conditional clauses afterwards. I'm also not sure if using Where inside of an include method does what you expect it to.
You can also apply your conditional in the first parameter of .FirstOrDefaultAsync().
var fund = await funds.Where(x => x.Id == fundId)
.Include(f => f.Compositions)
.ThenInclude(c => c.CompositionItems)
.ThenInclude(item => item.Asset)
.FirstOrDefaultAsync(x =>
x.Id == fundId && x.Compositions.Any(compo => compo.Date == compositionDate),
token
)
I apologize if this is a duplicate, but I searched and can not find my scenario.
I have a "parent" table called Tournaments and a Players table which houses participants of tournaments. This is a many to many relationship because I can have multiple tournaments and the same players (or more or less) can participate in each tournament.
I have created a join table for the purpose of allowing my many-to-many relationship.
my objects look like this:
TournamentEntity.cs
public class TournamentEntity : IInt32Identity
{
public int Id { get; set; }
...
public List<TournamentPlayers> Participants { get; set; }
}
TournamentPlayers.cs (this is my join entity)
public class TournamentPlayers
{
public int TournamentId { get; set; }
public TournamentEntity Tournament { get; set; }
public int PlayerId { get; set; }
public PlayerEntity Player { get; set; }
}
PlayerEntity.cs
public class PlayerEntity : IInt32Identity
{
public int Id { get; set; }
...
public List<TournamentPlayers> Participants { get; set; }
}
I would like to return a list and/or a single instance of TournamentEntity with a simple lambda expression from a single PlayerEntity passed into my method.
example method:
public async Task<TournamentEntity> GetTournamentByTournamentParticipant(PlayerEntity tournamentParticipant)
{
return await EntityDbSet
.Where(x => x.Participants.Contains) // this is where I'm a bit lost on how to link to "tournamentParticipant"
...
.Include(x => x.Participants)
.ThenInclude(b => b.Player)
.FirstOrDefaultAsync();
}
thanks in advance.
Have you tried this
return await EntityDbSet
.Where(x => x.Participants.Any(p => p.PlayerId == tournamentParticipant.Id))
.Include(x => x.Participants)
.ThenInclude(b => b.Player)
.FirstOrDefaultAsync();
This question already has an answer here:
EFCore Linq ThenInclude Two Foreign Keys To Same Table
(1 answer)
Closed 5 years ago.
I have 4 classes
public class Customer
{
public string CustomerName { get; set; }
public ICollection<Order> Orders{ get; set; }
}
public class Order
{
public string OrderNumber { get; set; }
public ICollection<OrderLine> OrderLines { get; set; }
public OrderType Type { get; set; }
}
public class OrderLine
{
public string StockItem { get; set; }
}
public class OrderType
{
public string Type { get; set; }
}
Using entity frameworks i want to pull out all the information starting at Customer level. I can get most information out but i am stuck getting OrderType to show against the order.
This is what i have so far.
var customerOrderDetails = _myOrderRepository
.GetAll()
.Include(o => o.Orders)
.ThenInclude(l => l.OrderLines)
I've tried added select after the o.Orders but that doesn't seem to work.
You can do something like this:
var customerOrderDetails = _myOrderRepository
.GetAll()
.Include(o => o.Orders)
.Include(o => o.Orders.Select(l => l.OrderLines))
.Include(o => o.Orders.Select(t => t.Type))
var customerOrderDetails = _myOrderRepository
.GetAll()
.Include(o => o.Orders)
.ThenInclude(l => l.OrderLines)
.Include(o => o.Orders).ThenInclude(t => t.Type)
Adding another Include after the OrderLines and then selecting the type seemed to work.
I have to copy a collection of recodrs and add them to a db with new Ids.
var subEntities= ct.SubEntities.Where(qf => qf.ParentEntityId == oldParentEntityId).ToList();
subEntities.ForEach(qf => { qf.ParentEntityId = newParentEntityId; qf.Id = default(int); });
ct.SubEntities.AddRange(subEntities);
When AddRange has run all entities subEntities has awkward Ids like -2147482647 and they go into db though there is a correct sequence. How to fix it?
My entity classes and mapping:
public class SubEntity
{
public int Id { get; set; }
public Guid ParentEntityId { get; set; }
public virtual ParentEntity ParentEntity { get; set; }
//props
}
public class ParentEntity
{
public Guid Id { get; set; }
public virtual List<SubEntity> SubEntities { get; set; }
//props
}
//OnModelCreating
builder.Entity<ParentEntity>()
.HasMany(q => q.SubEntities)
.WithOne(qf => qf.ParentEntity)
.HasForeignKey(qf => qf.ParentEntityId)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Cascade);
The problem was the way the ParentEntity were extracted. It were tracked by EF (so did the SubEntities too I suppose), so I tried to add a collection already being tracked. I don't quite understand how EF works in this case but the solution was:
var subEntities= ct.SubEntities
.Where(qf => qf.ParentEntityId == oldParentEntityId)
.AsNoTracking()
.ToList();
I have read a lot of the questions about that same error but none since to match my exact problem. I'm trying to access the property of an object, itself part of a root object, using Fluent NHibernate. Some answers say I need to use projections, others that I need to use join, and I think it should work through lazy loading.
Here are my two classes along with the Fluent mappings:
Artist class
public class Artist
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Album> Albums { get; set; }
public virtual string MusicBrainzId { get; set; }
public virtual string TheAudioDbId { get; set; }
public Artist() { }
}
public class ArtistMap : ClassMap<Artist>
{
public ArtistMap()
{
LazyLoad();
Id(a => a.Id);
Map(a => a.Name).Index("Name");
HasMany(a => a.Albums)
.Cascade.All();
Map(a => a.MusicBrainzId);
Map(a => a.TheAudioDbId);
}
}
Album class
public class Album
{
public virtual int Id { get; set; }
public virtual Artist Artist { get; set; }
public virtual string Name { get; set; }
public virtual IList<Track> Tracks { get; set; }
public virtual DateTime ReleaseDate { get; set; }
public virtual string TheAudioDbId { get; set; }
public virtual string MusicBrainzId { get; set; }
public Album() { }
}
public class AlbumMap : ClassMap<Album>
{
public AlbumMap()
{
LazyLoad();
Id(a => a.Id);
References(a => a.Artist)
.Cascade.All();
Map(a => a.Name).Index("Name");
HasMany(a => a.Tracks)
.Cascade.All();
Map(a => a.ReleaseDate);
Map(a => a.TheAudioDbId);
Map(a => a.MusicBrainzId);
}
}
And the error happens when this code is interpreted:
var riAlbum = session.QueryOver<Album>()
.Where(x => x.Name == albumName && x.Artist.Name == artist)
.List().FirstOrDefault();
The error happens when Fluent NHibernate tries to resolve the x.Artist.Name value:
{"could not resolve property: Artist.Name of: Album"}
What would be the correct way of doing this?
You have to think of your QueryOver query as (nearly) directly translating into SQL. With this in mind, imagine this SQL query:
select
Album.*
from
Album
where
Album.Name = 'SomeAlbumName' and
Album.Artist.Name = 'SomeArtistName'
This won't work because you can't access a related table's properties like that in a SQL statement. You need to create a join from Album to Artist and then use a Where clause:
var riAlbum =
session.QueryOver<Album>()
.Where(al => al.Name == albumName)
.JoinQueryOver(al => al.Artist)
.Where(ar => ar.Name == artistName)
.List()
.FirstOrDefault();
Also, since you're using FirstOrDefault, you may want to consider moving that logic to the database end. Currently, you're pulling back every record matching your criteria and then taking the first one. You could use .Take to limit the query to 1 result:
var riAlbum =
session.QueryOver<Album>()
.Where(al => al.Name == albumName)
.JoinQueryOver(al => al.Artist)
.Where(ar => ar.Name == artistName)
.Take(1)
.SingleOrDefault<Album>();
Another explanation is that you are missing your mapping of this property or field in a NHibernateClassMapping definition. I came here about why I was getting this error based on the following scenario.
var query = scheduleRepository.CurrentSession().Query<Schedule>()
.Where(x => x.ScheduleInfo.StartDate.Date < dateOfRun.Date);
This was giving me a Could Not Resolve Property error for StartDate. This was a head scratcher, since I use this syntax all the time.
My mapping file was the following:
public class ScheduleInfoMapping : NHibernateClassMapping<ScheduleInfo>
{
public ScheduleInfoMapping()
{
DiscriminateSubClassesOnColumn("Type");
Map(x => x.Detail).MapAsLongText();
}
}
which was missing the StartDate. Changed to:
public class ScheduleInfoMapping : NHibernateClassMapping<ScheduleInfo>
{
public ScheduleInfoMapping()
{
DiscriminateSubClassesOnColumn("Type");
Map(x => x.Detail).MapAsLongText();
Map(x => x.StartDate);
}
}
Which resolved the error.