Loop linq query n times by updating property name - c#

I have this query
var query = from v in this._venueRepository.Table
join s in this._storeRepository.Table on v.VenueID equals s.VenueID
join w in this._workstationRepository.Table on s.StoreID equals w.StoreID
join t in this._tillSummaryRepository.Table on w.WorkstationID equals t.TillOpID
group new { v.DiscItemName_1, t.DiscItem_1, t.QDiscItem_1 } by new { v.DiscItemName_1 } into g
select new { Discount = g.Key, Amount = g.Sum(p => p.DiscItem_1), Qty = g.Sum(p => p.QDiscItem_1) };
I would like to execute this query (probably asynchronously) but each time I execute this query I would like to update the parameter "_1" -> "_2" -> "_3" etc for example
var query = from v in this._venueRepository.Table
join s in this._storeRepository.Table on v.VenueID equals s.VenueID
join w in this._workstationRepository.Table on s.StoreID equals w.StoreID
join t in this._tillSummaryRepository.Table on w.WorkstationID equals t.TillOpID
group new { v.DiscItemName_2, t.DiscItem_2, t.QDiscItem_2 } by new { v.DiscItemName_2 } into g
select new { Discount = g.Key, Amount = g.Sum(p => p.DiscItem_2), Qty = g.Sum(p => p.QDiscItem_2) };
etc, etc Any ideas how I can do this? Perhaps reflection?

One way of doing this is of course reflection as you mention. A sample based on your example is below:
var t = ._venueRepository.Table.FirstOrDefault().GetType();
for(int iterationCount = 1; iterationCount < MAX_ITERATIONS ; iterationCount++)
{
PropertyInfo itemNameProperty = t.GetProperty(String.Format("DiscItemName_{0}", iterationCount));
PropertyInfo discItermProperty = t.GetProperty(String.Format("DiscItem_{0}", iterationCount));
//Repeat the above for all properties.
var query = from v in this._venueRepository.Table
join s in this._storeRepository.Table on v.VenueID equals s.VenueID
join w in this._workstationRepository.Table on s.StoreID equals w.StoreID
join t in this._tillSummaryRepository.Table on w.WorkstationID equals t.TillOpID
//Repeat the below for other properties
group new { itemNameProperty.GetValue(v), dicItemProperty.GetValue(v) , qDiscProperty.GetValue(v) } by new { itemNameProperty.GetValue(v) } into g
//Similarly do for the select new.
select new { Discount = g.Key, Amount = g.Sum(p => p.DiscItem_1), Qty = g.Sum(p => p.QDiscItem_1) };
//Other code here.
}
If you are using the Entity framework, the above query will not work. The reason this will not work is that the Entity Framework will try to translate your query to SQL, and will fail at the reflection parts. What you have to do is split your query in two. Do the filter and join in one step and retrieving it to a List and then using reflection to create the anonymous types in another query. You will be using more memory however for this operation and you are losing benefits from using Linq to Entity.

Related

Linq - Linq expression different context error

I have 3 tables
- ERPEntry
- ERPEntryType
- ERPApp
I am trying to get data from these 3 tables using the below query but i got the error :
specified linq expression contains references to queries that are
associated with different contexts
var erpEntryInfo = (from s in ERPDB.ERPEntrys
JOIN t in ERPDB.ERPEntryTypes
on s.EntryTypeID equals t.EntryTypeID
join a in APPDB.ERPApps
on s.AppId equals a.AppId
where s.UserIDAdded == '250176'
select new ERPInfo
{
EntryId = s.EntryID,
EntryType = t.EntryTypeName,
ERPApp = a.ApplicationName,
DateAdded = s.DateAdded
}).OrderByDescending(d => d.DateAdded).Take(10).ToList();
I searched based on the errror and tried to split the above query into 2 as below.
var res = (from s in ERPDB.ERPEntrys
join t in ERPDB.ERPEntryTypes
on s.EntryTypeID equals t.EntryTypeID
where s.UserIDAdded == '250176'
select new {s.EntryTypeID, s.DateAdded, t.EntryTypeName, s.AppID }).OrderByDescending(d => d.DateAdded).Take(10).ToArray();
var y = (from a in APPDB.ERPApps
join b in res on a.AppId equals //??//
select new ERPInfo
{
EntryId = b.EntryID,
EntryType = b.EntryTypeName,
ERPApp = a.ApplicationName,
DateAdded = b.DateAdded
}).ToList();
I am having an issue in the above query to access AppId which i got into the result res..i commented with //??// in the above code
can i get any help on this.

Linq query w/ sub query and max

