Order Collection by child child collection - c#

I have the following objects
public class ObjectA{
public string Name { get; set; }
public ICollection<ObjectB> ObjectBCollection { get; set; }
}
public class ObjectB{
public string Name { get; set; }
public ICollection<ObjectC> ObjectCCollection { get; set; }
}
public class ObjectC{
public string Name { get; set; }
public DateTime Date { get; set; }
public InternalType Type { get; set; }
}
public enum InternalType {
TypeA,
TypeB,
TypeC
}
Now i want to order a List of ObjectA by the Dates in ObjectC that are closests to the current date. To make things a little more interesting, I also want it sorted by the InteralType. But I want TypeB have priority over TypeA and TypeC comes last.
I was thinking of creating an extra value that presents the integer value of the timespan between the current date and the Date property and multiply that by the Type property, but I can't figure out how to actually do that.

First of all if you want specific ordering in Enum you can do this:
public enum InternalType : int
{
TypeA = 2,
TypeB = 1,
TypeC = 3
}
Next if I understood your question correctly you have:
var collection = new List<ObjectA>();
which you need to sort by ALL dates in child elements. You can use Linq expressions for this:
List<KeyvaluePair<DateTime, ObjectA>> collectionWithDates = collection
.Select
(
objectA => new KeyValuePair<DateTime, ObjectA>
(
objectA
.SelectMany(a => a.ObjectBCollection)
.SelectMany(b => b.ObjectCCollection)
.OrderBy(c => c.Date).ThenBy(c => (int)c.Type)
.Last()
.Date,
objectA
)
)
.ToList();
To get ordered list of ObjectA you just need to:
var orderedCollection = collectionWithDates
.OrderBy(d => d.Key)
.Select(d => d.value)
.ToList();
I believe this should work. However I didn't tested it.
Correct me in comments if I misunderstood requirements.
Last thing to add - as far as I know, Linq expressions are not the fastest way to sort collections.

Related

Searching for an item in a list that's nested inside another list based on a property value in C# using LINQ?

