How would I order a list of items where some of the items contain double quotes?
Advance
Access
“Chain free” deal
Binding
Broker
Doing this FaqData = repo.FaqData.OrderBy(q => q.Description) results in the following
“Chain free” deal
Advance
Access
Binding
Broker
Tried this as well
FaqData = repo.FaqData.OrderBy(q => q.QuestionDescription.Replace("”", ""))
FaqData = repo.FaqData.OrderBy(q => q.Description.Replace(#"""",""))
OrderBy calls the delegate once per item contained in the list being sorted. The delegate should return a value which, when compared to values obtained in the same way for other items in the list, will provide a value that can be sorted.
Typically the value returned in the delegate is a property of the listed item - but because its code, it could return anything you like, including values that arn't anything to do with the items in the list.
In this example instead of returning the list property ".Description" the code is returning a new string value derived from the ".Description" property. The derivation is simply to use the .net String.Replace to replace all double-quote values with an empty string.
This means the sorting algorithm sorts on the ".Description" with double-quotes removed.
This is not very efficient if you call this sorting code many times, and could easily be done differently; either by adding a new property to the class being sorted as such;
public string PlainTextDescription
{
get {
return this.Description.Replace(#"""","");
}
}
and sorting like this;
FaqData = repo.FaqData.OrderBy(q => q.PlainTextDescription)
or by pre-populating the PlainTextDescription field using the logic, but only when the .Description value changes; this would be much more efficient because the String.Replace would only be called once each time the .Description changes - with the example above, the String.Replace code must be called every time the sorter needs to evaluate the PlainTextDescription field, which means we're doing the String.Replace many times instead of once.
I'm thinking you not only want to ignore quotes but anything that isn't A-Z.
All you need to do is include a function to Where that strips out anything you don't want. To do that you can use a regular expression, like this:
var filtered = Regex.Replace(s, #"[^A-Za-z0-9]","")
Now to put it in a Where statement:
var tests = new[] { "Advance","Access","Binding","Broker",#"""Chain free"" deal","`Twas the night before Christams","#NotAllMen","Zenit","Quickly"};
var sorted = tests.OrderBy(s => Regex.Replace(s, #"[^A-Za-z0-9]",""));
foreach (var s in sorted)
{
Console.WriteLine(s);
}
Output:
Access
Advance
Binding
Broker
"Chain free" deal
#NotAllMen
Quickly
`Twas the night before Christams
Zenit
Code on DotNetFiddle
Related
I got a class called BG which has a property called Name Code.
I instantiate an object called bgList.
Now I am trying to get all the Code of the objects which have their 'Crop' property set to cropName.
I would like to convert the following working code to linq but for the life of me am unable to do that - am quite sure that I am missing something:
List<string> breedingGroupsAndRoles = new List<string>();
for (int i = 0; i < bgList.Count; i++)
{
if (bgList[i].Crop == cropName)
breedingGroupsAndRoles.Add(bgList.[i].Code);
}
The closest I came was this but it only nets me the first item:
breedingGroupsAndRoles.Add(bgrList.Find(c => c.Crop == cropName).Role);
List<string> breedingGroupsAndRoles = bgList
.Where(bg => bg.Crop == cropName)
.Select(bg => bg.Code)
.ToList();
Just for the sake of completeness, the Find method you tried calling on bgList is not part of LINQ, it's a member of the generic List class itself. It only returns the first element matched by the predicate you provide, which is why you were only getting one result. You probably wanted the FindAll method, which returns a list of all matching elements:
List<BG> breedingGroups = bgList.FindAll(c => c.Crop == cropName);
Note that this produces a list of matching BG instances rather than just their Role properties. Depending on how you're processing the results this may be sufficient, otherwise you'll still need LINQ or a loop and a second list to extract the Role values. In any case, an all-LINQ solution such #Tim Schmelter's is likely the better way to go.
I have a string:
strCheckedCategories = "2;"
an EntityList representing a SharePoint list, with item IDs from 1 to 21:
EntityList<VendorSearchesItem> vendorSearches =
dataContext.GetList<VendorSearchesItem>("Vendor Searches");
a LINQ query returning fields from two SharePoint lists that are joined to the "Vendor Searches" list:
var vendorSearchesQuery = (from s in vendorSearches
orderby s.VendorID.Title
select new
{
Vendor = s.VendorID.Title,
Website = s.VendorID.VendorWebsite,
VendorID = s.VendorID.Id,
SearchType = s.SearchTypeID.Title,
SearchTypeId = s.SearchTypeID.Id
});
and another LINQ query returning only the items where the item ID is in the list:
var q2 = from m2 in vendorSearchesQuery
where strCheckedCategories.Contains(m2.SearchTypeId.ToString())
select m2
The problem is that, in addition to returning the item with ID 2 (desired result) the query also returns items with ID 12, 20, and 21. How can I fix that?
So, fundamentally, what you want to do here is have an IN clause in which you specify a bunch of values for a field and you want rows who's value for that column is in that set.
CAML does actually have an IN clause which you could use, but sadly LINQ to Sharepoint doesn't provide any means of generating an IN clause; it's simply not supported by the query provider.
You're trying to use a bit of a hack to get around that problem by trying to do a string comparison rather than using the proper operators, and you're running into the pitfals of stringifying all of your operations. It's simply not well suited to the task.
Since, as I said, you cannot get LINQ to SharePoint to use an IN, one option would simply be to not use LINQ, build the CAML manually, and execute it using the standard server object model. But that's no fun.
What we can do is have a series of OR checks. We'll see if that column value is the first value, or the second, or the third, etc. for all values in your set. This is effectively identical to an IN clause, it's just a lot more verbose.
Now this brings us to the problem of how to OR together an unknown number of comparisons. If it were ANDs it'd be easy, we'd just call Where inside of a loop and it would AND those N clauses.
Instead we'll need to use expressions. We can manually build the expression tree ourselves of a dynamic number of OR clauses and then the query provider will be able to parse it just fine.
Our new method, WhereIn, which will filter the query to all items where a given property value is in a set of values, will need to accept a query, a property selector of what property we're using, and a set of values of the same type to compare it to. After we have that it's a simple matter of creating the comparison expression of the property access along with each key value and then ORing all of those expressions.
public static IQueryable<TSource> WhereIn<TSource, TKey>(
this IQueryable<TSource> query,
Expression<Func<TSource, TKey>> propertySelector,
IEnumerable<TKey> values)
{
var t = Expression.Parameter(typeof(TSource));
Expression body = Expression.Constant(false);
var propertyName = ((MemberExpression)propertySelector.Body).Member.Name;
foreach (var value in values)
{
body = Expression.OrElse(body,
Expression.Equal(Expression.Property(t, propertyName),
Expression.Constant(value)));
}
return query.Where(Expression.Lambda<Func<TSource, bool>>(body, t));
}
Now to call it we just need the query, the property we're filtering on, and the collection of values:
var q2 = vendorSearchesQuery.WhereIn(vendor => vendor.SearchTypeId
, strCheckedCategories.Split(';'));
And voila.
While I'd expect that to work as is, you may need to call the WhereIn before the Select. It may not work quite right with the already mapped SearchTypeId.
You should probably use a Regex, but if you want a simpler solution then I would avoid string searching and split those strings to an array:
string strCheckedCategories = "2;4;5;7;12;16;17;19;20;21;";
string[] split = strCheckedCategories.Split(';');
It will create an empty entry in the array for the trailing semicolon delimiter. I would check for that and remove it if this is a problem:
strCheckedCategories.TrimEnd(';');
Finally now you can change your where clause:
where split.Contains(m2.SearchTypeId.ToString())
If you have a very large list it is probably worth comparing integers instead of strings by parsing strCheckedCategories into a list of integers instead:
int[] split = strCheckedCategories.Split(';').Select(x => Convert.ToInt32(x)).ToArray();
Then you can do a quicker equality expression:
where split.Contains(m2.SearchTypeId)
try:
strCheckedCategories.Split(new []{';'}).Any(x => x == m2.SearchTypeId.ToString())
Contains will do a substring match. And "20" has a substring "2".
var q2 = from m2 in vendorSearchesQuery
where strCheckedCategories.Split(';').Contains(m2.SearchTypeId.ToString())
select m2
var q2 = from m2 in vendorSearchesQuery
where strCheckedCategories.Contains(";" + m2.SearchTypeId + ";")
select m2
And your strCheckedCategories should always end with ; and start with ;, for example ;2;, ;2;3;, ...
NOTE: This trick works only when your SearchTypeId should always not contain ;. I think you should use another kind of separator like \n or simply store your checked categories in a list or some array. That's the more standard way to do.
I am trying to use EF 5 to apply multiple search criteria to a result set (in this case, for a library catalog search). Here is the relevant code:
public IQueryable<LibraryResource> GetSearchResults(string SearchCriteria, int? limit = null)
{
List<string> criteria = SearchCriteria.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
IQueryable<LibraryResource> allResults = context.LibraryResources.Include("Type").Where(r => r.AuditInfo.DeletedAt == null);
foreach (string criterion in criteria)
{
allResults = allResults.Where(r => (r.Title.Contains(criterion) || r.Keywords.Contains(criterion) || r.Author.Contains(criterion) || r.Comments.Contains(criterion)));
}
allResults = allResults.OrderBy(r => r.Title);
if (limit.HasValue) allResults = allResults.Take(limit.Value);
return allResults;
}
Sample SearchCriteria = "history era"
For some reason, only the last criterion gets applied. For instance, in the sample above, all the books with "era" in the title, author, keywords and comments are returned, without also filtering by "history". I stepped through the code, and the loop executes twice, with the appropriate criterion each time. Can you see something I can't? Thanks!
You have fallen victim to modifying the value of a closed-over variable.
Change the code to this:
foreach (string criterion in criteria)
{
var crit = criterion;
allResults = allResults.Where(/* use crit here, not criterion */);
}
The problem here is that while you are building up the query your filtering expressions close over the variable criterion, in effect pulling it in scope at the point where the query is evaluated. However, at that time criterion will only have one value (the last one it happened to loop over) so all but the last of your filters will in fact be turned into duplicates of the last one.
Creating a local copy of criterion and referencing that inside the expressions corrects the problem because crit is a different local variable each time, with lifetime that does not extend from one iteration of the loop to the next one.
For more details you might want to read Is there a reason for C#'s reuse of the variable in a foreach?, where it is also mentioned that C# 5.0 will take a breaking change that applies to this scenario: the lifetime of the loop variable criterion is going to change, making this code work correctly without an extra local.
I have a standard List<Uri> defined that contains a short list of items. I'd like to iterate over the list using a foreach() but would like to 'bring to the top' those items that contain a specific string value in order to process them first. Is this possible with the OrderBy() and, better yet, is it possible in a single line?
Thanks!
You can do that:
foreach(var uri in uriList.OrderByDescending(uri => uri.ToString().Contains("foo"))
{
// Use uri
Yes. you can use OrderByDescending() using an order that returns a boolean - example:
var results = items.OrderByDescending( x => x.Name=="Herbert").ToList();
In this case the order would return true for "Herbert" and false for all other values. All true values will be ordered after all false values - we reverse the order by using OrderByDescending() and have the desired outcome.
Adapted to your Uri list and Contains() which also returns a boolean this would mean:
foreach(var uri in uriList.OrderByDescending(x => x.ToString().Contains(someString))
{
//..
}
The others used OrderByDescending, which will work, but you asked for using OrderBy, so here:
yourList.OrderBy(u => u.AbsoluteUri.Contains("somevalue") ? string.Empty : u.AbsoluteUri);
Let's say I have a list of User objects with two properties...ID and Name
List<User> lst = List<User>();
I fill it up with a bunch of Users. Ok, now I want to trim my list using RemoveAll() and this function.
private Boolean IsExisting(int id) {
//blah blah
return true;
//blah blah
return false;
}
So I use this statement:
gdvFoo.DataSource = lst.RemoveAll(t => IsExisting(t.ID));
It is my understanding that whenever IsExisting returns true that element should be removed from lst, but what happens, strangely enough, is it returns an integer?, not a truncated list and I received the following error message:
Data source is an invalid type. It must be either an IListSource, IEnumerable, or IDataSource.>
List.RemoveAll method
The method removes all matching instances from the list on which you called it. This modifies the existing list, rather than returning a new one.
The return value is the number of rows removed.
RemoveAll() returns the number of elements removed.
You need to do this:
lst.RemoveAll(t => IsExisting(t.ID));
gdvFoo.DataSource = lst;
The docs are very clear about what's going on:
Return Value
Type: System.Int32
The number of elements removed from the List .
Perhaps the following Linq would be more in line with your expectations?
lst.Except(t => IsExisting(t.ID)).ToList();
Instead of RemoveAll(), you could try using IEnumerable's filter where you would say something like :
var filteredList = lst.Where(item => IsExisting(item.Id))
This makes the code a little more easier to read and focusses on the objective of the task at hand, rather than how to look at implementing it.
List<T>.RemoveAll(...) has a return type of int which is not an IListSource, IEnumerable nor IDataSource
The RemoveAll is modifying the list and returning the number of items removed. You just set your datasource to the list in a second step.
lst.RemoveAll(t => IsExisting(t.ID));
gdvFoo.DataSource = lst;