I have a model that looks like the following:
public class MyType{
public string Id {get;set;}
public string Name{get;set;}
public List<MyType> Children{get;set;}
}
and in my data I have just two level data, meaning my objects will look like:
{
MyType{"1","firstParent",
{
MyType{"2","firstChild",null},
MyType{"3","secondChild",null}}
},
MyType{"4","secondParent",
{
MyType{"5","firstChild",null},
MyType{"6","secondChild",null}}
}
}
How do I query to get MyType object with a specific Id where it might be a parent or child?
The following will return only parents.
collection.FirstOrDefault(c => c.id==id)
You can use Any with a recursive local function to find objects on any level (your data structure would seem to indicate a deeper level is possible)
bool hasIdOrChildren(MyType t, string localId)
{
return t.Id == localId || (t.Children != null && t.Children.Any(o => hasIdOrChildren(o, localId)));
}
collection.FirstOrDefault(c => hasIdOrChildren(c, id));
Or using pre C#7 syntax:
Func<MyType, string, bool> hasIdOrChildren = null;
hasIdOrChildren = (MyType t, string localId) =>
{
return t.Id == localId || (t.Children != null && t.Children.Any(o => hasIdOrChildren(o, localId)));
};
collection.FirstOrDefault(c => hasIdOrChildren(c, id));
If you are only interested in one level, you can drop the reclusiveness:
collection.FirstOrDefault(c => c.Id == id || (c.Children != null && c.Children.Any(o => o.Id == id)));
Edit
The code above gives the parent if any child has the id, you can also flatten the whole tree structure using SelectMany also with a recursive function:
IEnumerable<MyType> flattenTree(MyType t)
{
if(t.Children == null)
{
return new[] { t };
}
return new[] { t }
.Concat(t.Children.SelectMany(flattenTree));
};
collection
.SelectMany(flattenTree)
.FirstOrDefault(c => c.Id == id);
This method can be useful for any type of processing where you need to flatten the tree.
You could build a list of all MyType including children and then query on it like this :
collection.SelectMany(c => c.Children).Concat(collection).Where(c => c.id == id)
I think you're looking for
var flattenedList = IEnumerable.SelectMany(i => i.ItemsInList);
This flattens the list and gives back one list with all items in it.
In your case you need to select
collection.SelectMany(c => c.Type).Concat(collection).Where(item => item.Id == 5);
MSDN
You still got the childs in your joined parents here, but you can still erase them or ignore them.
I think, you should flatten collection using SelectMany method, then use FirstOrDefault to get element by id:
MyType selected = collection
.SelectMany(obj => new MyType[] {obj, obj.NestedList})
.FirstOrDefault(obj => obj.id == id);
Related
I'm trying to create a complex Linq query that goes like this:
Get all organisations which have employees that match the given filter parameters.
Example filter:
Firstname: John
Name: Smith
My first attempt:
if (!filter.Name.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.Name.ToLower().Contains(filter.Name.ToLower())));
}
if (!filter.Firstname.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.Firstname.ToLower().Contains(filter.Firstname.ToLower())));
}
if (!filter.ContactNumber.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.ContactNumber.contains(filter.ContactNumber)));
}
The problem with this approach is that when there is someone with the firstname John (ex. John Johnson) in organisation A, and someone with the last name Smith (Jenny Smith) in organisation A. The organisation (A) that contains those two persons gets returned. Which it shouldn't. I only want organisations that have people with the firstname "john" AND the lastname "Smith"
I found a working, but dirty and non-scalable approach:
if (!filter.ContactNumber.IsNullOrWhiteSpace())
{
if (!filter.Name.IsNullOrWhiteSpace() && !filter.Firstname.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.ContactNumber.contains(filter.ContactNumber)
&& p.Name.ToLower().Contains(filter.Name.ToLower())
&& p.Firstname.ToLower().Contains(filter.Firstname.ToLower())));
}
else if (!filter.Name.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.ContactNumber.contains(filter.ContactNumber)
&& p.Name.ToLower().Contains(filter.Name.ToLower())));
} else if (!filter.Firstname.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.ContactNumber.contains(filter.ContactNumber)
&& p.Firstname.ToLower().Contains(filter.Firstname.ToLower())));
} else
{
query = query.Where(o => o.Persons.Any(p => p.ContactNumber.contains(filter.ContactNumber));
}
} else if(!filter.Name.IsNullOrWhiteSpace())
{
if (!filter.Firstname.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.Firstname.ToLower().Contains(filter.Firstname.ToLower()) && p.Name.ToLower().Contains(filter.Name.ToLower())));
} else
{
query = query.Where(o => o.Persons.Any(p => p.Name.ToLower().Contains(filter.Name.ToLower())));
}
} else if (!filter.Firstname.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.Firstname.ToLower().Contains(filter.Firstname.ToLower())));
}
As you can see this not a very clean solution.
I also tried using method calls inside the expression but Linq couldnt translate that. Is there any way I can can make a list of predicate expressions an merge them to one? Or is there a another, better solution?
By the way, since I need a paginated list, it all has to be in one query.
For your information, this is what my filter class looks like. It is just a class send from my front-end with all the fields that need to be filtered.
public class ContactFilter
{
public string Name{ get; set; }
public string Firstname{ get; set; }
public string ContactNummer { get; set; }
}
One of the easiest solution is using LINQKit library:
var predicate = PredicateBuilder.New<Person>();
if (!filter.Name.IsNullOrWhiteSpace())
{
predicate = predicate.And(p => p.Name.ToLower().Contains(filter.Name.ToLower()));
}
if (!filter.Firstname.IsNullOrWhiteSpace())
{
predicate = predicate.And(p => p.Firstname.ToLower().Contains(filter.Firstname.ToLower()));
}
if (!filter.ContactNumber.IsNullOrWhiteSpace())
{
predicate = predicate.And(p => p.ContactNumber.contains(filter.ContactNumber));
}
Expression<Func<Person, bool>> exp = predicate;
query = query
.AsExpandable()
.Where(o => o.Persons.Any(exp.Compile()));
Is there any way I can can make a list of predicate expressions an merge them to one?
Yes, and that's the approach I'd prefer in this situation.
First build the list:
var filterExpressions = new List<Expression<Func<Person, bool>>();
if (!filter.Name.IsNullOrWhiteSpace())
{
filterExpressions.Add(p => p.Name.ToLower().Contains(filter.Name.ToLower()));
}
if (!filter.Firstname.IsNullOrWhiteSpace())
{
filterExpressions.Add(p => p.Firstname.ToLower().Contains(filter.Firstname.ToLower()));
}
if (!filter.ContactNumber.IsNullOrWhiteSpace())
{
filterExpressions.Add(p => p.ContactNumber.contains(filter.ContactNumber));
}
From there, you can use this implementation to And arbitrary Expressions together. You'll also need to decide what to do if there are no filters to apply (I'll use a default of no filter, but you may want to do something else).
var predicate = filterExpressions.DefaultIfEmpty(p => true)
.Aggregate((a, b) => a.And(b));
Now we get to the hard part. We have an expression that represents the lambda you want to pass to a call to Any. It would be nice if we could just do:
query = query.Where(o => o.Persons.Any(predicate));
But sadly, this won't work because the type of o.Persons isn't an IQueryable. So now we have an expression that we want to embed in another expression in which the inner expression needs to be a lambda. Fortunately this isn't too complicated:
public static Expression<Func<TSource, TResult>> EmbedLambda
<TSource, TResult, TFunc1, TFunc2>(
this Expression<Func<TFunc1, TFunc2>> lambda,
Expression<Func<TSource, Func<TFunc1, TFunc2>, TResult>> expression)
{
var body = expression.Body.Replace(
expression.Parameters[1],
lambda);
return Expression.Lambda<Func<TSource, TResult>>(
body, expression.Parameters[0]);
}
(Using a helper class from the above link)
Now we just need to call the method. Note we won't be able to rely entirely on type inference due to the way this all works out, so some types need to be specified explicitly.
query = query.Where(predicate.EmbedLambda((UnknownType o, Func<Person, bool> p) => o.Persons.Any(p)));
I have list of Countries and inside it have list of Places.
// ...
public IList<ICountriesDTO> Countries { get; set; }
public class CountriesDTO: ICountriesDTO
{
public IEnumerable<IPlacesDTO> Places { get; set;
}
I am trying to get list of Places that are not null.
allPlacesDTO.World.Countries
.SelectMany(x => x.Places == null ? null : x.Places)
.ToList();
But I receive a null exception when Places are null for their Countries object.
How can I do a null check for Places and just use return statement instead of doing select to null object, similar to what I have below?
if (allPlacesDTO.World.Countries.Places == null)
{
return;
}
Update:
My requirement was if there is no places in any of the countries just use the return statement to exit the current function without proceeding further. That was achieved by the accepted answer and Count function.
var lstAllPlaces = allPlacesDTO.World.Countries
.Where(x => x.Places != null)
.SelectMany(x => x.Places)
.ToList();
if (lstAllPlaces.Count() == 0)
{
return;
}
You can do the condition in where clause
allPlacesDTO.World.Countries.Where(x => x.Places != null)
.SelectMany(x => x.Places).ToList();
Or change the ternary operator to return new List() (it can be greedy)
I am building a web API that is suppose to populate data from a linked child table using a where clause.
I have attempted using include() with where() as per eager loading but without success.
public IQueryable<Market> getAllActive()
{
return db.Markets.Where(c => c.IsActive == true).Include(d => d.TravelCentres.Where(e => e.IsActive == true));
}
On researching, there are recommendations that I use explicit loading but it keeps error about the need to cast the data type. I am lost of ideas at the moment and will appreciate any help. Here is my code:
private TravelCentresDbContext db = new TravelCentresDbContext();
public IQueryable<Market> getAllActive()
{
//return db.Markets.Where(c => c.IsActive == true).Include(d => d.TravelCentres);
var result = db.Markets
.Where(c => c.IsActive == true)
.Select(p => new
{
Market = p.MarketId,
TravelCentres = p.TravelCentres.Where(x => x.IsActive == true)
});
return (IQueryable<Market>)result;
}
I get this exception message Unable to cast object of type
'System.Data.Entity.Infrastructure.DbQuery1[<>f__AnonymousType42[System.String,System.Collections.Generic.IEnumerable1[TravelCentres.Models.TravelCentre]]]'
to type 'System.Linq.IQueryable1[TravelCentres.Models.Market]'.
Blockquote
result is not an IQuerytable<Market>, it's an IQueryable of an anonymous type with properties Market and TravelCenters. So (IQueryable<Market>)result is an invalid cast. It would be advisable to create a model with Market and TravelCenters properties and then return that.
public class MyModel
{
public int MarketId { get; set; }
public IEnumerable<TravelCentre> TravelCentres { get; set; }
}
.
var result = db.Markets
.Where(c => c.IsActive == true)
.Select(p => new MyModel()
{
Market = p.MarketId,
TravelCentres = p.TravelCentres.Where(x => x.IsActive == true)
});
return (IQueryable<MyModel>)result;
Can I make a function to be called within Linq functions using EF?
for example
int GetEmployeeStatus(string empID)
{
using (DB = new db())
{
var status = db.EmployeeStatus
.Where(e => e.EmpID == empID && e.StatusEndDate == null)
.Select(e => e.Status)
.SingleOrDefault();
return status;
}
}
Now is there a way to use the function above anywhere in my applciation in some way like this:
....
var empList = db.Employees
.Where(e => e.CostCenterID == 123
&& GetEmployeeStatus(e.EmpID) == 1);
....
I do not want to write the creteria for finding the employee status over and over again, is there a way to do that or something similar in concept?
One more thing, I know the way I write the function up will cause a database trip for every row, I hope there is way to avoid that and just to embed the query within the Linq so it will be called once.
You can use an extension function:
public static class DbContextExtensions
{
public static IQueryable<Employee> WhereX(this IQueryable<Employee> queryable, int id)
{
return queryable.Where(e => e.CostCenterID == 123);
}
}
Let's say I have this simple structure
class FooDefinition
{
public FooDefinition Parent { get; set; }
}
class Foo
{
public FooDefinition Definition { get; set; }
}
class Bar
{
public ICollection<Foo> Foos { get; set; }
}
A Bar has a list of Foos which can be simple (no parent/child relationships) or nested just one level (i.e. a parent Foo has many child Foos). As can be seen here, the relationships are specified in the FooDefinition, not the Foo itself.
What I need to do is generate a list of Foos properly grouped by this hierarchy. Consider the following source data:
var simpleDefinition = new FooDefinition();
var parentDefinition = new FooDefinition();
var childDefinition = new FooDefinition { Parent = parentDefinition };
var bar = new Bar { Foos = new[]
{
new Foo { Definition = simpleDefinition },
new Foo { Definition = parentDefinition },
new Foo { Definition = childDefinition }
}};
I'd like to get a collection of top-level items with their chilren. An adequate data structure would probably be IEnumerable<IGrouping<Foo, Foo>>.
The result would look like:
Item 1 (simple)
Item 2 (parent)
Item 3 (child)
And of course I'd like to do this with a purely-functional Linq query. I do lots of these, but my brain seems to be stuck today.
bar.Foos.Where(x => x.Definition.Parent == null)
.Select(x => Tuple.Create(x,
bar.Foos.Where(c => c.Definition
.Parent == x.Definition
)));
This will return an IEnumerable<Tuple<Foo, IEnumerable<Foo>>>, where Item2 of the Tuple contains the children for the parent in Item1. For your example, this returns two Tuples:
Item1 = simpleDefinition and Item2 containing an empty enumerable
Item1 = parentDefinition and Item2 containing an enumerable which contains childDefinition
There might be a more elegant or faster way, but I couldn't come up with it...
Oh well, I contradict my own comment a little bit with this, but it is possible with GroupBy - at least nearly:
bar.Foos.Where(x => x.Definition.Parent == null)
.GroupBy(x => x,
x => bar.Foos.Where(c => c.Definition.Parent == x.Definition));
This will return an IEnumerable<IGrouping<Foo, IEnumerable<Foo>>>.
Update:
I wanted to know, if the solution you wanted is possible at all.
Yes, it is:
bar.Foos.Where(x => x.Definition.Parent != null)
.GroupBy(x => bar.Foos.Where(y => y.Definition == x.Definition.Parent)
.Single(),
x => x)
.Union(bar.Foos.Where(x => x.Definition.Parent == null &&
!bar.Foos.Any(c => c.Definition.Parent ==
x.Definition))
.GroupBy(x => x, x => (Foo)null));
But I really don't want to know the big O of this and it really shouldn't be used ;-)
If you add a class and a method, you can get to IEnumerable<IGrouping<Foo,Foo>>.
class FooRelation{
public Foo Parent {get; set;}
public Foo Child {get; set;}
}
static IEnumerable<FooRelation> GetChildren(Bar source, Foo parent){
var children = source.Foos.Where(c => c.Definition.Parent == parent.Definition);
if(children.Any())
return children.Select(c => new FooRelation{Parent = parent, Child = c});
return new FooRelation[]{new FooRelation{Parent = parent}};
}
You might even be able to fold that static method into this query...but it would get messy:
var r = bar.Foos.Where(x => x.Definition.Parent == null)
.SelectMany(x => GetChildren(bar, x))
.GroupBy(fr => fr.Parent, fr => fr.Child);