Get parent record two levels up - c#

Suppose I have this data structure in my db.
Parent_table
ParentID
someOtherProp
having 1:N relation with Child_table as shown below
ChildID
ParentID
This Child_table further has a child table with 1:N relationship as SubChild_table
SubChildID
ChildID
Now, I have SubChildID. How can I get access to Parent_table's someOtherProp? I tried with .Include(), but I am really not sure how to write. So far I have something like this:
var foo = _db.Parent_table
.Include(c => c.Child_table
.Select(d => d.SubChild_table
.Where(f => f.SubChildID == subChildID)))
.Select(r => r.someOtherProp)
.SingleOrDefault();
The error I get is:
The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties. Parameter name: path

You don't need .Include unless you want to return the included properties. You're free to access all properties of all objects in the hierarchy while constructing the query.
So, I think what you want is:
var foo = (from p in _db.Parent_table
join c in _db.Child_table on p.ParentId equals c.ParentId
join s in _db.SubChild_table on c.ChildId equals s.ChildId
where s.SubChildId == subChildId
select p.someOtherProp).SingleOrDefault();

Providing you have the foreign key relationships set
var foo = _db.SubChild_tables.Where(sc => sc.SubChildId == id).Single().Child_table.Parent_Table.SomeOtherProp;

A guess ..
var foo = from pt in Parent_table
join ct in Child_table
on pt.ParentID equals ct.ParentID
join sct in SubChild_table
on ct.ChildID equals sct.ChildID
where sct.SubChildID == "YourValue"
select new {
pt.SomeOtherProp
};
Or
var foo = Parent_table
.Join(Child_table, p => p.ParentID, c => c.ParentID, (p, c) => new { p, c })
.Join(SubChild_table, sc => sc.c.ChildID, c => c.ChildID, (sc, c) => new { sc, c })
.Where(sc.SubCHildID == "YourValue")
.Select(m => new {
m.p.someOtherProp
});

Related

How to Perform a Join Operation with a Group by In EF CORE

I have two tables Clients and ClientEvaluations, those tables are connected through a foreign key. Each ClientEvaluation has FK to a single Client Entity.
Now i need to query all clients with their last evaluation, no more than one valuation per client. Note that each evaluation has a date.
This code here achieves that in SQL.
SELECT C.Id, MAX(E.EvaluationDate) FROM [dbo].[Clients] as C
JOIN [dbo].[ClientEvaluations] AS E ON E.ClientId = C.Id
GROUP BY C.Id
I have also tried this but the problem with what I'm trying to achieve is that i need to get back from this query the Client entity properties as well.
var lastEvaluations = _db.ClientEvaluations.GroupBy(x => x.ClientId, (x, y) => new { ClientId = x, EvaluationDate = y.Max(z => z.EvaluationDate), }).ToList();
But the query here of course only returns the ClientId and the date, how can i include the whole client entity?
I hope you have configured _dbContext correctly. Then you can use include to do the join operation.
var results = _dbcontext
.Clients
.Include(x => x.ClientEvaluations) //join
.GroupBy(y => y.Id) // group by
.Select(z => new
{
Id = z.Key.Value,
Max = z.Max(x => x.EvaluationDate),
}).ToList();
Or
var results = from c in _dbcontext.Clients
join e in _dbcontext.ClientEvaluations
on c.Id equals e.ClientId
group c by c.Id into cg
select new
{
Id = Id = cg.FirstOrDefault().Id,
Max = cg.Max(x => x.EvaluationDate),
}).ToList();

C# : How to get anonymous type from LINQ result

