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)
});
Related
I'm working on an API; how can I set my linq query up to return the max value with a where condition?
See the example code below; I can return the max value of the field I want, but I need to filter it where another column value equals something.
var lot = db.ShrinkLotData.Where(x => x.SupplierMfgLot.ToLower() == label.SupplierMfgLot.ToLower() && x.CatPattern.ToLower() == label.CatPattern.ToLower())
.SingleOrDefaultAsync();
if (lot.Result == null)
{
var lots = db.ShrinkLotData.Where(x => x.CatPattern.ToLower() == label.CatPattern.ToLower());
int internallot = db.ShrinkLotData.Max(x => x.InternalLotNum).Value;
return Ok(lot);
}
return Ok(lot);
}
for the internallot, I want to return the highest value using similar syntax as the lots syntax.. (Where the catpattern equals a specific value)
What am I overlooking?
Thanks!
If I understand correctly, you basically need to use Where and Max together, so that you can select max value with a where condition.
db.ShrinkLotData.Where(x => x.CatPattern.ToLower() == label.CatPattern.ToLower()).Max(x => x.InternalLotNum).Value;
More Info : Composability of Queries:
..., you compose them in method syntax by
chaining the method calls together. This is what the compiler does
behind the scenes when you write queries by using query syntax. And
because a query variable does not store the results of the query, you
can modify it or use it as the basis for a new query at any time, even
after it has been executed.
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 have columns list in which I need to assign Isselected as true for all except for two columns. (Bug and feature). I have used this following code to achieve it and working fine, but is there any quick or easy way to achieve the same?
DisplayColumns.ToList().ForEach(a => a.IsSelected = true);
DisplayColumns.ToList().Where(a => a.ColumnName == "Bug" || a.ColumnName == "Feature").ToList().ForEach(a => a.IsSelected = false);
Thanks in advance
I have used this following code to achieve it and working fine, but is there any quick or easy way to achieve the same?
Well there's a cleaner way to achieve it in my view - just don't use lambdas etc at all:
foreach (var item in DisplayColumns)
{
item.IsSelected = item.ColumnName != "Bug" && item.ColumnName != "Feature";
}
You can make the decision in one go - it's false if the column name is either "bug" or "feature"; it's true otherwise. And you don't need to call ToList and use ForEach when the C# language has a perfectly good foreach loop construct for when you want to execute some code using each item in a collection.
I love LINQ - it's fantastic - but its sweet spot is querying (hence the Q) rather than manipulation. In this case only the ToList part is even part of LINQ - List<T>.ForEach was introduced in .NET 2.0, before LINQ.
Sure, you can assign the IsSelected at once.
DisplayColumns.ToList().ForEach(a => a.IsSelected = !(a.ColumnName == "Bug" || a.ColumnName == "Feature"));
Provided that DisplayColumns isn't a projection of an anonymous type (in which case the properties are not re-assignable), you'll be able to change the flag in a single pass iteration through the collection.
You can also use Contains to ease the comparison. At class scope:
private static readonly string[] _searches = new [] {"Bug", "Feature"}
In your method:
DisplayColumns
.ToList() // For List.ForEach, although not #JonSkeet's caveat re mutating in Linq
.ForEach(a => a.IsSelected = !_searches.Contains(a.ColumnName));
Edit
As others have mentioned, creation of a new list simply to gain access to .ForEach to change objects in the (original) collection is wasteful and changes will be lost on a collection of value types. Rather, iterate over the original collection with foreach (or even for).
Firstly you only need to call ToList() once when creating a collection from your IEnumerable.
doing this after each operator is costly and redundant.
Secondly just change your condition . all true except for the tow.
DisplayColumns.Where(a => a.ColumnName != "Bug" && a.ColumnName != "Feature").ForEach(a => a.IsSelected = true).ToList();
Edit :
I'm sorry i like a part john's answer since this can be a re occurring thing , or IsSelected could be a Nullable , any ways lets keep it as general as possible .
I also like Stuart's approach , with the collection ( i also thought of it but didn't want to confuse . so let's give the best of all worlds.
when using linq we are actually building an expression tree at the end of which we can choose to materialize into a collection.
there for _searchs can change and each time we materialize that expression we do it with the values currently in that collection , thous making our code much more general .
private static readonly string[] _searches = new [] {"Bug", "Feature"}
DisplayColumns.ForEach(a => a.IsSelected = !_searchs.Contains(a.ColumnName)).ToList();
I'm assuming ForEach is an Extension method for type IEnumrable
Maybe this:
tmp = DisplayColumns.ToList();
var res = tmp.Except(tmp.Where(a => a.ColumnName == "Bug" || a.ColumnName == "Feature"));
foreach(var x in res) x.IsSeleceted = true;
Without using foreach
DisplayColumns
.Select(s=> {
s.IsSelected = (s.ColumnName == "Bug" && s.ColumnName == "Feature");
return s;
});
I know you can use Any, Exists, and Single with LINQ but can't quite get this to work. I need to do a lookup based on an id to see if it's in the array and make sure that there is only ONE match on that value. because if there are 2 it's gonna cause an issue..the requirement that I'm checking is that the array only has one and only one of each ID in the array.
Here's what I tried
if(someIntArray.Single(item => item = 3)
//... we found the value 8 in the array only once so now we can be confident and do something
Here's how I would solve this:
if (someIntArray.Count(item => item == 3) == 1)
{
//only one '3' found in the array
...
}
I created a One() extension method set for just this situation:
public static bool One<T>(this IEnumerable<T> sequence)
{
var enumerator = sequence.GetEnumerator();
return enumerator.MoveNext() && !enumerator.MoveNext();
}
public static bool One<T>(this IEnumerable<T> sequence, Func<T, bool> predicate)
{
return sequence.Where(predicate).One();
}
//usage
if (someIntArray.One(item => item == 3)) ...
The problem with Single() is that it throws an exception if there isn't exactly one element. You can wrap it in a try-catch, but these are cleaner, and more efficient than Count() in most cases where there's more than one matching element. Unfortunately, there's no way around having to check the entire array to verify that there are either no elements or only one that matches a predicate, but this will at least "fail fast" if there are two or more, where Count() will always evaluate the entire Enumerable whether there's one matching element or fifty.
I think you're overthinking this.
var targetNumber = 3;
var hasExactlyOne = someIntArray.Count(i => i == targetNumber) == 1;
Using LINQ expression:
var duplicates = from i in new int[] { 2,3,4,4,5,5 }
group i by i into g
where g.Count() > 1
select g.Key
Results:
{4,5}
And of course you could check duplicates.Count() > 0 or log the ones that are a problem or whatever you need to do.
got it working:
if(someIntArray.Single(item => item = 3) > 0)
doh
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())