How to include IEnumerables with Entity Query - c#

When I try to include an ICollection property, it works, but if I try to include Enumerable, it fails to do so. I'm trying to have Eager loading, may I ask how abouts do Eager load an IEnumerable.
Person.cs
public virtual ICollection<Ownership> Ownerships { get; set; }
public virtual IEnumerable<Business> Businesses { get { return Ownerships.Where(x => x.Active).Select(ownership => ownership.Business); } }
My current code setup(lazy loading) that works
Entity = UnitOfWork.PersonRepository.Get(x => x.Id == id).First();
Entity.Businesses.ForEach(
x =>
SelectedBusinesses.Add(
Businesses.FirstOrDefault(b => b.Id == x.Id) ?? new BusinessDTO { Id = x.Id, Name = x.Name }));
what I tried to do - crashes
Entity = UnitOfWork.PersonRepository.Get(x => x.Id == id, includeProperties: new[] { "Ownerships", "Businesses"}).First();
Entity.Businesses.ForEach(
x =>
SelectedBusinesses.Add(
Businesses.FirstOrDefault(b => b.Id == x.Id) ?? new BusinessDTO { Id = x.Id, Name = x.Name }));

Related

EF: Get master-detail based on conditions on master as well as on detail

EF Core 3.1 and such master-detail model:
public class MyMaster
{
public Guid Id { get; set; }
public string Category { get; set; }
public List<MyDetail> MyDetails { get; set; }
}
public class MyDetail
{
public Guid Id { get; set; }
public Guid MasterId { get; set; }
public bool IsDeleted { get; set; }
}
I'd like to select all master records in certain category together with details not marked as deleted. Also if all details for particular master are marked as deleted then this master is not returned. Basically I'd like to run such simple select:
select * from MyMaster m
inner join MyDetail d on m.Id = d.MasterId and d.IsDeleted = 0
where m.Category = 'foo'
I try such LINQ method, but it returns also deleted detail records:
var result = await dbContext.MyMasters
.Include(m => m.MyDetails)
.Where(m => m.Category = 'foo')
.Join(dbContext.MyDetails.Where(d => !d.IsDeleted), m => m.Id, d => d.MasterId, (m, d) => m)
.ToListAsync();
How the LINQ method query should look like?
If you need data just for query, it can be easily retrieved by Select:
var result = await dbContext.MyMasters
.Where(m => m.Category = 'foo')
.Select(m => new MyMaster
{
Id = m.Id,
Category = m.Category,
MyDetails = m.MyDetails.Where(d => !d.IsDeleted).ToList()
})
.ToListAsync();
Your code is true bro, but I write and tested these queries and shown the result. its worked for me.
var result = await _dBContext.MyMasters
.Include(m => m.MyDetails)
.Where(m => m.Category == "foo")
.Join(_dBContext.MyDetails.Where(d => !d.IsDeleted),
m => m.Id,
d => d.MasterId,
(m, d) => new {
MasterId = m.Id,
Category = m.Category,
DetailId = d.Id,
IsDeleted = d.IsDeleted,
})
.ToListAsync();
var result2 = from m in _dBContext.MyMasters.Include(m => m.MyDetails)
join d in _dBContext.MyDetails on m.Id equals d.MasterId
where m.Category == "foo" && !d.IsDeleted
select new {
MasterId = m.Id,
Category = m.Category,
DetailId = d.Id,
IsDeleted = d.IsDeleted,
};
you try for select New type after Join two collection. IF you get Collection from MyDetails in masters, you have all items because it's not filtered, it's just all navigations item.
It looks like the actual question is how to filter the related entities.
Filtered Includes
In EF Core 5 and later this is done using filtered includes:
var result = await dbContext.MyMasters
.Include(m => m.MyDetails.Where(d=>!d.IsDeleted))
.Where(m => m.Category = 'foo')
.ToListAsync();
Global Query Filters
In EF Core 2 and late another option is to use global query filters to ensure deleted entities are never loaded through that DbContext, even by accident :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyDetail>().HasQueryFilter(p => !p.IsDeleted);
...
}
This way, the following query will only load active records :
var result = await dbContext.MyMasters
.Include(m => m.MyDetails)
.Where(m => m.Category = 'foo')
.ToListAsync();
A global query filter affects the entire DbContext which is perfectly fine. A DbContext isn't a model of the database, and entities aren't tables. It's perfectly fine to have different DbContexts with entities and different configurations targeting the same database, that handle specific scenarios/use cases (bounded contexts if you like DDD).
In this case most of the application would use a DbContext with the global query filter(s) while administrative pages would use a different one that allowed access to all records

how to convert include and then include in entity framework to MySQL query