Is there a way to search for an item in a list that's nested inside another list based on a property value using LINQ?
Given the follow models below, for a given Order (variable customerOrder), I want to return the earliest order date (Date) where the Day is "Sunday".
models:
public class Order
{
public int Id { get; set; }
public List<OrderLine> OrderLines { get; set; }
}
public class OrderLine
{
public string Description { get; set; }
public List<OrderDate> OrderDates { get; set; }
}
public class OrderDate
{
public DateTime Date { get; set; }
public string Day { get; set; }
}
code:
var dates = new List<DateTime>();
foreach(var a in customerOrder.OrderLines)
{
var orderDate = a.OrderDates.Where(x => x.DateTypeId.Equals("Sunday")).FirstOrDefault();
dates.Add(orderDate.ActualDate);
}
dates.OrderBy(d => d.Date);
return dates.FirstOrDefault();
EDIT
More elegant query
You can use Linq to achieve your result.
Here is a query that would closely mimick your code.
customerOrder.OrderLines
.Select(ol => ol.OrderDates
.Where(x => x.Day.Equals("Sunday"))
.FirstOrDefault())
.Where(d => d != null)
.OrderBy(d => d.Date)
.FirstOrDefault();
which could be more elegantly rewritten as:
customerOrder.OrderLines
.SelectMany(ol => ol.OrderDates)
.OrderBy(d => d.Date)
.FirstOrDefault(d => d.Day == "Sunday");
Here is a Linqpad query with some test data and dump for you to try.
Simply copy and paste in Linqpad.
void Main()
{
var customerOrder = new Order
{
Id = 1,
OrderLines = Enumerable
.Range(0, 10)
.Select(i => new OrderLine
{
Description = $"Line Description {i}",
OrderDates = Enumerable.Range(0, 10)
.Select(j => new OrderDate
{
Date = DateTime.Now.AddDays(i+j),
Day = DateTime.Now.AddDays(i+j).DayOfWeek.ToString()
})
.ToList()
})
.ToList()
}
.Dump();
customerOrder.OrderLines
.SelectMany(ol => ol.OrderDates)
.OrderBy(d => d.Date)
.FirstOrDefault(d => d.Day == "Sunday")
.Dump();
}
// You can define other methods, fields, classes and namespaces here
public class Order
{
public int Id { get; set; }
public List<OrderLine> OrderLines { get; set; }
}
public class OrderLine
{
public string Description { get; set; }
public List<OrderDate> OrderDates { get; set; }
}
public class OrderDate
{
public DateTime Date { get; set; }
public string Day { get; set; }
}
On a side note the OrderDate class is not necessary. The DateTime type has a property DayOfWeek that you can use to test is a Date is a Sunday.
DayOfWeek is an enum so you can simply test MyDate.DayOfWeek == DayOfWeek.Sunday rather than relying on a string for that purpose.
First of all your code will not work as intended.
dates.OrderBy(d => d.Date); doesn't work: OrderBy returns an IEnumerable, it doesn't change the original collection. You should either use List.Sort` or do this:
dates = dates.OrderBy(d => d.Date).ToList()
Secondly, you use FirstOrDefault: it has an overload that accepts predicate to search with; so the Where call is not needed. In addition FirstOrDefault will return null if nothing found. If this is a possible scenario, you should consider checking whether orderDate is null:
var dates = new List<DateTime>();
foreach(var a in customerOrder.OrderLines)
{
var orderDate = a.OrderDates.FirstOrDefault(x => x.DateTypeId.Equals("Sunday"));
if (orderDate is {})
{
dates.Add(orderDate.ActualDate);
}
}
dates = dates.OrderBy(d => d.Date).ToList();
return dates.FirstOrDefault();
That should work fine. But it hard to guess what aspects of behavior of your code samples are intended and what are not. You ask about searching, but say nothing about OrderBy part. Could you clarify this part, please?
Answering the question, if by better you mean more compact way, you can go with something like this:
var result = customerOrder.OrderLines
.SelectMany(a => a.OrderDates)
.OrderBy(d => d.Date)
.FirstOrDefault(x => x.DateTypeId.Equals("Sunday"));
return result;
You shouldn't be bothered with better way now; firstly you should start with at least working way. I suggest you to learn how to do these things both using Linq and without using Linq.
Better is a bit subjective, but you can use the Enumerable.SelectMany extension method to flatten the OrderDate instances into one sequence.
Then you can use the Enumerable.Where extension method to filter the dates that are "Sunday".
Then you can use the Enumerable.Min extension method to get the minimum date.
All of this can be chained together into a single statement.
DateTime earliestSunday = customeOrder
.OrderLines
.SelectMany(ol => ol.OrderDates)
.Where(od => od.Day == "Sunday")
.Min(od => od.Date);

Where condition inside lambda expression c#

I have a entity like
public class Program
{
public int ID { get; set; }
public bool IsActive { get; set; }
public string Title { get; set; }
}
and
public class EMetrics
{
public int ID { get; set; }
public bool IsActive { get; set; }
public string Title { get; set; }
public List<Program> Programs { get; set; }
}
I have repository method like,
IEnumerable<EMetrics> IEmetricsRepository.GetAllByProgram(params int[] programIds)
{
var metrics = EntitySet
.Where(x => programIds.Contains(x.Programs.Select(x => x.ID)))
.ToList();
return metrics;
}
[The above code throwing build error]
Here only where I am facing problem to get the EMetrics based on the program Ids array params.
I want list Emetrics which are associated with the program.
You're incorrectly accessing the same input parameter in your LINQ. It should be refactored by changing your inner Select to use a different parameter:
IEnumerable<EMetrics> IEmetricsRepository.GetAllByProgram(params int[] programIds)
{
var metrics = EntitySet
.Where(x => programIds.Contains(x.Programs.Select(y => y.ID)))
.ToList();
return metrics;
}
So you want to check if all elements of one collection are present in the other. In LINQ that can be done with combination of Except and Any:
var metrics = EntitySet
.Where(x => x.Programs.Select(p => p.ID).Except(programIds).Any())
.ToList();
Fyi - your current code is failing because Array.Contains expects a single item, an int in this case, while you are giving it a whole enumerable

Concat two Ienumerables of different types

I have two instances of IEnumerable<T> (with the Different T). I want to combine both of them .
IEnumerable<ClassA>
IEnumerable<ClassB>
Both in CLass A and ClassB i have one common property .Lets say for example it is EmpId ..
Is there a build-in method in .Net to do that or do I have to write it myself?
Assuming you can extract the common property to a common interface, let's say IEmployee, then you could just Cast() and then Concatenate the collections:
classAItems.Cast<IEmployee>().Concat(classBItems)
Note that this will only iterate over those IEnumerables on demand. If you want to create a List containing the content of both sequences at the time you combined them, you can use ToList():
List<IEmployee> all = classAItems.Cast<IEmployee>().Concat(classBItems).ToList();
You can do the same if you only need an array using ToArray().
You can get the concatenated common property easily enough:
var empIds = first.Select(x => x.EmpId).Concat(second.Select(x => x.EmpId));
If this is not what you are after, you will have to be more specific.
You cannot combine two sequences of different types in one sequence, unless you project some of their properties in a common type and create a sequence of this type.
For instance, let we have the two following classes:
public class A
{
public int ID { get; set; }
public string FirstName { get; set; }
public int Age { get; set; }
}
and
public class B
{
public int ID { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public bool Sex { get; set; }
}
Furthermore, let that you have two sequences, one containing objects of type classA and the other containing objects of type classB. Then, if you declare a third type called classCommon, that would contain the commont properties of classA and classB,
public class classCommon
{
public int ID { get; set; }
public int Age { get; set; }
}
you could try the following:
var result = listA.Select(x => new classCommon { ID = x.ID, Age = x.Age })
.Concat(listB.Select(x => new classCommon{ ID = x.ID, Age = x.Age });
You can concat them with respect to the lowest common denominator, which is object in you case:
IEnumerable<ClassA> e1 = new List<ClassA>();
IEnumerable<ClassB> e2 = new List<ClassB>();
IEnumerable<object> c = e1.Cast<object>()
.Concat(e2.Cast<object>());
But this will not give you much, you will have to runtime check of object type in c collection.
You can create a better common denominator, like some interface IClass which has property EmpId and is implemented by both ClassA and ClassB.
If you do not care about Intellisense, you can try to use dynamic:
IEnumerable<ClassA> e1 = new List<ClassA>() { new ClassA() { A = 1 } };
IEnumerable<ClassB> e2 = new List<ClassB>();
IEnumerable<dynamic> c = e1.Cast<object>()
.Concat(e2.Cast<object>());
int a = c.First().A;
In above code, a will properly result in 1.

Lambda expression - select single object with a IEnumerable<> property

Is it possible to select a single object and populate a containing IEnumerable property with a single Lambda expression?
Something like this:
var someViewModel = _repository.Table.Where(x => x.Id == someId)
.Select(new ListViewModel(){
GroupId = x.Group.Id,
GroupTitle = x.Group.Title
List = ?? // Select new SubViewModel and add it to IEnumerable<SubViewModel>
})
The result I'm after is a new object (ListViewModel in this case) that contains 3 properties. "List" being a collection of newly selected objects.
Is this possible? Am I coming at this from the wrong angle?
Thanks!
Update:
Let me try again :) Keep in mind that my naming is fictional here. Given the following two classes I would like to construct a DB query using a Lambda expression which creates a single "ListViewModel" that contains a collection of "SubViewModel". Does this help clarify?
public class SubViewModel
{
public int Id { get; set; }
public string Title { get; set; }
}
public class ListViewModel
{
public int GroupId { get; set; }
public string GroupTitle { get; set; }
public IEnumerable<SubViewModel> List { get; set; }
}
I'm not sure if I understand your question correctly but here is what I am thinking, you need to create a new IEnumberable and add the item to that collection.
var someViewModel = _repository.Table.Where(x => x.Id == someId)
.Select(new ListViewModel()
{
GroupId = x.Group.Id,
GroupTitle = x.Group.Title
List = new List<SubViewModel> { new SubViewModel(x) }
});

RavenDB cross entity query matching on non-identifier property

I have the following entity collections in RavenDB:
public class EntityA
{
public string Id { get; set; }
public string Name { get; set; }
public string[] Tags { get; set; }
}
public class EntityB
{
public string Id { get; set; }
public string Name { get; set; }
public string[] Tags { get; set; }
}
The only thing shared is the Tags collection: a tag of EntityA may exist in EntityB, so that they may intersect.
How can I retrieve every EntityA that has intersecting tags with EntityB where the Name property of EntityB is equal to a given value?
Well, this is a difficult one. To do it right, you would need two levels of reducing - one by the tag which would expand out your results, and another by the id to collapse it back. Raven doesn't have an easy way to do this.
You can fake it out though using a Transform. The only problem is that you will have skipped items in your result set, so make sure you know how to deal with those.
public class TestIndex : AbstractMultiMapIndexCreationTask<TestIndex.Result>
{
public class Result
{
public string[] Ids { get; set; }
public string Name { get; set; }
public string Tag { get; set; }
}
public TestIndex()
{
AddMap<EntityA>(entities => from a in entities
from tag in a.Tags.DefaultIfEmpty("_")
select new
{
Ids = new[] { a.Id },
Name = (string) null,
Tag = tag
});
AddMap<EntityB>(entities => from b in entities
from tag in b.Tags
select new
{
Ids = new string[0],
b.Name,
Tag = tag
});
Reduce = results => from result in results
group result by result.Tag
into g
select new
{
Ids = g.SelectMany(x => x.Ids),
g.First(x => x.Name != null).Name,
Tag = g.Key
};
TransformResults = (database, results) =>
results.SelectMany(x => x.Ids)
.Distinct()
.Select(x => database.Load<EntityA>(x));
}
}
See also the full unit test here.
There is another approach, but I haven't tested it yet. That would be to use the Indexed Properties Bundle to do the first pass, and then map those results for the second pass. I am experimenting with this in general, and if it works, I will update this answer with the results.

Categories