I have 2 lists that I am trying to compare, sadly they are not of the save class. One is from the magento api and the other is a custom class containing some values from the other. (only the ones that I need)
if (existingPhotoOrders.Where(x => x.OrderNumber.CompareTo(order.increment_id) == 0).ToList().Count > 0)
continue;
There is a problem with the API where I have to get all the orders and compare the ones that I want and then request individually extra information but the above query is not working it is just continueing with everything.
I have looked at other comparisons but they all require the classes to be the same. If anyone could shed some light I would be grateful.
It can be re-written like so using the Any method:
if (existingPhotoOrders.Any(x => x.OrderNumber.CompareTo(order.increment_id) == 0))
continue;
As to why it is always continuing, the expression must always be true! I presume the requirement is if the order increment id is not an order number of an existing order then continue, in which case, you can use the All method:
if (existingPhotoOrders.All(x => x.OrderNumber.CompareTo(order.increment_id) != 0))
continue;
Or as a more performant alternative:
var orderIds = new HashSet<int>(existingPhotoOrders.Select(epo => epo.OrderNumber));
foreach (var order in orders)
{
if (!orderIds.Contains(order.increment_id))
{
continue;
}
// rest of method here
}
The type of x and the type of order are not important, however the types of OrderNumber and increment_id. Of what types are they?
If one is string and the other int then you should convert the int to string:
.Where(x => x.OrderNumber == order.increment_id.ToString())
Related
How do you index an IQueryable?
I am using a LINQ to sql query to get in values from a particular column. The query is as follows,
var intitalQuery = (from a in sql.GetTable<Staff_Time_TBL>()
where a.Info_Data == SelectedOption
select a.Staff_No).Distinct();
From there I want to be able index the intitalQuery variable and get values as needed.
That value is then used in another query.
My first try was this,
Column1.DataContext = sql.Staff_Time_TBLs.Where(item =>
item.Section_Data == SelectedOption &&
item.Staff_No == intitalQuery[0];
Then I tried this from here with no luck.
Column1.DataContext = sql.Staff_Time_TBLs.Where(item =>
item.Section_Data == SelectedOption &&
item.Staff_No == intitalQuery.First());
From what I can from the link is that that way gets just the first value, I want to be able to get all values via indexing. How do you go about that?
IQueryable<T> inherits from IEnumerable and as such has a wealth of extension methods to accomplish almost anything you'd need from a sequence. In particular, .ToList() turns an enumerable into a List<T> that allows efficient indexing.
.ToList() is slightly more efficient than the more obvious .ToArray() when working with sequences of unknown initial length, because .ToArray() requires an additional copy to end up with an array of exactly the right size. (But arrays are faster to loop over, so it all depends on what you're doing.)
You can do this:
public static List<Staff_Time_TBLs> GetIndexed(string staffNo){
var stuff = sql.Staff_Time_TBLs.Where(item =>
item.Section_Data == SelectedOption &&
item.Staff_No == staffNo;
return stuff.ToList();
}
//to use it...
initialQuery.ForEach(p=>{
var indexvalue = GetIndexed(p)
});
The following code shows how I am assigning data into IEnumerable<UnCompletedJobDetailsBO>.
There is a list (IEnumerable<UnCompletedJobDetailsBO>) that has another list (List<JobDetailsBO>), with that child list (List<JobDetailsBO>) having a list on it. But the AllocationDetailList only ever has one list item.
public IEnumerable<UnCompletedJobDetailsBO> GetControlDetails(DateTime startDate)
{
var controlDetails =
(from booking in db.BookingDetail
where booking.BookingDateTime >= startDate
orderby booking.DocketNo
select new UnCompletedJobDetailsBO()
{
CustomerName = booking.Customer.Name,
CompanyName = booking.CompanyDetail.Name,
JobList =
(from job in db.BookingJob.Where(x => x.BookingID == booking.BookingID) //get job list
select new JobDetailsBO()
{
JobID = job.JobID,
JobType = job.JobType,
ItemName = job.ItemName,
AllocationDetailList =
(from jobAllocationDetail in db.JobAllocation
join returnUnCollected in db.JobReturnUnCollected
on jobAllocationDetail.JobAllocationDetailID
equals returnUnCollected.JobAllocationDetailID
into returnJob
from returnUnCollected in returnJob.DefaultIfEmpty()
where (jobAllocationDetail.Booking.BookingID == booking.BookingID)
select new AllocationBO()
{
JobUnCollectedID = returnJob.JobUnCollectedID,
JobType = jobAllocationDetail.JobType,
CurrentStatus = jobAllocationDetail.CurrentStatus,
}).DefaultIfEmpty().ToList(),
}).DefaultIfEmpty().ToList(),
}).ToList();
return controlDetails;
}
I want to remove the JobList item if the inner list (AllocationDetailList) item satisfies the condition below. Sometimes AllocationDetailList may be null, so I check that also. But when I write below query, it does not remove that particular JobList item that satisfies the condition. Thanks in advance.
public List<UnCompletedJobDetailsBO> RemovePODFromSelectedList(
List<UnCompletedJobDetailsBO> unCompletedJobDetailsBO)
{
unCompletedJobDetailsBO
.SelectMany(y => y.JobList)
.ToList()
.RemoveAll(x => ((x.AllocationDetailList[0] != null) ?
x.AllocationDetailList[0].JobType == "D" &&
x.AllocationDetailList[0].JobUnCollectedID == null &&
x.AllocationDetailList[0].CurrentStatus == 5 :
x.AllocationDetailList.Count > 1));
return unCompletedJobDetailsBO;
}
Without a good, minimal, complete code example, I'm not sure that any performance concern can be addressed. It's hard enough to fully understand the question as it is, but without being able to actually test the code, to reproduce and observe a specific performance concern, it's hard to know for sure where your concern specifically lies, never mind how to fix it.
That said, from the code you posted, it is clear why items are not being removed from the list. The basic issue is that while the SelectMany() method does have the effect of allowing you to enumerate all of the elements from all of the different JobList objects as a single enumeration, the elements are enumerated as a new enumeration.
When you call ToList(), you are creating a whole new list from that new enumeration, and when you call RemoveAll(), you are only removing elements from that new list, not the lists from which they originally came.
You say you can get it to work with a for loop. I assume you mean something like this:
public List<UnCompletedJobDetailsBO> RemovePODFromSelectedList(
List<UnCompletedJobDetailsBO> unCompletedJobDetailsBO)
{
foreach (var item in unCompletedJobDetailsBO)
{
item.JobList.RemoveAll(x => ((x.AllocationDetailList[0] != null) ?
x.AllocationDetailList[0].JobType == "D" &&
x.AllocationDetailList[0].JobUnCollectedID == null &&
x.AllocationDetailList[0].CurrentStatus == 5 :
x.AllocationDetailList.Count > 1));
}
return unCompletedJobDetailsBO;
}
Note: there is no need to return unCompletedJobDetailsBO. That entire object is unchanged, never mind the variable. The only thing the code is modifying is each individual JobList object within the passed-in object's members. I.e. the above method could actually have a return type of void, and the return statement could be removed entirely.
It is possible you could speed the code up by removing the elements in a different way. The List<T>.RemoveAll() method is in fact reasonably efficient, with O(n) cost. But it still involves copying all of the data in the list after the first element that is removed (so that all the elements are shifted down in the list). If you have to have the list ordered, this may be as good as you can do, but if not, you could process the removal differently, or use a different data structure altogether, something unordered where removal of one or more elements costs less.
But again, without more details and without a good example to work with addressing that particular issue doesn't seem practical here.
The condition
x.AllocationDetailList[0] != null
will throw exception if there is no item in the AllocationDetailList. Instead you need to check
x.AllocationDetailList!=null && x.AllocationDetailList.Count>0.
Also .ToList() after SelectMany in your code will create a new list and items will be removed from that new list instead of unCompletedJobDetailsBO. You need to modify the remove function as below
unCompletedJobDetailsBO.ForEach(y => y.JobList.RemoveAll(x => ((x.AllocationDetailList != null && x.AllocationDetailList.Count>0)
?
x.AllocationDetailList[0].JobType == "D"
&& x.AllocationDetailList[0].JobUnCollectedID == null
&& x.AllocationDetailList[0].CurrentStatus == "5"
:
x.AllocationDetailList.Count > 1
)
));
I'm trying to group a collection of custom entity object by username, then by position. For a specific user, when I get to the second 'groupby', I always get the error:
"At least one object must implement IComparable."
The user in question has a username "#gill'n it". When I execute the first "GroupBy" on that suername, it returns an anonymous type of Count=6, which is correct, but the number of elements in the type is 8 where the final two are null. I think those final two nulls are causing my problem, but I can't figure out where they're coming from.
List<CheatSheet> allRelevantCheatSheets = CheatSheet.GetCheatSheets(Globals.FOOString) // only grade football sheets
.Where(x => x.Username != String.Empty) // only grade user sheets
.Where(x => x.SeasonCode == currrentFOOSeason) // only grade sheet for the current season
.Where(x => x.LastUpdated < kickoffDate) // only grade sheets before the kickoff date
.Where(x => x.Positions.Count == 1) // only grade single-position sheets
.Where(x => (bool)x.MappedProperties[CSProperty.PPRLeague.ToString()] == false) // only grade standard socring sheets
.ToList();
foreach (var userSheetGroup in allRelevantCheatSheets.GroupBy(x => x.Username).OrderBy(x => x.Key))
{
int newCheatSheetID = 0;
groupCounter++;
// then group userSheets by position
foreach (var targetUserPositionalSheetGroup in userSheetGroup.GroupBy(x => x.Positions[0]).OrderBy(x => x.Key))
{
// finally limit the type of sheet returned to only 1 (the latest one ordered by date), must cast to list in order to avoid casting error, then take first item
CheatSheet userTopPositionSheet = (CheatSheet)targetUserPositionalSheetGroup.OrderBy(x => x.CheatSheetID).OrderBy(x => x.Positions[0]).OrderByDescending(x => x.LastUpdated).Take(1).ToList()[0];
newCheatSheetID = ArchiveCheatSheet(userTopPositionSheet);
if (newCheatSheetID == 0)
{
errorCounter++;
}
else
{
sheetCounter++;
ArchiveCheatSheetItems(userTopPositionSheet.CheatSheetID, newCheatSheetID);
}
}
}
First, the null values are a red herring. Internal implementations of collections in .NET often seem to have underlying element counts that are powers of two, likely to maintain amortized time in collection manipulation operations. The count of 6 is correct.
Second, if the type of x.Positions[0] does not implement IComparable, and no default comparer can be found, you would get the specified exception due to .OrderBy(x => x.Key) because there is no definition of how to sort the elements. If this is the case, use the two-argument extension method (three-argument if you count this) that allows you to specify the IComparer to use: http://msdn.microsoft.com/en-us/library/bb549422(v=vs.110).aspx. Or, if you have ownership of the type of x.Positions[0], modify it to implement IComparable.
I have a for each loop to get data which is very time consuming.any suggestion to convert this to linq. Thanks in advance.
iListReport = obj.GetClosedReports();
string sRepType ="";
foreach (ReportStatisticsInfo item in reportStatistic)
{
sRepType = item.ReportName.Trim();
IList<string> lastClosedReport = new List<string>();
foreach (TaskListInfo taskInfo in iListReport)
{
string reportName = taskInfo.DocumentName.Trim();
if (string.Compare(sRepType, reportName, true) == 0)
{
if (taskInfo.ActionID == Convert.ToInt16(ReportAction.Close) && !lastClosedReport.Contains(taskInfo.DocumentID))
{
iClosedreportCount += 1;
lastClosedReport.Add(taskInfo.DocumentID);
}
}
}
}
Here you go. I've done a pretty literal translation of your code into LINQ which will hopefully help you to see how I've converted it.
Note the use of the let keyword which allows you to declare a range variable (which allows you to perform your trim once and then use the result in multiple places).
Also note the use of group by at the bottom of the LINQ query to ensure we only take the first occurence of each documentID.
IList iListReport = obj.GetClosedReports();
var query = from item in reportStatistic
let sRepType = item.ReportName.Trim()
from taskInfo in iListReport
let reportName = taskInfo.DocumentName.Trim()
where string.Compare(sRepType, reportName, true) == 0
&& taskInfo.ActionID == Convert.ToInt16(ReportAction.Close)
//here's how we make sure we don't get the same documentID twice
//we group by the id and then take the first
group taskInfo by taskInfo.DocumentID into grouping
select grouping.First().DocumentID;
var lastClosedReport = query.ToList();
iClosedreportCount = lastClosedReport.Count;
How to convert a foreach loop to LINQ
Here are some comparisons of your code against LINQ version to help you out if you've got to do a conversion again sometime. Hopefully this will help anyone else out there that has got to convert a foreach loop to LINQ.
1. foreach and from
You can perform a straight swap of the foreach clause for a LINQ from clause. You can see that this:
foreach (ReportStatisticsInfo item in reportStatistic)
has become this:
from item in reportStatistic
2) Variable declaration and the let keyword
When you declare variables within your foreach, you can swap them out for the LINQ let statement. You can see that this declaration:
sRepType = item.ReportName.Trim();
has become:
let sRepType = item.ReportName.Trim()
3) if statements and the where clause
Your if statements can go inside the where clause. You can see that the following two if statements:
if (string.Compare(sRepType, reportName, true) == 0)
if (taskInfo.ActionID == Convert.ToInt16(ReportAction.Close)
have become this where clause
where string.Compare(sRepType, reportName, true) == 0
&& taskInfo.ActionID == Convert.ToInt16(ReportAction.Close)
4) Using group by to remove duplicates.
It's all been quite simple so far because everything has just been a straight swap. The most tricky part is the bit of code where you prevent duplicates from appearing in your result list.
if (taskInfo.ActionID == Convert.ToInt16(ReportAction.Close)
&& !lastClosedReport.Contains(taskInfo.DocumentID))
{
iClosedreportCount += 1;
lastClosedReport.Add(taskInfo.DocumentID);
}
This is tricky because it's the only part that we have to do a bit differently in LINQ.
Firstly we group the 'taskInfo' by the 'DocumentID'.
group taskInfo by taskInfo.DocumentID into grouping
Then we take the first taskInfo from each grouping and get it's ID.
select grouping.First().DocumentID;
A note about Distinct
A lot of people try to use Distinct to get rid of duplicates. This is fine when we're using primitive types, but this can fail when you're using a collection of objects. When you're working with objects Distinct will do a reference comparison of the two objects. This will fail to match objects that are different instances but happen to have the same ID.
If you need to remove duplicates based upon a specific property within an object, then the best approach is to use a group by.
With LINQ you'll get a single IEnumerable<string> with duplicates
from item in reportStatistic
from taskInfo in iiListReport
where (string.Compare(item.ReportName.Trim(), taskInfo.DocumentName.Trim(), true) == 0)
&& taskInfo.ActionID == Convert.ToInt16(ReportAction.Close)
select taskInfo.DocumentID
You can then Distinct().GroupBy(d => d.taskInfo)
Consider the requirement to change a data member on one or more properties of an object that is 5 or 6 levels deep.
There are sub-collections that need to be iterated through to get to the property that needs inspection & modification.
Here we're calling a method that cleans the street address of a Employee. Since we're changing data within the loops, the current implementation needs a for loop to prevent the exception:
Cannot assign to "someVariable" because it is a 'foreach iteration variable'
Here's the current algorithm (obfuscated) with nested foreach and a for.
foreach (var emp in company.internalData.Emps)
{
foreach (var addr in emp.privateData.Addresses)
{
int numberAddresses = addr.Items.Length;
for (int i = 0; i < numberAddresses; i++)
{
//transform this street address via a static method
if (addr.Items[i].Type =="StreetAddress")
addr.Items[i].Text = CleanStreetAddressLine(addr.Items[i].Text);
}
}
}
Question:
Can this algorithm be reimplemented using LINQ? The requirement is for the original collection to have its data changed by that static method call.
Update: I was thinking/leaning in the direction of a jQuery/selector type solution. I didn't specifically word this question in that way. I realize that I was over-reaching on that idea (no side-effects). Thanks to everyone! If there is such a way to perform a jQuery-like selector, please let's see it!
foreach(var item in company.internalData.Emps
.SelectMany(emp => emp.privateData.Addresses)
.SelectMany(addr => addr.Items)
.Where(addr => addr.Type == "StreetAddress"))
item.Text = CleanStreetAddressLine(item.Text);
var dirtyAddresses = company.internalData.Emps.SelectMany( x => x.privateData.Addresses )
.SelectMany(y => y.Items)
.Where( z => z.Type == "StreetAddress");
foreach(var addr in dirtyAddresses)
addr.Text = CleanStreetAddressLine(addr.Text);
LINQ is not intended to modify sets of objects. You wouldn't expect a SELECT sql statement to modify the values of the rows being selected, would you? It helps to remember what LINQ stands for - Language INtegrated Query. Modifying objects within a linq query is, IMHO, an anti-pattern.
Stan R.'s answer would be a better solution using a foreach loop, I think.
I don't like mixing "query comprehension" syntax and dotted-method-call syntax in the same statement.
I do like the idea of separating the query from the action. These are semantically distinct, so separating them in code often makes sense.
var addrItemQuery = from emp in company.internalData.Emps
from addr in emp.privateData.Addresses
from addrItem in addr.Items
where addrItem.Type == "StreetAddress"
select addrItem;
foreach (var addrItem in addrItemQuery)
{
addrItem.Text = CleanStreetAddressLine(addrItem.Text);
}
A few style notes about your code; these are personal, so I you may not agree:
In general, I avoid abbreviations (Emps, emp, addr)
Inconsistent names are more confusing (addr vs. Addresses): pick one and stick with it
The word "number" is ambigious. It can either be an identity ("Prisoner number 378 please step forward.") or a count ("the number of sheep in that field is 12."). Since we use both concepts in code a lot, it is valuable to get this clear. I use often use "index" for the first one and "count" for the second.
Having the type field be a string is a code smell. If you can make it an enum your code will probably be better off.
Dirty one-liner.
company.internalData.Emps.SelectMany(x => x.privateData.Addresses)
.SelectMany(x => x.Items)
.Where(x => x.Type == "StreetAddress")
.Select(x => { x.Text = CleanStreetAddressLine(x.Text); return x; });
LINQ does not provide the option of having side effects. however you could do:
company.internalData.Emps.SelectMany(emp => emp.Addresses).SelectMany(addr => Addr.Items).ToList().ForEach(/*either make an anonymous method or refactor your side effect code out to a method on its own*/);
You can do this, but you don't really want to. Several bloggers have talked about the functional nature of Linq, and if you look at all the MS supplied Linq methods, you will find that they don't produce side effects. They produce return values, but they don't change anything else. Search for the arguments over a Linq ForEach method, and you'll get a good explanation of this concept.
With that in mind, what you probaly want is something like this:
var addressItems = company.internalData.Emps.SelectMany(
emp => emp.privateData.Addresses.SelectMany(
addr => addr.Items
)
);
foreach (var item in addressItems)
{
...
}
However, if you do want to do exactly what you asked, then this is the direction you'll need to go:
var addressItems = company.internalData.Emps.SelectMany(
emp => emp.privateData.Addresses.SelectMany(
addr => addr.Items.Select(item =>
{
// Do the stuff
return item;
})
)
);
To update the LINQ result using FOREACH loop, I first create local ‘list’ variable and then perform the update using FOREACH Loop. The value are updated this way. Read more here:
How to update value of LINQ results using FOREACH loop
I cloned list and worked NET 4.7.2
List<TrendWords> ListCopy = new List<TrendWords>(sorted);
foreach (var words in stopWords)
{
foreach (var item in ListCopy.Where(w => w.word == words))
{
item.disabled = true;
}
}