I have some code using Entity framework like this
public class Userpayment
{
public List<managerpayment> Managerpayments{ get; set; }
public accountpayment Accountpayments{ get; set; }
public adminpayment Adminpayment{ get; set; }
}
And here I am using include and then include like this
var withIncludes = await _userpayment
.Where(x => x.ManagerId == managerId)
.Include(t => t.managerpayment)
.Include(t => t.accountpayment)
.ThenInclude(t => t.adminpayment)
.AsNoTracking()
.ToListAsync();
return withIncludes
.Select(x =>
new
{
_userpayment= x,
managerpayment= x.managerpayment.OrderByDescending(z => z.Id).FirstOrDefault(),
accountpayment= x.accountpayment
})
.Where(m => m.managerpayment!= null)
I am trying to convert this to MySQL query and ended up like this
SELECT
*
FROM
`userpayment` `uspm`
JOIN `managerpayment` `mup` ON `mup`.`map_id` = `uspm`.`map_id`
JOIN `accountpayment` `msam` ON `msam`.`buyer_id` = `uspm`.`map_id`
JOIN `adminpayment` `amp` ON `amp`.`map_id` = `msam`.`map_id`
where
`uspm`.`manager_id`=managerId
Couple of questions here---
Is this how internally EntityFramework converts??
how to convert managerpayment= x.managerpayment.OrderByDescending(z => z.Id).FirstOrDefault() this to mysql --since managerpayment is a list i want to do orderbydescending and perform join
and how to add this condition Where(m => m.managerpayment!= null)

I have a LINQ statement that is partially query and partially method. How can I do this using one or the other?

