I have a self referencing table "Product" with the following structure (where D = Draft and A = Approved)
ID ParentID Status Name
---------------------------
1 NULL A Foo
2 1 A Foo2
3 NULL D Bar
4 1 D Foo3
A row can either be "new" (where ParentID == null) or can be a version of an existing row. So we can see from the table that there are 3 versions for the item "Foo" and only 1 for "Bar".
I need a way of returning the latest versions of each item based on whether the user is able to see only "Approved" items or is able to see "Draft" as well. So for example
Users who can see "D" would have:
3 NULL D
4 1 D
The "latest" row for "Foo" and "Bar".
Users who can see "A" would have:
2 1 A
ie. only the "Approved" versions.
Thanks in advance,
Jose
Here is the Linq query that should work for you:
bool hasDraftAccess = false;
var query = DataContext.Records.AsQueryable();
if (!hasDraftAccess) {
query = query.Where(r => r.Status == 'A');
}
var seriesQuery = query.Select(r => new { Record = r, SeriesID = r.ParentID ?? r.ID });
var latestQuery = seriesQuery.GroupBy(s => s.SeriesID).Select(g => g.OrderByDescending(s => s.Record.ID).First());
var resultsQuery = latestQuery.Select(s => s.Record);
var results = resultsQuery.ToArray();
Here's what's happening:
First, add a WHERE clause to filter out draft records if the user doesn't have access to them
Then add a pseudo column called 'SeriesID' that groups all the related versions into that one column. That is, make it easy to group parent and related children.
Group the related records and then pick whichever record is most recent
Select the Linq Entity from the anonymous type so that it is updatable
I should note that if you have the ability to change your data schema you should consider adding a column called InsertDate or something to that effect. Right now I am assuming that whatever record has the highest ID is the latest. It is often better to add a DateTime field and sort on that instead.
I apologize that this isn't actually using Linq syntax--I prefer fluent coding styles--but it could be easily translated to Linq syntax if you preferred it.
Totally untested - but something like this might work, if I've understood the question correctly.
Can see approved
context.Table.Where(p => p.Status == "A")
Can see approved and draft
context.Table.Where(p => p.Status == "D" || (p.Status == "A" && !context.Table.Any(q => q.Status == "D" && q.Parent == p.Parent)))
Related
I have a list "A" and a generic list "B". I need to find all the items of "A" those are not in "B" with multiple condition.
List "A":
EXWORK
CENTAGES
PREMIUM
List "B":
PARTICULARS CATAGORY DETAIL
EXWORK ERECTION ABC
CENTAGES ERECTION ABC
PREMIUM SUPPLY ABC
For this I use following code:
var value = A.Where(a => B.All(b => b.CATAGORY == "SUPPLY" && b.PARTICULARS!=a));
but this return the value "Premium" also whereas it shouldn't be. I am not figuring out where I am making mistake.
My Desired result is:
EXWORK
CENTAGES
Do you need to find all items in A that are not listed as a PARTICULAR categorized as SUPPLY in B?
If so, you could find all items in B where CATEGORY = "SUPPLY" and return list A except the PARTICULAR values of the filtered B items.
var value = A.Except(
B.Where(b => b.CATAGORY == "SUPPLY")
.Select(b => b.PARTICULARS));
Example fiddle here.
I think perhaps you mean:
var value = A.Where(a => !B.Any(b => b.CATAGORY == "SUPPLY" && b.PARTICULARS == a));
If you look at your query, you are trying to filter list A based on the condition from List B i.e b.CATAGORY == "SUPPLY" and PARTICULARS should be present in A list.
To get desire output, you have iterate though List B and filter records based on condition b.CATAGORY == "SUPPLY" and PARTICULARS property,
var result = B
.Where(x => x.CATAGORY == "ERECTION" && A.Contains(x.PARTICULARS)) //Use Contains.
.Select(x => x.PARTICULARS); //Select only PARTICULARS value.
Try Online
If you want data from List A, then you can break your linq in two steps,
var resultB = B
.Where(x => x.CATAGORY == "SUPPLY")
.Select(x => x.PARTICULARS).ToList();
var value = A.Except(resultB);
I have a situation where I have to match up multiple customers numbers from one system with a single customer number in another system.
So for instance customer number 225, 228 and 223 in system A will all map to customer number 110022 in system B.
Easy enough, I have a matrix setup to do that.
I pull the matrix data in like this:
var dt_th_matrix = (from m in aDb.Matrix_Datatrac_TopHat select m).ToArray();
So the records would be something like:
customerA: 3 CustomerB: 1001
CustomerA: 4 CustomerB: 1001
CustomerA: 5 Customer: 1002
Then I do a big data pull and step through all the items. For each of the items I go grab the matching customer number from the matrix like this:
foreach (var dt_stop in mainPull)
{
int? th_customerId = (from d in dt_th_matrix
where d.datatrac_customer_no == dt_stop.Customer_No.ToString()
select d.tophat_customer_detail_Id).First();
What I would rather do is to just embed the code to grab the customer numbrer from the matrix directly in my datapull -- the part "Query goes here somehow" will be some type of Lambda I assume. Any help?
I have tried something like this:
th_customerId = (dt_th_matrix.First().tophat_customer_detail_Id.Equals c.Customer_No)
But that is not it (obviously)
var mainPull = (from c in cDb.DistributionStopInformations
join rh in cDb.DistributionRouteHeaders on c.Route_Code equals rh.Route_Code
where c.Company_No == 1 &&
(accountNumbers.Contains(c.Customer_No)) &&
(brancheSearchList.Contains(c.Branch_Id) && brancheSearchList.Contains(rh.Branch_Id)) &&
c.Shipment_Type == "D" &&
(c.Datetime_Created > dateToSearch || c.Datetime_Updated > dateToSearch) &&
rh.Company_No == 1 &&
((rh.Route_Date == routeDateToSearch && c.Route_Date == routeDateToSearch) ||
(rh.Route_Date == routeDateToSearch.AddDays(1) && c.Route_Date == routeDateToSearch.AddDays(1)))
orderby c.Unique_Id_No
select new
{
c.Datetime_Updated,
th_customerId = ("Query goes here somehow")
c.Datetime_Created,
c.Unique_Id_No,
c.Original_Unique_Id_No,
c.Unique_Id_Of_New_Stop,
c.Branch_Id,
c.Route_Date,
c.Route_Code,
c.Sequence_Code,
c.Customer_No,
c.Customer_Reference,
c.Shipment_Type,
c.Stop_Name,
c.Stop_Address,
c.Stop_City,
c.Stop_State,
c.Stop_Zip_Postal_Code,
c.Stop_Phone_No,
c.Stop_Arrival_Time,
c.Stop_Departure_Time,
c.Address_Point,
c.Stop_Special_Instruction1,
c.Stop_Special_Instruction2,
c.Stop_Expected_Pieces,
c.Stop_Expected_Weight,
c.Stop_Signature,
c.Actual_Arrival_Time,
c.Actual_Depart_Time,
c.Actual_Service_Date,
c.Stop_Actual_Pieces,
c.Stop_Exception_Code,
c.Created_By,
rh_Route_Date = rh.Route_Date,
routeHeaderRouteCode = rh.Route_Code,
rh.Actual_Driver,
rh.Assigned_Driver,
rh_routeDate = rh.Route_Date
}).ToArray();
I will try and clarify the above.
What I need is for the Linq query to say :
For each record that I pull I will goto the Array named dt_th_matrix and get the record that matches for this line and use it.
The data in the matrix looks exactly like this:
Record 1: datatrac_customer_no: 227, tophat_customer_detail_Id 1
Record 2: datatrac_customer_no: 228, tophat_customer_detail_Id: 1
Record 3: datatrac_customer_no: 910, tophat_customer_detail_Id: 5
Then for the first record pulled in the mainPull the field c.customer_no == 228 so I need the query in the select new statement to replace th_customerId with 1 (from Record 2 in the Matrix.
Then say the next record pulled in the mainPull the field c.customer_no = 910 the th_customerId would be 5.
That is what the first line of my foreach statement is currently doing. I want to move that logic to inside my LINQ query.
If I understand you correctly, using a dictionary with a key of datatrac_customer_no and a value of tophat_customer_detail_Id would be a good idea here:
var dt_th_matrix = (from m in aDb.Matrix_Datatrac_TopHat select m).ToDictionary(m=>m.datatrac_customer_no,m=>m.tophat_customer_detail_Id);
With this you should be able to replace your "Query goes here somehow" with
dt_th_matrix[c.Customer_No]
Using LINQ would be possible as well, but I don't think it's worth the performance overhead and reduction in readibility.
If you still want to use LINQ for this with your original matrix, this should work as your query:
dt_th_matrix.Single(m => m.datatrac_customer_no == c.Customer_No).tophat_customer_detail_Id
Both expressions will throw an exception if the key is not found or exists multiple times - but if I understand your structure correctly this should not be possible. Otherwise you need to check for this.
I got a little linq query searching for a customer in a database:
var query =
from c in database.customer
where c.ID == input
select c;
Every customer has an ID, which in this case is set by an user-"input"
All customers in database are from "Germany" but some shall be copied into the database with a different value for country ("England" instead of "Germany")
Now, before adding them to the database directly, I want to check if there is already an "english version" of this customer in the database.
if (query.Where(c => c.country == "England"))
//do nothing
else
//add the customer with c.country = "England"
The problem is that this is not a regular if-statement.
Is there a possible way to achieve what I want to express in this if-condition?
Thanks
just change condition to
if (query.Any(c => c.country.Equals("England")))
//do nothing
else
//add the customer with c.country = "England"
Linq method Where returns IEnumerable value wchich can't be automatically converted into bool value.
Methods like Any or All returns true or false based on whether condition is true for at least one element in collection or all of them respectively.
I'm trying to learn about linq queries. I have a list _taskDetail which contains 8 elements. I do not understand why the first query below returns the answer 8? Yes the list contains 8 elements but all the td.Names are different and I have specified td.Name == taskName so why is it returning everything even elements where the td.Name does not equal taskName?
The second query gives me the expected and correct answer of 1.
var Ans1 = _taskDetail.Select(td => td.Name == taskName).Count();
var Ans2 = (from tdList in _taskDetail
where tdList.Name == taskName
select tdList).Count();
Ans1 = 8
Ans2 = 1
The first version doesn't make sense, you need a Where, and not a Select which is a projection, not a filter.
The Select, in the first version, will return you an IEnumerable<bool> (for each item in the list, it will return true if Name == taskName and false if not. So all items will be returned, and the count will give you... the count of the list.
so
_taskDetail.Where(td => td.Name == taskName).Count();
By the way, you can simply do
_taskDetail.Count(td => td.Name == taskName);
Detail to maybe understand better
The second (wrong) version (query syntax) corresponding to your actual (wrong) first version (method syntax) would be
(from tdList in _taskDetail
select tdList.Name == taskName).Count();
It's because the first query is wrong. Select only produces a projection, it does NOT filter the results. The correct way to execute is using Where instead of Select...
var Ans1 = _taskDetail.Where(td => td.Name == taskName).Count();
I currently have a LINQ statement that returns an IQueryable to be displayed into a Telerik RadGrid. This statement is set to pull Records that match the Period inputted, and also have the "Premium" Column set to true. It then selects the EmployeeID & ProjectID distinctly using the GroupBy property.
These columns are then displayed in the RadGrid, along with a "PremiumCode" column.
Currently my statement works to display ALL of the records that meet the top credentials (Employee Name, Project, Premium Code), but my end Goal is to pull only those Records which DONT already have a "PremiumCode" assigned to the Project for that particular Employee.
public static IQueryable GetEmptyPremiums(string Period)
{
DataContext Data = new DataContext();
var PR = (from c in Data.System_Times
where c.Period == Period && c.Premium == true
orderby c.System_Employee.LName
select c).GroupBy(s => s.EmployeeID & s.ProjectID).Select(x => x.FirstOrDefault());
return PR;
}
Currently it is displaying properly, but every record is being displayed, not just the ones that require a PremiumCode.
Is there a way to re-work my LINQ statement to only include the records that need a PremiumCode?
EDIT:
Jay,
I have tried to modify your solution to fit my needs, but unfortunately with no success. Records in the Premium table are not added until a Premium Code is defined, therefore there will never be a null "PremiumCode".
To describe my end-goal a tad more clearly: I am looking to show the information in a grid like in the image above. The records shown will be the distinct Time records that have the bool value "Premium" checked as true but don't have a PremiumCode record in the Premium Table.
If the checked record has a matching record in the Premium table (EmployeeID, and ProjectID matching) then it already possesses a Premium Code set and will not need to be displayed in the Grid.
If the checked record has no matching record in the Premium table (EmployeeID, and ProjectID not matching) then it requires a PremiumCode and will need to be displayed in the Grid.
I believe this can be achieved with ".Any()" but I am having troubles aligning my Syntax and Logic to make this Grid display properly.
How about:
DataContext Data = new DataContext();
var projectsWithoutPremium = Data.Premiums.Where(p => p.PremiumCode == null)
.Select(p => p.ProjectId);
var PR = (from c in Data.System_Times
where c.Period == Period && c.Premium == true
&& projectsWithoutPremium.Contains(c.ProjectId)
orderby c.System_Employee.LName
select c).GroupBy(s => s.EmployeeID & s.ProjectID).Select(x => x.FirstOrDefault());
return PR;
update in response to question edit
DataContext Data = new DataContext();
var PR = (from c in Data.System_Times
where c.Period == Period && c.Premium == true
&& !Data.Premiums.Any(p => p.ProjectID == c.ProjectID && p.EmployeeID == c.ProjectID)
orderby c.System_Employee.LName select c)
.GroupBy(s => s.EmployeeID & s.ProjectID)
.Select(x => x.FirstOrDefault());
return PR;
If premium code is a string, you might want to try adding something like .Where(x => string.isNullOrEmpty(x.PremiumCode)) before the GroupBy clause.