Access data from indirect child table using LInq - c#

I have following table in my database and im accessing them through EF.
TestPack { id, name, type }
Sheets{ id, tp_id, date, rev }
Spool { id, sheet_id, date, rev, fabricated, installed }
which means a test pack has 1-M sheets and each sheet has 1-M spools. I want to get count of total Spools in the Test Pack, and count of spools that are fabricated, count of spools that are installed.
How do I get that through Linq query?

if I understand you right,you would like to have something like this
(from tp in ctx.TestPack
join st in ctx.Sheets on st.tp_id equals tp.id
join sl in ctx.Spool on sl.steet_id equals st.id
where tp.id == testPackId //you can change or delete this condition
select new {
Total = sl.Count() ,
FabricatedSpools = sl.Count(x=>x.fabricated == true),
InstalledSpools = sl.Count(x=>x.installed == true)
}).FisrtOrDefault();
Or maybe
(from tp in ctx.TestPack
join st in ctx.Sheets on st.tp_id equals tp.id
join sl in ctx.Spool on sl.steet_id equals st.id
where tp.id == testPackId //you can change or delete this condition
select new {
Total = sl.Count() ,
FabricatedSpools = (from s in sl
where s.fabricated == true
select s.Count()),
InstalledSpools = (from i in sl
where i.installed== true
select i.Count()),
}).FisrtOrDefault();

Not sure what you exact models are like but see below.
var testPackID = 2;//assuming
//assuming your DbContext is ctx;
var totalSpools = ctx.Spools.Count(x => x.Sheets.tp_id == testPackID);
var fabricatedSpools = ctx.Spools.Count(x => x.Sheets.tp_id == testPackID && x.fabricated);
var installedSpools = ctx.Spools.Count(x => x.Sheets.tp_id == testPackID && x.installed);

Sample data and query
I had generated the queries in SQL Server and hope you can do in LINQ. if you want specifically in LINQ let me know.
And can you please clarify whether you want result in 3 or all the 3 results in one.
Hope this helps.
Thank you.

Related

How to improve performance when joining List and Linq object

I have one list, read from file:
var lsData = ReadExcelFile<CustomerEntity>(path);
And one Object (loaded into memory):
lsCustomer = await CustomerService.GetAll()
.Where(c => c.isDeleted == null || !c.isDeleted.Value)
.OrderBy(c=> c.Code)
.ToListAsync();
And the join command:
var lsDuplicateEmail =
(from imp in lsData
join cust in lsCustomer
on ImportHelpers.GetPerfectStringWithoutSpace(imp.Email) equals ImportHelpers.GetPerfectStringWithoutSpace(cust.Email)
into gjoin
from g in gjoin.DefaultIfEmpty()
select new
{
ImportItem = imp,
CustomerItem = g,
}
into result
where !string.IsNullOrEmpty(result.ImportItem.Email) && result.CustomerItem != null
&& !ImportHelpers.CompareString(result.ImportItem.Code, result.CustomerItem.Code)
select result);
var lsDuplicateEmailInSystem = lsDuplicateEmail.Select(c => c.ImportItem.Code).Distinct().ToList();
I perform test with lsData list about 2000 records, lsCustomer about 200k records.
The Customer Email field is not indexed in the DB.
The join command executes with about 10s (even though the result is 0 records), too slow.
I've looked around and can't seem to index the email field in lsCustomer. I know the reason for the slowness is because the complexity is O(n*m).
Is there any way to improve performance?
Try the following code. Instead of GroupJoin, which is not needed here I have used Join. Also moved filters up in query.
var lsDuplicateEmail =
from imp in lsData
where !string.IsNullOrEmpty(imp.Email)
join cust in lsCustomer
on ImportHelpers.GetPerfectStringWithoutSpace(imp.Email) equals ImportHelpers.GetPerfectStringWithoutSpace(cust.Email)
where !ImportHelpers.CompareString(imp.Code, cust.Code)
select new
{
ImportItem = imp,
CustomerItem = cust,
};
Also show GetPerfectStringWithoutSpace implementation, maybe it is slow.
Another possible solution is to swap lsData and lsCustomer in query, maybe lookup search is not so fast.