I need to get NewsImage field and list of categories Ids that associated with the news in Many to Many relationship ... but it gives me error:
The type of one of the expressions in the join clause is incorrect.Type inference failed in the call to 'Join'.
My code looks like this
var Result1 = (from c in db.News
join d in db.Categories
on c.NewsId equals d.News.Select(l => l.NewsId)
where c.NewsId == 1
select new { c.NewsImagePath, d.CategoryId }).ToList();
Assuming you have a navigation property defining the n-n relation I would write:
var result = db.News
.Where(x => x.NewsId == 1)
.SelectMany(x => x.Categories,
(news, category) => new { news.NewsImagePath, category.CategoryId })
.ToList();
The problem is inside the on statement.
on c.NewsId equals d.News.Select( l => l.NewsId )
The Select on the right-hand side will return a IEnumerable of news, which is not what you want.
Something like this would technically work:
on c.NewsId equals d.News.Select( l => l.NewsId ).FirstOrDefault()
But it does not make sense logically.
I suspect the whole query should be built differently. I think you want to join when the category list of news contains the news item. In that case, you can't use the join statement, it would look somewhat like this:
from n in db.News
from c in db.Categories
where c.News.Select( ne => ne.NewsId ).Contains( n.NewsId )
select new { n.NewsImagePath, c.CategoryId }

Can make left join in Entity Framework without dataset

I have this query
return _ctx.TestPackages.Where(s => s.Id == TestPackageId).
Join(_ctx.TestPackageReportDetails, s => s.Id, d => d.TestPackageId, (s, d) => new { reportDetail = d, testpack = s }).
Join(_ctx.TestPackageReports, p => p.reportDetail.TestPackageReportId, o => o.Id, (p, o) => new { combined = p, report = o })
.ToList()
As you can see my query makes join between 3 tables TestPackages TestPackageReportDetails and TestPackageReports. When I have more than one record in TestPackageReportDetails with same testpackageid, the result is repeated 3 times in the output. How can I avoid the repetition?
Should I make a left join between TestPackageReportDetails and TestPackages? If yes how can I do that?
If your intention is to Eager load the report details then you should be using .Include :
_ctx.TestPackages.Include(t=>t.TestPackageReportDetails.TestPackageReports).Where(s => s.Id == TestPackageId);
Since you're only selecting three fields (As shown before you edit your answer again and remove the select) then you can do this:
(from s in _ctx.TestPackages
join d in _ctx.TestPackageReportDetails,
on s.Id equals d.TestPackageId
join r in _ctx.TestPackageReports
on s.Id equals r.reportDetail.TestPackageReportId
where s.Id == TestPackageId
select new
{
s.Id,
s.packageNumber,
s.Size,
s.TestPackageOrder
}).Distinct().ToList().Select(m=> new ..) // continue your normal selection

Entity Framework filter nested collection

I have a entity relation diagram as follows.
ClassEntity:
public int id
public int std
public virtual ICollection<StudentEntity> students
StudentEntity:
public int id
public string name
public string gender
public virtual ClassEntity class
public virtual StudentAddressEntity studentAddress
StudentAddressEntity:
public int id
public string address
I need to get the class and its male children.
var classEntity = dbContext.Set<ClassEntity>().Where(t => t.id == classId);
var query = classEntity.Include(c => c.students.Select(s => s.studentAddress))
.FirstOrDefault(c => c.students.Any(s => s.gender == GenderEnum.Male));
But it is returning the class with all the students. How to filter only male students?
I have used joins to accomplish similar results in the past. For eg I've accounts that have addresses nested (1:M). If I want to get, say, all the accounts that belong to a particular country, I would use joins as below:
(from a in accountRepo.GetAll()
join aa in accountAddressRepo.GetAll() on a.AccountId equals aa.AccountId
join ad in addressRepo.GetAll() on aa.AddressId equals ad.AddressId
where ad.CountryId == codeCountryId
select a).ToList();
If you are not using repository pattern you can simply replace accountRepo.GetAll() with DbContext.Set().
In your case you should be able to join Student, Address and Class entities and get similar results. Something like below should work for you:
(from s in DbContext.Set<StudentEntity>
join a in DbContext.Set<StudentAddressEntity> on s.studentAddress.id equals a.id
join c in DbContext.Set<ClassEntity> on s.class.id equals c.id
where c.std == classId && s.gender== GenderEnum.Male
select s).ToList();
please note this is a simple representation based on my understanding of your database and entity names. You may need to tweak this query a bit to make it compilable but the underlying idea should work for you. Please let me know how did it work for you.
You intentionally "can't" do this directly with the EF proxies. For example, consider what would happen when you tried to call SaveChanges() and all of the female students are missing from ClassEntity.Students!
Instead, the usual thing to do if you're just displaying data is to project onto an anonymous type or a DTO, e.g.:
var classOnlyMale = dbContext.Set<ClassEntity>()
.Where(x => x.Id == classId)
.Select(x => new // I'm using an anonymous type here, but you can (and usually should!) project onto a DTO instead
{
// It's usually best to only get the data you actually need!
Id = x.Id
Students = x.Students
.Where(y => y.Gender == GenderEnum.Male)
.Select(y => new { Name = y.Name, ... })
});
Or, if you desperately need to make changes and save them:
var classOnlyMale = dbContext.Set<ClassEntity>()
.Where(x => x.Id == classId)
.Select(x => new
{
Class = x,
MaleStudents = x.Students.Where(y => y.Gender == GenderEnum.Male)
});
I quite strongly recommend the former unless there's no way around it. It's really easy to introduce bugs if you're making changes to filtered data and trying to save it.
The below should load only the male students for each class.
var classEntity = testContext.Set<ClassEntity>().Where(t => t.Id == classId);
var classes = classEntity.ToList().Select(c =>
{
testContext.Entry(c)
.Collection(p => p.Students)
.Query()
.Where(s => s.Gender == GenderEnum.Male)
.Load();
return c;
});
I think join is the right way to go about it as suggested by Manish Kumar.
(from s in DbContext.Set<StudentEntity>
join a in DbContext.Set<StudentAddressEntity> on s.studentAddress.id equals a.id
join c in DbContext.Set<ClassEntity> on s.class.id equals c.id
where c.std == classId && s.gender== GenderEnum.Male
select s).ToList();

