I am looking to optimize some code to less lines and without the need for "for loops" using LINQ if possible. I saw a similar post asking for Select and Where in a single line but it wasn't exactly the same.
Suppose I have:
A list of elements in "fields" which has properties "Id" and "Name" which can be retrieved calling respectively .Id and .Name
Ex.
fields[0] = Element
fields[0].Id = 12345
fields[0].Name = Name01
I want to create a new list "filteredIds" containing the Id properties of selected fields.
This is the for loop version:
List<Id> filteredIds = new List<Id>();
fields = {Element1, Element2, ...}; //List of Elements
List<string> selectedNames = new List<string>() {"Name01", "Name05", "Name10"};
foreach (Element e in fields):
if (selectedNames.Contains(e.Name())
{
filteredIds.Add(e.Id);
}
Can this be done in a single line like this in LINQ?
filteredIds = fields.Select(i => i.Id).Any(o => selectedNames.Contains(o.Name)).ToList();
Any() returns true/false values. You need to call Where() to actually filter results.
filteredIds = fields.Where(o => selectedNames.Contains(o.Name)).Select(i => i.Id).ToList();
Almost correct. You should use Where to filter the list, not Any.
Any returns a boolean which is true if at least one element in the list satisfies the predicate, while Where returns all the elements that satisfy the predicate.
You also need to apply the Where filter before the Select, as the name property is removed by the select.
I have a list of items (InputList). Along with this I have a list of names (Names). Now I need only those items from InputList whose names are present in the Names list.
One solution is that I do a loop on Names list and check if it is present in InputList or not.
foreach (var name in names)
{
var result = InputList.FirstOrDefault(i => i.Name == name);
if (result != null)
outputList.Add(result);
}
There is IN operator in SQL in which we provide list and query returns desired result. Can I achieve the same using Linq?
That you want is the following:
var result = inputList.Where(x=>Names.Contains(x.Name));
the above query selects all the elements of inputList, whose name is contained in the Names list.
The signature of Contains method is bool Contains(T item), where T is the type of objects that are contained in your list. If the item you pass to this method is contained in your list returns true. Otherwise, it returns false.
For further documentation on the Contains method, please have a look here.
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 have a Linq query of which contains a List within a list. I thought I only wanted to have First record so I had the select part of my query written like this:
select new
{
EtchVectors = vio.Shapes.FirstOrDefault().Formatted
}).ToList();
This works great, it returns first record and the list that I have aliased "vio" has a list in it ( public List Shapes { get; set; } and Parse contains 2 properties, Formatted and Original. As I am rewriting this it seems I do not have access to "Formatted" if I get rid of FirstOrDefault()
This returns BOTH Formatted and Original obviously
EtchVectors = vio.Shapes
but, I cannot obviously do this:
EtchVectors = vio.Shapes().Formatted ( Shapes cannot be used like a method)
Should I be doing a different method or using a lambda ??
I think you are looking for a projection
EtchVectors = vio.Shapes.Select( s => s.Formatted );
Say I have this list of strings that I'm using to create a list of objects.
My list of strings has a count of 171 items in it, while my list of objects has 170. So one did not passed, but I need to figure out which one.
Luckily, all the strings can be found in each name of each object. That means that, for example:
string nameObjOne
Will equals to this:
public class myObj
{
public string myName {get;set;}
}
So how can I check if all the strings in my first list are located in the list of objects so that I may figure out which one is not there?
You can select items that are in List<string> but does not have corresponding item in List<myObj> using LINQ:
var results = strings.Except(myObjects.Select(o => o.myName)).ToArray();
After that, you can just check length of results array to determine, if there is such an item.
It is O(n+m) solution, because of Except implementation, which uses HastSet.