How do I rewrite this SQL into a Linq query?
Plain SQL
SELECT *
FROM contracts
INNER JOIN
(SELECT contractid, max(date) date
FROM contractlogs GROUP BY contractId) b
ON contracts.id = b.contractId
Attempt at Linq
from c in _db.Contracts
join sub in (from cl in _db.ContractLogs
group cl by cl.contractId into g
select new { contractId = g.contractId, changedate = g.Max(x => x.date)})
on c.id equals sub.contractId
select new { c, cl }
Goal of the query is to select all contracts w/ their newest update (first) (in contractLogs). I'm currently stumped on how the select would work. Ideally i'm trying to return an object with c & cl.
You can get the most recent log by sorting them in descending order and taking the first one:
from c in _db.Contracts
let mostRecentContractLog = c.ContractLogs
.OrderByDescending(cl => cl.date)
.FirstOrDefault()
select new { c, mostRecentContractLog }
As you see, I assume you have a navigation property Contract.ContractLogs. It's always strongly recommended to work with navigation properties in stead of manually coded joins.
The most literal translation is going to involve you calling groupby on ContractLogs and then joining that into Contacts. I think the ordering of your operations in your LINQ attempt is a little off however I don't often use the query syntax so I'm not positive about that. Regardless, I think you'd prefer something like this;
_db.ContractLogs.GroupBy(x => x.contractId).Select(x => new { contractid = x.Key, changedate = x.Max(y => y.date) })
With that you can do the join into _db.Contracts but I think you could write it more simply with a where though that might be less optimized by the LINQ to SQL provider. Anyway, just completing the example with a join;
OldQuery.Join(_db.Contracts, cl => cl.contractid,
c => c.contractid, (cl, c) => cl);
In cases like this it's often easier to write the query and subquery separately:
var subQuery =
from cl in _db.ContractLogs
group cl by cl.contractId into g
select new { contractId = g.Key, date = g.Max(cl => cl.date) };
var query =
from c in _db.Contracts
join cl in subQuery on c.contractId equals cl.contractId
select new { contract = c, cl.date };
You can try this:
from c in _db.Contracts
select new
{
c,
cl = _db.ContractLogs.Where(l => l.contractId == c.contractId).OrderByDescending(l => l.date).FirstOrDefault()
}

Linq to Entities Left outer join grouped into a collection

from component in Materials.OfType<Container>().Where(m => m.Active)
join segmentFinanceRating in segmentFinanceRatingView on component.Id equals segmentFinanceRating.MaterialId into segmentFinanceRatingGroup
from segmentFinanceRatingWithDefault in segmentFinanceRatingGroup.DefaultIfEmpty()
select new
{
id = component.Id,
name = component.Name,
subType = component.SubType,
size = component.Size,
MaterialIds = component.Materials.Select(x => x.Id),
BrandNames = component.Brands.Select(x => x.Name),
SegmentRatings = segmentFinanceRatingWithDefault
}
I have the above LINQ to Entities query that has a LEFT JOIN to get rating values for 1 or more segments for a given component.
The segmentFinanceRating entity has the properties, { MaterialId, SegmentId, Rating, LowRated }
At the moment the results are not grouped to the relevant component, i.e. the SegmentRatings property is not a single collection of segmentFinanceRating objects, instead I have multiple data rows with 1 segmentFinanceRating object in each.
I have seen some examples of using group x by y into z but I couldn't get it working, possibly due to some of the collections on the component that I need too, I'm not sure.
Any help would be appreciated on how to do this, thanks.
GroupBy in List doesn't work for you?
var list = (from component in Materials.OfType<Container>().Where(m => m.Active)
join segmentFinanceRating in segmentFinanceRatingView on component.Id equals segmentFinanceRating.MaterialId into segmentFinanceRatingGroup
from segmentFinanceRatingWithDefault in segmentFinanceRatingGroup.DefaultIfEmpty()
select new
{
id = component.Id,
name = component.Name,
subType = component.SubType,
size = component.Size,
MaterialIds = component.Materials.Select(x => x.Id),
BrandNames = component.Brands.Select(x => x.Name),
SegmentRatings = segmentFinanceRatingWithDefault
}).ToList().GroupBy(s=> s.SegmentRatings);
In this case it's much easier to do the join in the anonymous type:
from component in Materials.OfType<Container>().Where(m => m.Active)
select new
{
id = component.Id,
name = component.Name,
subType = component.SubType,
size = component.Size,
MaterialIds = component.Materials.Select(x => x.Id),
BrandNames = component.Brands.Select(x => x.Name),
SegmentRatings = (from segmentFinanceRating in segmentFinanceRatingView
where segmentFinanceRating.MaterialId == component.Id
select segmentFinanceRating)
}
You will have an empty collection of SegmentRatings when there are none for a specific component, giving the same effect as outer join.

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
}

Linq to SQL join and group

I have two tables in my database:
Town:
userid, buildingid
Building:
buildingid, buildingname
What i want is to populate a GridView like this:
But I don't want the buildings to be shown more than once. Here is my code:
var buildings = dc.Towns
.Where(t => t.userid == userid)
.GroupJoin(dc.Buildings,
t => t.buildingid,
b => b.buildingid,
(Towns, Buildings) => new
{
BuildningName = Buildings.First().buildingname,
Count = Towns.Building.Towns.Count()
});
gvBuildings.DataSource = buildings.ToList();
gvBuildings.DataBind();
New code which works:
var buildings = (from t in dc.Towns
where t.userid == userid
join b in dc.Buildings
on t.buildingid equals b.buildingid
into j1
from j2 in j1.DefaultIfEmpty()
group j2 by j2.buildingname
into grouped
select new
{
buildingname = grouped.Key,
Count = grouped.Count()
});
gvBuildings.DataSource = buildings.ToList();
gvBuildings.DataBind();
var buildings = from t in dc.Towns
join b in dc.Buildings on t.buildingid equals b.buildingid into j1
from j2 in j1.DefaultIfEmpty()
group j2 by b.buildingname into grouped
select new { buildingname = grouped.key, Count = grouped.Count()}
I think this should do it. I have not tested it so it might give error but it will be something like this.
Wouldn't something like this do it?
Users
.Select(User => new {User, User.Building})
.GroupBy(x => x.Building)
.Select(g=> new {Building = g.Key, Count = g.Count()})
According to my experience with Linq to SQL, when the expression is becoming complicated it is better to write a stored procedure and call it with Linq to SQL. In this way you get better maintainability and upgradeability.
Rather than an option to pure SQL, I see “Linqu to SQL” as a tool to get hard typed object representation of SQL data sets. Nothing more.
Hope it helps you.

Categories

Resources