How do I rewrite part of an entity framework expression tree for re-use server side? (SQL)

I have the following code below, which is part of a larger expression tree, and it strikes me that I should be able to write a method/expression that can be reused to obtain the Contacts list without all this duplicated code.
Is there a way I can write something like this in each part of the tree:
Contacts = getContactEmailAndPhones(cnt)
or
Contacts = ... select getContactEmailAndPhones(cnt)
and not have to duplicate the code as below?
Company = com != null
? new PM_VC<Company>()
{
Vendor = com,
// Get all the contacts mapped to the vendor
Contacts = (from m_cc in db.M_CCs
join cnt in db.Contacts on m_cc.ContactID equals cnt.ContactID
where m_cc.CompanyID == com.CompanyID
select new PM_Contact
{
Contact = cnt,
Emails = (from ea in db.EmailAddresses
where ea.ContactID == cnt.ContactID
select ea).ToList(),
Phones = (from ph in db.Phones
where ph.ContactID == cnt.ContactID
select ph).ToList(),
Primary = m_cc.PrimaryContact
}).ToList()
}
: null,
Individual = ivend != null
? new PM_VC<Individual>()
{
Vendor = ivend,
// Get all the contacts mapped to the vendor
Contacts = (from m_vc in db.M_IVCs
join cnt in db.Contacts on m_vc.ContactID equals cnt.ContactID
where m_vc.IVid == ivend.IVid
select new PM_Contact
{
Contact = cnt,
Emails = (from ea in db.EmailAddresses
where ea.ContactID == cnt.ContactID
select ea).ToList(),
Phones = (from ph in db.Phones
where ph.ContactID == cnt.ContactID
select ph).ToList(),
Primary = m_vc.PrimaryContact
}).ToList()
}
: null,
Joint = jvend != null
? (from m_ivjv in db.M_IVJVs
join ijven in db.Individual on m_ivjv.IVid equals ijven.IVid
where m_ivjv.JointID == jvend.JointID
select new PM_VC<Individual>()
{
Vendor = ijven,
// Get all the contacts mapped to the vendor
Contacts = (from m_vc in db.M_IVCs
join cnt in db.Contacts on m_vc.ContactID equals cnt.ContactID
where m_vc.IVid == ijven.IVid
select new PM_Contact
{
Contact = cnt,
Emails = (from ea in db.EmailAddresses
where ea.ContactID == cnt.ContactID
select ea).ToList(),
Phones = (from ph in db.Phones
where ph.ContactID == cnt.ContactID
select ph).ToList(),
Primary = m_vc.PrimaryContact
}).ToList()
}).ToList()
: null,
I presume the easier method is to replace the part after the select (since there are different mapping/join table references to obtain the relevant cnt object. Any suggestions on how to do this so it can be executed server-side, rather than in the client code, would be greatly appreciated!
To clarify, while I could leave the code as-is, I want to use the parts that obtain email and phones in another method to get those details for specific vendor IDs. In this method, it's using a join, and it would be unnecessary duplication and maintenance to have yet another instance.
I am using entity framework core 5 and c#.

Converting SQL to LINQ query when I cannot use "IN"

I'm trying to convert this very simple piece of SQL to LINQ:
select * from Projects p
inner join Documents d
on p.ProjectID = d.ProjectID
left join Revisions r
on r.DocumentID = d.DocumentID
and r.RevisionID IN (SELECT max(r2.RevisionID) FROM Revisions r2 GROUP BY r2.DocumentID)
WHERE p.ProjectID = 21 -- Query string in code
This says, if any revisions exist for a document, return me the highest revision ID. As it's a left join, if not revisions exist, I still want the results returned.
This works as expected, any revisions which exist are shown (and the highest revision ID is returned) and so are all documents without any revisions.
When trying to write this using LINQ, I only get results where revisions exist for a document.
Here is my attempt so far:
var query = from p in db.Projects
join d in db.Documents on new { ProjectID = p.ProjectID } equals new { ProjectID = Convert.ToInt32(d.ProjectID) }
join r in db.Revisions on new { DocumentID = d.DocumentID } equals new { DocumentID = Convert.ToInt32(r.DocumentID) } into r_join
from r in r_join.DefaultIfEmpty()
where
(from r2 in db.Revisions
group r2 by new { r2.DocumentID }
into g
select new { MaxRevisionID = g.Max(x => x.RevisionID) }).Contains(
new { MaxRevisionID = r.RevisionID }) &&
p.ProjectID == Convert.ToInt32(Request.QueryString["projectId"])
select new { d.DocumentID, d.DocumentNumber, d.DocumentTitle, RevisionNumber = r.RevisionNumber ?? "<No rev>", Status = r.DocumentStatuse == null ? "<Not set>" : r.DocumentStatuse.Status };
I'm not very good at LINQ and have been using the converter "Linqer" to help me out, but when trying I get the following message:
"SQL cannot be converted to LINQ: Only "=" operator in JOIN expression
can be used. "IN" operator cannot be converted."
You'll see I have .DefaultIfEmpty() on the revisions table. If I remove the where ( piece of code which does the grouping, I get the desired results whether or not a revision exists for a document or not. But the where clause should return the highest revision number for a document IF there is a link, if not I still want to return all the other data. Unlike my SQL code, this doesn't happen. It only ever returns me data where there is a link to the revisions table.
I hope that makes a little bit of sense. The group by code is what is messing up my result set. Regardless if there is a link to the revisions table, I still want my results returned. Please help!
Thanks.
=======
The code I am now using thanks to Gert.
var query = from p in db.Projects
from d in p.Documents
where p.ProjectID == Convert.ToInt32(Request.QueryString["projectId"])
select new
{
p.ProjectID,
d.DocumentNumber,
d.DocumentID,
d.DocumentTitle,
Status = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault().DocumentStatuse.Status,
RevisionNumber = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault().RevisionNumber
};
gvDocumentSelection.DataSource = query;
gvDocumentSelection.DataBind();
Although this works, you'll see I'm selecting two fields from the revisions table by running the same code, but selecting two different fields. I'm guessing there is a better, more efficient way to do this? Ideally I would like to join on the revisions table in case I need to access more fields, but then I'm left with the same grouping problem again.
Status = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault().DocumentStatuse.Status,
RevisionNumber = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault().RevisionNumber
Final working code:
var query = from p in db.Projects
from d in p.Documents
where p.ProjectID == Convert.ToInt32(Request.QueryString["projectId"])
select new
{
p.ProjectID,
d.DocumentNumber,
d.DocumentID,
d.DocumentTitle,
LastRevision = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault()
};
var results = from x in query
select
new
{
x.ProjectID,
x.DocumentNumber,
x.DocumentID,
x.DocumentTitle,
x.LastRevision.RevisionNumber,
x.LastRevision.DocumentStatuse.Status
};
gvDocumentSelection.DataSource = results;
gvDocumentSelection.DataBind();
If you've got 1:n navigation properties there is a much simpler (and recommended) way to achieve this:
from p in db.Projects
from d in p.Documents
select new { p, d,
LastRevision = d.Revisions
.OrderByDescending(r => r.RevisionId)
.FirstOrDefault() }
Without navigation properties it is similar:
from p in db.Projects
join d in db.Documents on new { ProjectID = p.ProjectID }
equals new { ProjectID = Convert.ToInt32(d.ProjectID) }
select new { p, d,
LastRevision = db.Revisions
.Where(r => d.DocumentID = Convert.ToInt32(r.DocumentID))
.OrderByDescending(r => r.RevisionId)
.FirstOrDefault() }
Edit
You can amend this very wide base query with all kinds of projections, like:
from x in query select new { x.p.ProjectName,
x.d.DocumentName,
x.LastRevision.DocumentStatus.Status,
x.LastRevision.FieldA,
x.LastRevision.FieldB
}

Remove certain records from a set of records using LINQ

Question - how to remove certain records from LINQ, like i have some records i wanted them to be removed from my LINQ.
Scenario - i have table A with 10 records and table B with 2 records i want to remove records that are belong to B to be removed from A [using linq]
-below i have all the records in q and i want to remove the records that are there in p.
var p = from c in q
join dr in dc.TableData on c.Id equals dr.CaseId
select new View()
{
ActiveCaseId = c.ActiveCaseId,
Id = c.Id
};
q = q.Except(p);
You can't do it with the Except as you show, because p and q are different types. But it would also be a bit clumsy.
You can do it in one query:
var p = from c in q
where !dc.TableData.Any(dr => dr.CaseId == c.Id)
select new View()
{
ActiveCaseId = c.ActiveCaseId,
Id = c.Id
};

Linq sort date when it's a shortdatestring

I'm trying to sort by date with linq when I already have a date brought back from linq that I converted to a shortdatestring:
var NOVs = from n in db.CT_NOVs
join i in db.CT_Inspectors on n.ARBInspectorID equals i.CTInspectorID
join v in db.CT_ViolationTypes on n.ViolationTypeID equals v.ViolationTypeID
join t in db.CT_Tanks on n.CargoTankID equals t.CargoTankID
join c in db.CT_Companies on t.CompanyID equals c.CompanyID
select new
{
n.NOVID,
n.NOVNumber,
NOVDate = n.NOVDate.Value.ToShortDateString(),
ARBInspectorFirstName = i.FirstName,
ARBInspectorLastName = i.LastName,
v.ViolationName,
t.CargoTankID,
c.CompanyName
};
Here is where I try to sort by the date, but it's giving me an error since I converted the datetime into a shortdatestring:
if (column == "NOVDate")
{
if (sortDirection == "ascending")
NOVs = NOVs.OrderBy(b => Convert.ToDateTime(b.NOVDate));
else
NOVs = NOVs.OrderByDescending(b => Convert.ToDateTime(b.NOVDate));
}
Any clue on how to sort by NOVDate?
Why don't you leave it as a full DateTime object and convert it to a ShortDateString right before you display it to the user? I usually try to leave the objects in their native format and convert them to display to the user at the last second. That'll help with separating your data layer from your presentation layer too, i.e., let the presentation layer decide how to display the date to the user; your data layer shouldn't really care.
From the comments:
Since you're using a <asp:BoundField> element with your GridView, you can use the DataFormatString property to have the grid view automatically format it.
It looks like using DataFormatString="{0:d}" should do the trick. (thanks Gromer!)
You can use the the "orderby" clause before selecting the elements.
Check this link: http://msdn.microsoft.com/en-us/library/bb383982.aspx
you sort it first then display it as you want:
var NOVs = from n in db.CT_NOVs
join i in db.CT_Inspectors on n.ARBInspectorID equals i.CTInspectorID
join v in db.CT_ViolationTypes on n.ViolationTypeID equals v.ViolationTypeID
join t in db.CT_Tanks on n.CargoTankID equals t.CargoTankID
join c in db.CT_Companies on t.CompanyID equals c.CompanyID
orderby n.NOVDate.Value
select new
{
n.NOVID,
n.NOVNumber,
NOVDate = n.NOVDate.Value.ToShortDateString(),
ARBInspectorFirstName = i.FirstName,
ARBInspectorLastName = i.LastName,
v.ViolationName,
t.CargoTankID,
c.CompanyName
};
then you can do this:
if (column == "NOVDate")
{
if (sortDirection != "ascending")
NOVs = NOVs.Reverse();
}

Categories

Resources