I have the following structure.
public class ToolSettings
{
public string Extension { get; set; }
public ObservableCollection<Tool> Tools { get; set; }
}
public class Tool
{
public string Name { get; set; }
public string Command { get set; }
}
// Within app code
public ObservableCollection<ToolSettings> settings { get; set; }
I'd like to grab the Tools collection from the settings collection where the Extension equals a certain string.
Below is my LINQ code, but I'm only getting one item in my collection when I know there's more. It looks like it produces a collection of a collection, which is why there's only one item.
myListBox.ItemsSource = from i in settings
where i.Extension == myExtension
select i.Tools;
EDIT:
Thanks for all the good (and quick) answers. It turns out I only need the first item, but I know that the SelectMany method will come in handy in the future. So, thanks for all the heads up. Here is the full solution I used.
myListBox.ItemsSource = (from i in settings
where i.Extension == myExtension
select i.Tools).First();
myListBox.ItemsSource = settings.Where(s => s.Extension == myExtension)
.SelectMany(s => s.Tools);
Or, if you prefer query syntax to the fluent syntax:
myListBox.ItemsSource = from s in settings
where (s.Extension == myExtension)
from t in s.Tools
select t;
That will give you an IEnumerable<ObservableCollection<Tool>>. It willprobably only have one item in it, but that item will be a ObservableCollection. If you want that collection itself, tack .First() (or .FirstOrDefault()) at the end.
If i.Extension == myExtension could find several ToolsSettings in the collection (I'm guessing not), then you will need to use .SelectMany()
Try this:
myListBox.ItemsSource = (from i in settings
where i.Extension == myExtension
from t in i.Tools
select t);
You can use .SelectMany(), but that's only really useful if you want to take multiple ToolSettings and select all of their Tools as a single collection. If Extension is unique, use .Single() to reduce the collection of a single collection to just the single collection.
The question is a little vague. You are right, you are getting a collection of a collection, in that there might only be one ToolSettings instance where Extension satisfies your criteria and because of that, since you are selecting Tools, you are getting a sequence of ObservableCollection<Tool> instances.
What it sounds like you really want is to get the sequence of all Tool instances where the condition is met. In that case, you want to use the SelectMany extension method on the Enumerable class:
myListBox.ItemsSource = settings.Where(i => i.Extension == myExtension).
SelectMany(i => i.Tools);
Or, if you prefer query syntax, you can do the following:
myListBox.ItemsSource =
from i in settings
where i.Extension == myExtension
from t in i.Tools
select t;
Which will translate to a call to SelectMany when the compiler gets done with it.
Related
Yesterday I was working on a code refactor and came across an exception that I really couldn't find much information on. Here is the situation.
We have an a pair of EF entities that have a many to many relationship through a relation table. The objects in question look like this, leaving out the unnecessary bits.
public partial class MasterCode
{
public int MasterCodeId { get; set; }
...
public virtual ICollection<MasterCodeToSubCode> MasterCodeToSubCodes { get; set; }
}
public partial class MasterCodeToSubCodes
{
public int MasterCodeToSubCodeId { get; set; }
public int MasterCodeId { get; set; }
public int SubCodeId { get; set; }
...
}
Now, I attempted to run a LINQ query against these entities. We use a lot of LINQ projections into DTOs. The DTO and the query follow. masterCodeId is a parameter passed in.
public class MasterCodeDto
{
public int MasterCodeId { get; set; }
...
public ICollection<int> SubCodeIds { get; set; }
}
(from m in MasterCodes
where m.MasterCodeId == masterCodeId
select new MasterCodeDto
{
...
SubCodeIds = (from s in m.MasterCodeToSubCodes
select s.SubCodeId).ToList(),
...
}).SingleOrDefaultAsync();
The internal query throws the following exception
Expression of type 'System.Data.Entity.Infrastructure.ObjectReferenceEqualityComparer' cannot be used for constructor parameter of type 'System.Collections.Generic.IEqualityComparer`1[System.Int32]'
We have done inner queries like this before in other places in our code and not had any issues. The difference in this one is that we aren't new-ing up an object and projecting into it but rather returning a group of ints that we want to put in a list.
I have found a workaround by changing the ICollection on MasterCodeDto to IEnumerable and dropping the ToList() but I was never able to find out why I couldn't just select the ids and return them as a list.
Does anyone have any insight into this issue? Normally returning just an id field and calling ToList() works fine when it is not part of an inner query. Am I missing a restriction on inner queries that prevents an operation like this from happening?
Thanks.
Edit: To give an example of where this pattern is working I'll show you an example of a query that does work.
(from p in Persons
where p.PersonId == personId
select new PersonDto
{
...
ContactInformation = (from pc in p.PersonContacts
select new ContactInformationDto
{
ContactInformationId = pc.PatientContactId,
...
}).ToList(),
...
}).SingleOrDefaultAsync();
In this example, we are selecting into a new Dto rather than just selecting a single value. It works fine. The issues seems to stem from just selecting a single value.
Edit 2: In another fun twist, if instead of selecting into a MasterCodeDto I select into an anonymous type the exception is also not thrown with ToList() in place.
I think you stumbled upon a bug in Entity Framework. EF has some logic for picking an appropriate concrete type to materialize collections. HashSet<T> is one of its favorites. Apparently (I can't fully follow EF's source code here) it picks HashSet for ICollections and List for IEnumerable.
It looks like EF tries to create a HashSet by using the constructor that accepts an IEqualityComparer<T>. (This happens in EF'sDelegateFactory class, method GetNewExpressionForCollectionType.) The error is that it uses its own ObjectReferenceEqualityComparer for this. But that's an IEqualityComparer<object>, which can not be converted to an IEqualityComparer<int>.
In general I think it is best practice not to use ToList in LINQ queries and to use IEnumerable in collections in DTO types. Thus, EF will have total freedom to pick an appropriate concrete type.
I have the following code:
public class Navigation
{
public Navigation()
{
SubNavigation = new List<Navigation>();
}
public int Order { get; set; }
public string Text { get; set; }
public string RouteName { get; set; }
public IList<Navigation> SubNavigation { get; set; }
}
I then have:
IList<Navigation> list = new List<Navigation>();
I populate the list with some data. Not all items have a sub navigation. Currently the navigation only goes one level deep.
Now I would like to sort both the navigation and the sub-navigation for each item by order. I have tried all kinds of approaches but no matter what I tried I could not get the sub-navigation to sort without re-creating the object. The below code works:
IList<Navigation> result = list.OrderBy(l => l.Order)
.Select(n => new Navigation
{
Order = n.Order,
Text = n.Text,
RouteName = n.RouteName,
SubNavigation = n.SubNavigation.OrderBy(s => s.Order).ToList()
}).ToList();
I am not in love with this approach and my question is if there is any cleaner/better way of doing this using LINQ and the method syntax?
You could add a new property on your object:
public IList<Navigation> OrderedSubNavigation
{
get
{
return SubNavigation.OrderBy(s => s.Order).ToList();
}
}
Then when you want the ordered one you just use that.
I have tried all kinds of approaches but no matter what I tried I could not get the sub-navigation to sort without re-creating the object.
Well no, you wouldn't be able to cleanly - because getting the subnavigation to be in a particular order requires modifying the existing object, and LINQ's not built for that. LINQ's built for queries, which shouldn't mutate the data they work on.
One option would be to only sort the subnavigation when you need to - live with the fact that it's unordered within a Navigation, and then when you actually need the subnavigation items (e.g. for display) you can order at that point. Aside from anything else, this will make it more efficient if you end up not displaying the subnavigation items.
Why IntelliSense dosen't give me option to select properties of the Glasses collection(i.e itemToDelete) after implimenting the LINQ.
Here is my C# LINQ:
public static void DeletePicturesFromHrdDisc(List<Glasses> Temp, int GlassID)
{
var itemToDelete = (from item in Temp
where item.GlassesID==GlassID
select item);
itemToDelete.(dosen't give me the optipn to select Properties of the Glasses class)
}
here is my Glasses class:
public class Glasses
{
public string Brand { get; set; }
public string SmalPicture { get; set; }
public string BigPicture { get; set; }
public string color { get; set; }
public string FrameType { get; set; }
public int GlassesID { get; set; }
public string NameGlasses { get; set; }
public string Collection { get; set; }
}
But I can see Glasses properties if i use .First() or .FirstorDefault() extensions as follows:
Any idea why can't I access the properties in the first version and why i see them in IntelliSense only if i use .First() or .FirstorDefault() extensions on my LINQ.
Thank you in advance!
When you apply Where/Select to Temp, you're not just getting a single result (there could be more than one result matching where item.GlassesID==GlassID, the compiler cannot be sure) so it does not return a single object instance of Glasses but an IEnumerable<Glasses>. Simplified, think that you get a List<Glasses> back and you need to extract the actual object from the List before using its properties.
If you definitely know there's at most only one result returned, you can use SingleOrDefault() which returns the Glasses instance in the IEnumerable or null if it's empty, or Single() which returns the Glasses instance in the IEnumerable and throws an error if there isn't one. Both throw an exception if there is more than one result.
Using First() or FirstOrDefault() would mean that if you accidentally get more than one result, you'll in this case get the first one, a random one. Perhaps not a good thing for an "itemToDelete", you'd probably want to know if your assumption is wrong.
BTW, you can hover over var in Visual Studio to see the type of an expression.
In this case, itemToDelete is an IEnumerable<Glasses> of Glasses in Temp with a GlassesID of GlassID. Assuming your GlassesID is actually unique in Temp, it will only have one item which you can extract with .Single() or .SingleOrDefault().
EDIT: As others have pointed out, .Single(), not .First() is the right method to use if you want to check for exactly one item.
LINQ queries always produce a seqence of items, of type IEnumerable<Glasses> in your case. Even if there is only one element in the sequence, it is still a sequence. The sequence itself can only be enumerated, it does not have any other properties. To get at the elements' properties you first have to extract an element. That's exactly what First() does. It returns the first element of the sequence.
If your query always should produce exactly one item and any other result is an error you should use Single() instead of First(). If zero or one elements are acceptable you can use SingleOrDefault() and check the result for null.
Because your first version is a collection of Glasses, not a Glasses object itself. First() or FirstOrDefault() return a single instance of Glasses, thus the properties are available.
as the first() will give only a single item. so the compiler is giving intellisense for it. as the 1st version is IEnumerable<Glasses> so it has collection of objects. you need to iterate it.
var itemToDelete = (from item in Temp
where item.GlassesID==GlassID
select item);
foreach(var i in itemToDelete )
{
i.SomeProperty // do some thing with i ;
}
I'm trying to use a lambda expression to remove a certain object from a list, based on a value within that object. Here is my lambda:
ChartAttributes.ToList().RemoveAll(a => a.AttributeValue.Contains("PILOT"));
Here is the ChartAttributes list
IList<IChartAttribute> ChartAttributes
Here is the object ChartAttribute contained within the above list
public virtual string AttributeKey { get; set; }
public virtual string AttributeValue { get; set; }
public virtual int ChartAttributeId { get; set; }
public virtual int ChartSpecificationId { get; set; }
There is a chart attribute with its AttributeKey set to "PILOT". But this never gets removed. What am I doing wrong?
Thanks
Your code is taking an IEnumerable, copying all of its elements into a list and then removing items from that copy. The source IEnumerable is not modified.
Try this:
var list = ChartAttributes.ToList();
list.RemoveAll(a => a.AttributeValue.Contains("PILOT"));
ChartAttributes = list;
EDIT
Actually a better way, without needing to call ToList:
ChartAttributes = ChartAttributes.Where(a => !a.AttributeValue.Contains("PILOT"));
Your call to .ToList() makes a new list, and you end up removing the item from that list.
Whatever ChartAttributes is, you're not touching the contents of that.
Basically you're doing this:
var newList = ChartAttributes.ToList();
newList.RemoveAll(...);
If you were to inspect the contents of newList at this point you'd notice that your object(s) had been removed, but ChartAttributes, whatever type that is, still has those objects present.
You will have to remove the objects directly from ChartAttributes, but since you didn't say which type that is, I can't give you an example of how to do that.
If you need to remove items and save to database, you can try this sample code:
foreach (var x in db.myEntities.Where(a => a.AttributeValue.Contains("PILOT")))
db.myEntities.Remove(x);
db.SaveChanges();
It doesn't use RemoveAll, but it's another option to save the contents.
I had a similar problem and did a cast instead (as my setter for the property was internal):
((List<IChartAttribute>)ChartAttributes).RemoveAll(a => a.AttributeValue.Contains("PILOT"));
What I have is basically:
public class Object{
public bool IsObjectValid { set; get; }
}
public class MyThing{
public List<Object> Objects { set; get; }
}
What I want to do:
public class ObjectsFiltered{
public List<Object> ValidObjects{
get{
var list = LFs.Sort<_LF> where (IsObjectValid == true);
return list;
}
}
}
I know there has to be a way to sort out the List, filtering out the bool true/false. I just can't seem to wrap my head around Linq fully. I just can't seem to find a tutorial that screams "AH HA!" about Linq Lambda to me :/
I'd rather just return a subset, only only keep one "object" alive... instead of my current setup of multiple sets of lists. KISS.
Ultimately I will use the bool-toggles to feed TreeViews on my WPF form(s).
Clarification: I think the goal is to have a one list (List Objects) and a couple properties that show a filtered version of Objects. Instead of having Objects, ObjecstValid, ObjectsInvalid, ObjectsSomeOtherRuleSet... each a different List...
I'd like to have One List to rule them all... and have properties that return a variation on the list, as desired.
You can use LINQ:
public IEnumerable<Object> ValidObjects{
get{
return LFs.Where(item => item.IsObjectValid)
.OrderBy(item => item.SomeProperty);
}
}
Unless you need a List<T>, it's better to return an IEnumerable<T>, so that you won't store it all in-memory.
The lambda expression item => item.SomeProperty is an inline function that takes a parameter called item and returns item.SomeProperty. (The parameter and return types are inferred by the compiler)
To filter your objects, you can return simply:
return LFs.Where(x => x.IsObjectValid).ToList();
Note, however, that if you intend to draw on that function frequently, you may see some performance boost by maintaining a pre-filtered list internally.
LFs.Where(x => x.IsObjectValid).ToList().Sort()
To sort useing default compared. Otherwise
LFs.Where(x => x.IsObjectValid).OrderBy(x => x.PropertyToSortBy).ToList();