Many-to-Many Query in Entity Framework 6 without Junction Tables

I have several entites involved in one-to-many and many-to-many relationships. Entity framework doesn't expose models for the generated junction tables, so I'm trying to figure out how to use navigation properties to produce the equivalent result to this query:
select p.Name, p.Description, p.Version, p.Filename, f.Name as Platform, p.ReleaseNotesURL
from packages p
inner join Platforms f on (f.ID = p.PlatformID)
inner join PackageGroups pg on (pg.Package_ID = p.ID)
inner join Groups g on (g.ID = pg.Group_ID)
inner join GroupCustomers gc on (gc.Group_ID = g.id)
where gc.Customer_ID = #customerId AND p.IsPublished = 1
SOLVED: Thanks to octavioccl I got the solution I was after:
var request = HttpContext.Request;
var appUrl = HttpRuntime.AppDomainAppVirtualPath;
var baseUrl = string.Format("{0}://{1}{2}", request.Url.Scheme, request.Url.Authority, appUrl);
var items = from s in db.Packages
join p in db.Platforms on s.PlatformID equals p.ID
from g in s.Groups
from c in g.Customers
where c.ID == customer.ID && s.IsPublished
select new
{
Name = s.Name,
Description = s.Description,
Version = s.Version,
PackageURL = baseUrl + "/PackageFiles/" + s.Filename,
Platform = p.Name,
ReleaseNotesURL = s.ReleaseNotesURL
};
return Json(items.ToList(), JsonRequestBehavior.AllowGet);
I don't know the names of your entities and navigation properties but I think you could do something like this:
int id=10;
var items = from s in db.Packages
join p in db.Platforms on s.PlatformID equals p.ID
from g in s.Groups
from c in g.Customers
where c.Id==id && s.Published==1
select new {Name=s.Name,
Description=s.Description,
Version= s.Version,
FileName= s.Filename,
PlatformName=p.Name,
ReleaseNoteUrl=p.ReleaseNoteUrl};
In OnModelCreating try something like this:
modelBuilder.Entity<PackageGroup>()
.HasMany(x => x.Groups)
.WithRequired(x => x.PackageGroups)
.HasForeignKey(x => x.Group_ID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<PackageGroup>()
.HasMany(x => x.Packages)
.WithRequired(x => x.PackageGroups)
.HasForeignKey(x => x.Package_ID)
.WillCascadeOnDelete(false);
Edit: Having defined the relationships described above, you can then use a query like this (psudocode):
var query =
from g in db.groups
from pg in g.packageGroups
from p in pg.packages
where g.Name = "Something" && p.Version = 1
select new { yada yada }

Categories