Here is my query as it stands now:
Goals = await (from p in _context.FixtureActivityTb
where p.ActivityType.Trim() == "G"
group p by p.PlayerId into x
join j in _context.PlayerTb on x.Key equals j.PlayerId
select new Stats
{
Name = j.GivenName,
pID = j.PlayerId,
TeamId = j.TeamId,
Count = x.Count()
})
.OrderByDescending(s => s.Count)
.ThenBy(s => s.Name)
.Take(10)
.ToListAsync();
As you can see this is a mix of method and query. I should be able to do a join, where, and add the data to a custom class all in Method, however so far I have not been able to put it all together. Any guidance will be appreciated.
I will include these other items, however, I think they are beside the point.
Variable Declaration:
public IList<Stats> Goals { get; set; }
Class:
public class Stats
{
public Guid pID { get; set; }
public string TeamId { get; set; }
public string Name { get; set; }
public int Count { get; set; }
}
If i understand your question (and the lack of coffee isn't affecting me), to get this all to a Linq chain method, it should be as simple as
Goals = await _context.FixtureActivityTb.Where(p => p.ActivityType.Trim() == "G")
.GroupBy(p => p.PlayerId)
.Join(_context.PlayerTb, x => x.Key, j => j.PlayerId, (x, j)
=> new Stats
{
Name = j.GivenName,
pID = j.PlayerId,
TeamId = j.TeamId,
Count = x.Count()
})
.OrderByDescending(s => s.Count)
.ThenBy(s => s.Name)
.Take(10)
.ToListAsync();
It's quite acceptable to mix the two, especially where you have a base query, and a paging part, as here. You can even compose the queries over multiple statements, eg:
var q = from p in _context.FixtureActivityTb
where p.ActivityType.Trim() == "G"
group p by p.PlayerId into x
join j in _context.PlayerTb on x.Key equals j.PlayerId
select new Stats
{
Name = j.GivenName,
pID = j.PlayerId,
TeamId = j.TeamId,
Count = x.Count()
};
Goals = await q.OrderByDescending(s => s.Count)
.ThenBy(s => s.Name)
.Take(10)
.ToListAsync();

LINQ query with n+1 issue

I have a query with three sub queries and my problem is that the sub queries are run for each Country (n+1).
I have boiled down my query to make it easier to read, since the main query is around 70 rows, and changed the domain to make it more understandable.
I've tried including Cities/Mountains/Rivers and running .ToList() on the sub queries, but to no avail.
// The CountryDto class I'm selecting to.
public class CountryDto
{
public string CountryName { get; set; }
public IEnumerable<CityDto> CityDtos { get; set; }
public IEnumerable<MountainDto> MountainDtos { get; set; }
public IEnumerable<RiverDto> RiverDtos { get; set; }
}
// The query
var query = _db.Countries
.Select(country => new CountryDto
{
CountryName = country.Name,
CityDtos = country.Citites
.Where(city => city.Population > 10000)
.Select(city => new CityDto
{
Name = city.Name,
}),
MountainDtos = country.Mountains
.Where(mountain => mountain.Height > 100)
.Select(mountain => new MountainDto
{
Name = mountain.Name,
}),
RiverDtos = country.Rivers
.Where(river => river.Length > 1000)
.Select(river => new RiverDto
{
Name = river.Name,
}),
})
.Where(c => c.CityDtos.Any() || c.MountainDtos.Any() || c.RiverDtos.Any());
var totalCount = query.Count();
var countries = await query.ToListAsync();
Entity Framework Core supports parts of the query being evaluated on the client and parts of it being pushed to the database. It is up to the database provider to determine which parts of the query will be evaluated in the database.
In your case, all .Any parts are evaluated in the client side I guess. You can configure your code to throw an exception for client evaluation.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;")
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
For more info https://learn.microsoft.com/en-us/ef/core/querying/client-eval
Ended up splitting the query into a .Where() part for the count and a .Select() part for the result, which removed my n+1 issue.
var query = await _db.Countries
.Include(c => c.Cities)
.Include(c => c.Mountains)
.Include(c => c.Rivers)
.Where(c => c.Cities.Any(city => city.Population > 10000)
|| c.Mountains.Any(mountain => mountain.Heigh > 1000)
|| c.River.Any(river => river.Length > 100000))
.Where(c => c.Cities.Any() || c.Mountains.Any() || c.Rivers.Any())
.ToListAsync();
var totalCount = query.Count();
var countries = query
.Select(country => new CountryDto
{
CountryName = country.Name,
CityDtos = country.Citites
.Select(city => new CityDto
{
Name = city.Name,
}),
MountainDtos = country.Mountains
.Select(mountain => new MountainDto
{
Name = mountain.Name,
}),
RiverDtos = country.Rivers
.Select(river => new RiverDto
{
Name = river.Name,
}),
})
.ToList();

Return a list or array with different model types

I have the following code:
BandProfileModel BandProfile;
MusicanProfileModel MusicanProfile;
RegularProfileModel RegularProfile;
using (AppDbContext ctx = new AppDbContext())
{
var rolesForUser = userManager.GetRoles(UserId);
if(rolesForUser.Count() <= 0)
{
return View("SetupNewProfile");
}
//This below don't work: Canno't implicity convert type "System.Linq.IQueryable"....
BandProfile = ctx.BandProfiles.Where(u => u.UserId == UserId);
MusicanProfile = ctx.MusicanProfiles.Where(u => u.UserId == UserId);
RegularProfile = ctx.RegularProfiles.Where(u => u.UserId == UserId);
}
return View(Profiles);
I want to merge the BandProfile, MusicanProfile and RegularProfile into an Array or List and return it to the view, but I don't know how to do It.
How can I merge different class/model types into one Array/list?
If you have an abstraction for these types, you could return a list of this abstraction. It could be an interface or a abstract class or even a simples class that all types inherit. The option could be to return a collection of System.Object (.net) or object (in C#). For sample
var result = new List<object>();
using (AppDbContext ctx = new AppDbContext())
{
var rolesForUser = userManager.GetRoles(UserId);
if(rolesForUser.Count() <= 0)
{
return View("SetupNewProfile");
}
var bandProfile = ctx.BandProfiles.Where(u => u.UserId == UserId).ToList();
var musicanProfile = ctx.MusicanProfiles.Where(u => u.UserId == UserId).ToList();
var regularProfile = ctx.RegularProfiles.Where(u => u.UserId == UserId).ToList();
result.AddRange(BandProfile);
result.AddRange(MusicanProfile);
result.AddRange(RegularProfile);
}
return View(result);
Using a object you have to check and convert (cast) the type to read all properties or call method from the object. Follow the OOP (short for Oriented Object Programming), you could have a abstract type to hold all comom properties/methods you need into a single type. Doing something like this, you could convert the result of your lists into this abstract type and use it on the View to have a strongly typed view. For sample:
Let's supose you have this classe:
public class Profile
{
public int Id { get; set; }
public string Name { get; set; }
}
You could have classes iheriting from it or converting to it.
Another option could be casting your queries to return this type.
var result = new List<Profile>();
using (AppDbContext ctx = new AppDbContext())
{
var rolesForUser = userManager.GetRoles(UserId);
if(rolesForUser.Count() <= 0)
{
return View("SetupNewProfile");
}
var bandProfile = ctx.BandProfiles.Where(u => u.UserId == UserId)
.Select(x => new Profile() { Id = x.Id, Name = x.Name})
.ToList();
var musicanProfile = ctx.MusicanProfiles.Where(u => u.UserId == UserId)
.Select(x => new Profile() { Id = x.Id, Name = x.Name})
.ToList();
var regularProfile = ctx.RegularProfile.Where(u => u.UserId == UserId)
.Select(x => new Profile() { Id = x.Id, Name = x.Name})
.ToList();
result.AddRange(BandProfile);
result.AddRange(MusicanProfile);
result.AddRange(RegularProfile);
}
return View(result);
This can be done by creating a new class that contains the properties common to all three entities. Then you can select a projection of the entity to the new class.
If your new class looks like this:
public class Profile
{
public int Id {get;set;}
public string Name {get;set;}
}
Then you can select like this:
BandProfile = ctx.BandProfiles.Where(u => u.UserId == UserId).Select(x => new Profile(){Id = x.Id, Name = x.Name}).ToList();
MusicanProfile = ctx.MusicanProfiles.Where(u => u.UserId == UserId).Select(x => new Profile(){Id = x.Id, Name = x.Name}).ToList();
RegularProfile = ctx.RegularProfiles.Where(u => u.UserId == UserId).Select(x => new Profile(){Id = x.Id, Name = x.Name}).ToList();
Then you should be able to merge those 3 collections into a single list and return it in the View. This can also have the added benefit of not returning more data to the client than you intended because only properties explicitly added to the Profile class will be returned.

Categories