Nested LINQ with multiple lists - c#

I think this is just a personal code problem, but I just can't see how I'm supposed to return the IsIdentifier bool from this nested object. dp has a list of dd's, and dd has a list of supportedformats, and one supportedformat has a list of parameters, and in parameter is a property (bool) called isIdentifier and I want to return it.
parameters.Add(new SupportedParameter
{
Name = name,
Version = version,
IsIdentifier = dp.Where(dp => dp.dd
.Where(dd => dd.SupportedFormats
.Where(sf => sf.Parameters.Where(sp => sp.Name == name).FirstOrDefault().IsIdentifier)
)
)
}
);
Errors:
Cannot implicitly convert type IEnumerable<SupportedFormat> to bool.
Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate type
Also can somebody tell me how to format the code on stackoverflow? Because I'm on a different pc and usually TAB and SHIFT + TAB do the trick. But it didn't work so I tried CTRL + [ or CTRL + ] and these don't work either.

From looking at your code, it appears that you want to find the first deeply nested Parameter with Name == name and then return its IsIdentifier boolean property.
You can reduce the complexity of this operation by "flattening" out all the nested collections, so you'll end up with one big collection containing every Parameter across all the parent objects, by using the LINQ SelectMany extension method.
Here's how I would approach your specific case:
...
IsIdentifier = dps
.SelectMany(dp => dp.dds)
.SelectMany(dd => dd.SupportedFormats)
.SelectMany(sf => sf.Parameters)
.FirstOrDefault(parameter => parameter.Name == name)?.IsIdentifier ?? false;
...
The ?.IsIdentifier ?? false part is just a fallback in case there are no Parameters with the specified name - it will just set IsIdentifier to false in that case.

Related

Nullable record structs in C# [duplicate]

