A UI allows users to select one or many tags. I would like to select all Nodes that have ALL that tags the user entered associated, not just a single tag.
public JsonResult SearchNodesByTags(string[] tags)
{
var dbTags = _DbContext.Tags.Where(t => tags.Contains(t.DisplayName)).ToList();
var nodes = _DbContext.Nodes.Where(n => n.Tags.Intersect(dbTags).Any());
// Error about intersection with non primitive
return Json(nodes);
}
You can do this in one statement:
var nodes = _DbContext.Nodes
.Where(n => n.Tags.All(t => tags.Contains(t.DisplayName)));
Your statement is not correct because dbTags is a local list containing Tag objects. When you use this list in a LINQ-to-Entities expression there is no way to translate these objects into SQL variables. That's only possible with primitive values.
var nodes = _DbContext.Nodes.Where(n => n.Tags.Intersect(dbTags).Any());
First of all, you can't use objects in a Linq-to-Entities expression, so you'd have to use something like this to compare:
n.Tags.Select(t => t.DisplayName).Intersect(tags)
Second, Intersect will give you the set of items that are in both given sets, so you'll end up with all Nodes that has any of the tags, instead of all nodes that have all of the tags.
If you want all the Nodes that contain every Tag in tags, you might want to use the answer from here on subsets:
_DbContext.Nodes
.Where(n => !tags.Except(n.Tags.Select(t => t.DisplayName)).Any())
.Select(...
set1.Except(set2) contains elements of set1 that aren't in set2
!set1.Except(set2).Any() == true if set2 includes every element of set1
Related
I am looking for a way to return only distinct elements in an array but that has to be based on a certain property, other properties of the array can be duplicated.
eg:
var elements = [{id:123,name:"abc",start:12:00:00},{id:123,name:"abc",start:12:00:00},
{id:123,name:"abc",start:1:00:00},{id:123,name:"def",start:12:00:00}]
we only want to return results based on unique start time and name as :
[{id:123,name:"abc",start:12:00:00},{id:123,name:"abc",start:1:00:00},{id:123,name:"def",start:12:00:00}]
the property either name or start has to be different for each element {id:123,name:"abc",start:12:00:00} => is returned only once as there is two elements with the same name and start.
I looked for DistinctBy functionality but it does not allow me to use the function. Only has Distinct() function.
The easy way to do this is with a GroupBy, so if you want to have a unique name or start you could do it like this:
var elements = [
{id:123,name:"abc",start:12:00:00},
{id:123,name:"abc",start:12:00:00},
{id:123,name:"abc",start:1:00:00},
{id:123,name:"def",start:12:00:00}
];
var unique = elements
.GroupBy(e => new {e.name, e.start})
.Select(g => g.First())
.ToArray(); // or ToList, or just leave it as an IEnumerable
exhibits contain ids that are in a certain order. When I query another table to get the BMI ids based on the exhibit ids, the order is not the same. Instead of pulling the first document id in exhibit, I think it is pulling the first record in the database that has the same exhibit id, but I want it to pull the record in the database in the same order as the exhibits ids.
var exhibits = _context.ApExhibits.Where(x => x.CASE_ID == apDockets.CASE_ID)
.Where(x => x.EXHIBIT_NBR != null)
.Where(x => !documents617.Contains(x.DOC_ID))
.OrderBy(x => x.EXHIBIT_NBR)
.Select(x => x.DIM_ID).ToList();
if (exhibits.Count > 0)
{
var bmiIds =
_context.DocumentImages.Where(x => exhibits.Contains((int)x.DIM_ID))
.Select(x => (int)x.BMI_ID).ToList();
}
it seems like your first collection exhibits is ordered based on EXHIBIT_NBR whereas when you query the _context.DocumentImages you're not ordering it by the same property, hence you're going to receive results based on the order of the elements in the source sequence which in this case is _context.DocumentImages. Essentially, you're saying "given an element of the source sequence DocumentImages, search linearly within the exhibits collection and if there is an element which meets the given criteria then retain the element of the source sequence".
So let's say the first element from the source sequence DocumentImages to be passed into the Where clause has an equivalent id of an element from the collection exhibits but the element in exhibits is say at the 5th position, this will make the element of the source sequence the first element of the result list when we perform a ToList() eager operation on the methods whereas it should technically be at the 5th position of the result list given that the matching element from exhibits is also at the 5th position.
So in order to have elements that are in the same order as exhibits, one solution is to inner join the DocumentImages and exhibits collections which would be the equivalent of checking if one element in one collection is contained within another. Then we can order by the same property as you did with exhibits.
example with query syntax:
var bmiIds = (from e in _context.ApExhibits
join x in _context.DocumentImages on e.DIM_ID equals (int)x.DIM_ID
where exhibits.Contains((int)x.DIM_ID)
orderby e.EXHIBIT_NBR
select (int)x.BMI_ID).ToList();
example with fluent syntax:
var bmiIds = _context.ApExhibits
.Join(_context.DocumentImages,
e => e.DIM_ID,
x => (int)x.DIM_ID,
(e, x) => new { e.EXHIBIT_NBR, x.BMI_ID })
.Where(x => exhibits.Contains((int)x.DIM_ID))
.OrderBy(e => e.EXHIBIT_NBR)
.Select(x => (int)x.BMI_ID).ToList();
Background: I have two Collections of different types of objects with different name properties (both strings). Objects in Collection1 have a field called Name, objects in Collection2 have a field called Field.
I needed to compare these 2 properties, and get items from Collection1 where there is not a match in Collection2 based on that string property (Collection1 will always have a greater or equal number of items. All items should have a matching item by Name/Field in Collection2 when finished).
The question: I've found answers using Lists and they have helped me a little(for what it's worth, I'm using Collections). I did find this answer which appears to be working for me, however I would like to convert what I've done from query syntax (if that's what it's called?) to a LINQ query. See below:
//Query for results. This code is what I'm specifically trying to convert.
var result = (from item in Collection1
where !Collection2.Any(x => x.ColumnName == item.FieldName)
select item).ToList();
//** Remove items in result from Collection1**
//...
I'm really not at all familiar with either syntax (working on it), but I think I generally understand what this is doing. I'm struggling trying to convert this to LINQ syntax though and I'd like to learn both of these options rather than some sort of nested loop.
End goal after I remove the query results from Collection1: Collection1.Count == Collection2 and the following is true for each item in the collection: ItemFromCollection1.Name == SomeItemFromCollection2.Field (if that makes sense...)
You can convert this to LINQ methods like this:
var result = Collection1.Where(item => !Collection2.Any(x => x.ColumnName == item.FieldName))
.ToList();
Your first query is the opposite of what you asked for. It's finding records that don't have an equivalent. The following will return all records in Collection1 where there is an equivalent:
var results=Collection1.Where(c1=>!Collection2.Any(c2=>c2.Field==c1.Name));
Please note that this isn't the fastest approach, especially if there is a large number of records in collection2. You can find ways of speeding it up through HashSets or Lookups.
if you want to get a list of non duplicate values to be retained then do the following.
List<string> listNonDup = new List<String>{"6","1","2","4","6","5","1"};
var singles = listNonDup.GroupBy(n => n)
.Where(g => g.Count() == 1)
.Select(g => g.Key).ToList();
Yields: 2, 4, 5
if you want a list of all the duplicate values then you can do the opposite
var duplicatesxx = listNonDup.GroupBy(s => s)
.SelectMany(g => g.Skip(1)).ToList();
Im getting a table Tags from the db.
the table has columns ID and TagName
I'm doing something like this to get a list of strings:
var taglist = Model.Tags.Select(x => x.TagName.ToLower()).ToArray();
then I'm comparing against another string array to get the strings that occur in both:
var intersectList = tagList.Intersect(anotherList);
I have my list, but now I also want the ID of each item remaining in the intersect list that corresponds to the tagList. (can just be an int array)
Can anyone help with a good way to do this?
Don't use intersect, it only works for collections of the same type. You could do a simple join or other form of filtering. It would be easiest to throw the string list into a HashSet and filter by tags that contain TagNames in that set. This way, you keep your tags unprojected so they keep their ids and other properties.
var stringSet = anotherList.ToHashSet(StringComparer.OrdinalIgnoreCase);
var tagList = Model.Tags.Where(t => stringSet.Contains(t.TagName)).ToList();
And put them into a list. Don't throw them into an array unless you specifically need an array (for use in a method that expects an array).
Could you do:
var intersectIds = Model.Tags
.Where(tag => anotherList.Contains(tag.TagName))
.Select(tag => tag.Id)
.ToList();
Maybe use Dictionary<int, string> instead of Array?
I have a linq query that is querying over IEnumberable. When I have a matching element for my where clause I would like to know the position of the element in the IEnumberable.
var result = from e in elements
where (string) e.Attribute("class") == "something"
select e.Position();
The e.Position() of course does not compile. The value of e.Position() would be the position of the selected element in the elements IEnumberable.
Any ideas on how to do this?
You need to use the overloaded Select method that allows for an index since that capability is not available in query syntax.
elements.Select((e, i) => new { Element = e, Index = i })
.Where(item => (string)item.Element.Attribute("class") == "something")
.Select(item => item.Index);
If you're using .NET 4.0 then you can use the (new) Zip method and write the same thing using the query syntax as well. It creates some temporary objects, so it isn't as efficient, but some people may find it more readable:
var result = from e in elements.Zip
(Enumerable.Range(0, elements.Count()), Tuple.Create)
where (string)e.Item1.Attribute("class") == "something"
select e.Item2;
It 'zips' the input collection with a generated sequence of numbers (with the same range as is the length of the collection). Then you can store the combined value either using the Tuple class (that's what I did) or you could use anonymous type.