So I've got a collection of structs (it's actually a WCF datacontract but I'm presuming this has no bearing here).
List<OptionalExtra> OptionalExtras;
OptionalExtra is a struct.
public partial struct OptionalExtra
Now I'm running the below statement:
OptionalExtra multiOptExtra = OptionalExtras.Where(w => w.Code == optExtra.Code).FirstOrDefault();
if (multiOptExtra != null)
{
}
Now this won't compile:
the operator != cannot be applied to opperands of type OptionalExtra
and '<null>'
After a little googling I realised it's because OptionalExtra is a struct. Which I believe is not nullable unless defined as a nullable type?
So my question is, if my where statement returns no results what will be the outcome of the FirstOrDefault call? Will it thrown an exception?
Incidently this should never happen but better safe than sorry.
If your collection is empty, FirstOrDefault will return default(OptionalExtras). The default value of a struct is the struct with all its values in turn default initialized (i.e. zero, null, etc.).
If you assume that there will be an element and your code doesn't work with an empty collection, Use First() instead, since that will throw an exception when your collection is empty. It's generally better to fail fast than to return wrong data.
If you cannot assume that there will be an element, but also cannot deal with struct default initialization, you might make the structs in the collection a nullable value type, for example as follows:
OptionalExtras
.Where(w => w.Code == optExtra.Code)
.Cast<OptionalExtra?>()
.FirstOrDefault();
This way you can get a null return even for a struct. The key idea here is to extend the set of possible values to include something other than an OptionalExtra to allow detection of an empty list. If you don't like nullables, you could instead use a Maybe<> implementation (not a .NET builtin), or use an empty-or-singleton list (e.g. .Take(1).ToArray(). However, a nullable struct is likely your best bet.
TL;DR;
.FirstOrDefault<T>() returns default(T) if the sequence is empty
Use .First() instead if you assume the list is non-empty.
Cast to nullable and then use .FirstOrDefault<T>() when you cannot assume the list is non-empty.
As others have said, the result of your code when no elements match will be:
default( OptionalExtra )
If you want a null returned, you can cast your list to OptionalExtra?
OptionalExtra? multiOptExtra = OptionalExtras.Cast<OptionalExtra?>().Where( ...
You can then test for null
If default(OptionExtra) is still a valid value, it's better to change your code to this
var results = OptionalExtras.Where(w => w.Code == optExtra.Code).Take(1).ToList();
if (results.Any()) {
multiOptExtra = results[0]
}
The result will be the default value of your struct, e.g. default(OptionalExtras).
Whereas for a reference type the default value is null.
its provide you defualt value for your structure like as below
int[] numbers = { };
int first = numbers.FirstOrDefault();
Console.WriteLine(first);//this print 0 as output
other option to handle is make use of default value like as below
List<int> months = new List<int> { };
// Setting the default value to 1 by using DefaultIfEmpty() in the query.
int firstMonth2 = months.DefaultIfEmpty(1).First();
Console.WriteLine("The value of the firstMonth2 variable is {0}", firstMonth2);
If you want to check for null, use System.Nullable collection:
var OptionalExtras = new List<OptionalExtra?>();
/* Add some values */
var extras = OptionalExtras.FirstOrDefault(oe => oe.Value.Code == "code");
if (extras != null)
{
Console.WriteLine(extras.Value.Code);
}
Note that you have to use Value to access the element.
Assuming Code is a string for the purposes of my answer, you should be able just to test that value for its default.
OptionalExtra multiOptExtra = OptionalExtras.Where(w => w.Code == optExtra.Code).FirstOrDefault();
if (multiOptExtra.Code != null)
{
}

Generic method with dynamically selected parameter in predicate

I have many objects of diffrent type and i need to check few diffrent properties from each of them in the same way. I want to use this method inside object initializer like this:
collection.GroupBy(x => x.Identifier)
.Select(group => new SomeType
{
Identifier = group.Key,
Quantity = group.Sum(i => i.Quantity),
Type = MY_METHOD_HERE(group),
Name = MY_METHOD_HERE(group)
})
Strongly typed example for one of the properties:
private ItemType CheckItemTypeConsistency(IGrouping<string, Item> group)
{
if (group.Any(x => x.ItemType != group.First().ItemType))
{
throw new ArgumentException($"Item with number {group.Key} is inconsistent", nameof(Item.ItemType));
}
else
{
return group.First().ItemType;
}
}
But i also have other property in Item that needs to be checked in the same way with diffrent type, so i have similar method but .ItemType is changed everywhere to .Name and return type is string.
I also have diffrent object type that i need to use it for, so in another method Item is changed to Vehicle.
How to create generic method like that?
I tried something like this:
private TElement CheckConsistency<TKey, TElement>(IGrouping<TKey, TElement> group, (maybe something here?))
{
if (group.Any(x => x.(what here?) != group.First().(what here?)))
{
throw new ArgumentException($"Element with number {group.Key} is inconsistent");
}
else
{
return group.First();
}
}
I solved problem with returning value by returning whole item so i can just CheckConsistency().Property when invoking this method.
But i dont know what to do with (what here?).
I thought that maybe i can put something in (maybe something here?) that would be somehow used in place of (what here?)?
Any ideas? I'm not sure about reflection because this method could be called easily more than 1000 times depending on collection size and number of unique entries.
#Edit:
Lets say it's something like merging data from two files. For example 2 data sets of items where quantity is added together for items with the same identifier etc. but some properties should be the same like name and if they are diffrent then there's something wrong and i need to throw an error.
There are diffrent sets of data with completly diffrent properties like vehicles, but rules are similar, some fields are just added together etc, some must be identical.
Using an accessor function and genericizing the property type and object type, you have:
private TProp CheckConsistency<TClass, TProp>(IGrouping<string, TClass> group, Func<TClass, TProp> propFn) {
var firstPropValue = propFn(group.First());
if (group.Any(x => firstPropValue == null ? propFn(x) == null : !propFn(x).Equals(firstPropValue))) {
throw new ArgumentException($"Item with number {group.Key} is inconsistent");
}
else {
return firstPropValue;
}
}
Which you can use like:
var ans = collection.GroupBy(x => x.Identifier)
.Select(group => new SomeType {
Identifier = group.Key,
Quantity = group.Sum(i => i.Quantity),
Type = CheckConsistency(group, x => x.ItemType),
Name = CheckConsistency(group, x => x.Name)
});
If it is important to include the proper argument name in the exception, you could pass it in, take in an Expression<Func<>> and pull out the name, then compile the argument to a lambda to use (may be slow), or use reflection instead of a lambda property accessor (also possibly slow).
To use Reflection, I recommend caching the compiled function so you don't constantly re-compile each time the method is called:
// [Expression] => [Func]
Dictionary<LambdaExpression, Delegate> propFnCache = new Dictionary<LambdaExpression, Delegate>();
private TProp CheckConsistency<TClass, TProp>(IGrouping<string, TClass> group, Expression<Func<TClass, TProp>> propExpr) {
Func<TClass,TProp> propFn;
if (propFnCache.TryGetValue(propExpr, out var propDel))
propFn = (Func<TClass, TProp>)propDel;
else {
propFn = propExpr.Compile();
propFnCache.Add(propExpr, propFn);
}
var firstPropValue = propFn(group.First());
if (group.Any(x => !propFn(x).Equals(firstPropValue))) {
throw new ArgumentException($"Item with number {group.Key} is inconsistent", ((MemberExpression)propExpr.Body).Member.Name);
}
else {
return firstPropValue;
}
}

Using Linq to access an objects properties within another object

I need to get a value from an object within an another object. My problem is I can't access any values from within the subobject, i always get the value of the object type itself.
Code where i'm accessing the object
var test = scheduledTask.Fields.Select(x => x.FieldValue);
This brings back in the results view
[0] 10111
[1] {ObjectType.Extension}
I need to access the [1] element which contains the following properties (amongst others), and i need to access the DisplayName
{
DisplayName: "MainMenu",
CategoryId: -1,
Id: 433
}
ScheduledTask is
{
Fields: {Fields.Field[2]},
LastModifiedDate:null,
{Fields.Field[2]}
}
You don't need LINQ to access a specific index of an array.
string name = (scheduledTask.Fields[1].FieldValue as ObjectType.Extension)?.DisplayName;
Since the array contains values of different types I assume that we have an array of object. Therefore we must cast to the expected type to be able to access specific fields or properties.
In case the value is null or the type does not match as will yield null. The null-conditional operators ?. performs a member or element access operation only if an operand is non-null and otherwise return null.
If you don't know the index of the required value, you can query with
string name = (scheduledTask.Fields
.Select(x => x.FieldValue)
.OfType<ObjectType.Extension>()
.FirstOrDefault()
)?.DisplayName;
If you are sure the required value is there and not null, you can drop the ?.
Assuming x.FieldValue is an object you could try casting to check if it is of type ObjectType.Extension:
var test = scheduledTask.Fields.Select(x => {
var asExtension = x.FieldValue as ObjectType.Extension;
if(asExtension != null) return asExtension.DisplayName;
else return x.FieldValue;
});
ETA: The as operator is a sort of safe-cast that will return null if the runtime type of LHS argument doesn't match the static type identified by the RHS argument.

C# Linq Filter IEnumerable dynamic property and value

public class Sample
{
public int Id { get; set; }
public string Description { get; set; }
public DateTime EffectiveDate { get; set; }
}
IEnumerable<Sample> sampleList;
//Populate the list
Now I want to filter the list some times by "Id" property, sometimes "Description" property. Just want to pass the property name (filterColumn) and property value (filterValue) both as strings.
I tried the following:
IEnumerable<Sample> result = sampleList.Where(x => x.GetType().GetProperty(filterColumn).Name == filterValue);
and
string whereQuery = string.Format(" {0} = \"{1}\"", filterColumn, filterValue);
IEnumerable<Sample> result = sampleList.AsQueryable().Where(whereQuery);
The second option works, if i pass the filterColumn as "Description", but throws incompatible '=' operator between string and int error when "Id" is passed as filterColumn and some filterValue like "1".
Appreciate any help. Thanks
Your first approach can work. Expanding on Jon Skeet's comment, here's the adjusted statement.
IEnumerable<Sample> result = sampleList.Where(
x => x.GetType().GetProperty(filterColumn).GetValue(x, null).Equals(filterValue)
);
To put a little context around this, you would have to allow for the differing data types. You can do this at least two ways: use a generic method or use the object data type. For illustrative purposes, I'll use the object approach.
public IEnumerable<Sample> GetFiltered(
IEnumerable<Sample> samples, string filtercolumn, object filtervalue
{
return samples.Where(
x => x.GetType().GetProperty(filtercolumn).GetValue(x, null).Equals(filtervalue)
);
}
IEnumerable<Sample> sampleList;
var byId = GetFiltered(sampleList, "Id", 100);
var byDescription = GetFiltered(sampleList, "Description", "Some Value");
This example is not really safe as there is no type checking to ensure that the property value will be of the same data type that you are passing in. For example, there is nothing stopping you from passing "Description" and 100 as parameters. You can't do a meaningful comparison between an integer and a string so you will always come up with an empty result. The Equals method does not throw an exception, it just sees that the two objects are different. As Jon pointed out, you'll always want to use Equals in this case rather than the "==" operator. The Equals method is intended to compare content while "==" compares references. Example:
Console.WriteLine(12 == 12);
// True
object a = 12;
object b = 12;
Console.WriteLine(a == b);
// False - because, due to boxing, a and b are separate objects
// that happen to contain the same value. (Check out "boxing"
// if this doesn't make sense.)
Console.WriteLine(a.Equals(b));
// True - because the Equals method compares content (value)
Also, note that strings have some special behaviors when using the "==" operator. The important thing to remember is that there is a difference between references (containers) and content. You want to compare content and that means Equals. (I have noticed that the Immediate window in Visual Studio is inconsistent in its results regarding strings when using "==". I suspect this is because string references can be, but are not always, optimized in that window.)
You state that your second approach works. I have not seen this type of filter string in a standard IEnumerable.Where method. So I am guessing that you are using some extensions. Your example does not work as shown. The DataTable class uses filter strings that match your usage. In general, a filter string has to be constructed in different ways based on data type. For example, a string requires the quotes (which you have) but an integer value does not use quotes.
Another option that you have is to set up a dictionary with the required operations.
public IEnumerable<Sample> GetFiltered(
IEnumerable<Sample> samples, string property, string value)
{
var map = new Dictionary<string, Func<string, Func<Sample, bool>>>()
{
{ "Description", v => s => s.Description == v },
{ "Id", v => s => s.Id == int.Parse(v) },
};
return samples.Where(map[property](value));
}
The advantage here is that you can perform a more complex comparison, such as adding custom filters by ranges of values, or those containing more than one property.

Get Types using IEnumerable GetGenericArguments

I have developed a MVC helper for generating display and editable tables (a jquery plugin is required to allow dynamic addition and deletion of rows with full postback in the editable tables) e.g.
#Htm.TableDisplayFor(m => m.MyCollection as ICollection)
which used in conjunction with attributes will include totals in the footer, add columns for view and edit links, render hyperlinks for complex type etc. e.g.
[TableColumn(IncludeTotals = true)]
I'm about to publish it on CodeProject but before doing so, would like to solve one issue.
The helper first gets the ModelMetadata from the expression, checks that it implements ICollection, then gets the type in the collection (note the following snippet is from accepted answers on SO, but as explained below, is not entirely correct)
if (collection.GetType().IsGenericType)
{
Type type = collection.GetType().GetGenericArguments()[0]
The type is used to generate ModelMetadata for the table header (there might not be any rows in the table) and each row in the table body (in case some items are inherited types which have additional properties and would otherwise screw up the column layout)
foreach (var item in collection)
{
ModelMetadata itemMetadata = ModelMetadataProviders.Current
.GetMetadataForType(() => item, type);
What I would like to be able to do is use IEnumerable rather than ICollection so that .ToList() does not need to be called on linq expressions.
In most cases IEnumerable works fine e.g.
IEnumerable items = MyCollection.Where(i => i....);
is OK because .GetGenericArguments() returns an array containing only one type.
The problem is that '.GetGenericArguments()' on some queries returns 2 or more types and there seems to be no logical order. For example
IEnumerable items = MyCollection.OrderBy(i => i...);
returns [0] the type in the collection, and [1] the type used for ordering.
In this case .GetGenericArguments()[0] still works, but
MyCollection.Select(i => new AnotherItem()
{
ID = i.ID,
Name = 1.Name
}
returns [0] the type in the original collection and [1] the type of AnotherItem
So .GetGenericArguments()[1] is what I need to render the table for AnotherItem.
My question is, is there a reliable way using conditional statements to get the type I need to render the table?
From my tests so far, using .GetGenericArguments().Last() works in all cases except when using OrderBy() because the sort key is the last type.
A few things I've tried so far include ignoring types that are value types (as will often be the case with OrderBy(), but OrderBy() queries might use a string (which could be checked) or even worse, a class which overloads ==, < and > operators (in which case I would not be able to tell which is the correct type), and I have been unable to find a way to test if the collection implements IOrderedEnumerable.
Solved (using comments posted by Chris Sinclair)
private static Type GetCollectionType(IEnumerable collection)
{
Type type = collection.GetType();
if (type.IsGenericType)
{
Type[] types = type.GetGenericArguments();
if (types.Length == 1)
{
return types[0];
}
else
{
// Could be null if implements two IEnumerable
return type.GetInterfaces().Where(t => t.IsGenericType)
.Where(t => t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.SingleOrDefault().GetGenericArguments()[0];
}
}
else if (collection.GetType().IsArray)
{
return type.GetElementType();
}
// TODO: Who knows, but its probably not suitable to render in a table
return null;
}

Categories

Resources