Part of LINQ query to seperate method (EF Core) - c#

I guess this has been asked before but i don't find any good examples. I have this query.
series = await query
.GroupBy(o => new { o.AddedDate.Date.Month, o.AddedDate.Date.Year })
.Select(g => new DateLineGraphItem
{ Legend = new DateTime(g.Key.Year, g.Key.Month, 1), Number = g.Count() })
.ToListAsync();
Everything is the same all the time except that my DB has different names for all the "AddedDate" column and i'm trying to figure out how i can break this out into it's own method, something like this.
List<DateLineGraphItem> series = GroupByAndSelect("AddedDate")
List<DateLineGraphItem> series = GroupByAndSelect("CreatedDate")
Do i have to use Expressions and Predicates and all that or it is possible to do in some simple manner?

Do i have to use Expressions
It's always better to use expressions (the whole LINQ is based on that concept) than magic strings. The only problem is that there is no out of the box solution for composing expressions from other expressions, so you need to write your own or use 3rd party packages.
From the other side, the nameof operator eliminates most of the string drawbacks. And EF Core provides a handy method called EF.Property for simple scenarios like this.
So if the method contains string propertyName argument which points to direct property of type DateTime, you can simply replace the o.AddedDate with EF.Property<DateTime>(o, propertyName), e.g.
series = await query
.GroupBy(o => new { EF.Property<DateTime>(o, propertyName).Date.Month, EF.Property<DateTime>(o, propertyName).Date.Year })
.Select(g => new DateLineGraphItem
{ Legend = new DateTime(g.Key.Year, g.Key.Month, 1), Number = g.Count() })
.ToListAsync();

I had the same problem.
You may extend your entity class using an interface which contains a DateTime Field, like this:
public class EntityDb1 : IDatedEntity
{
public DateTime AddedDate { get; set; }
public DateTime MyDate => AddedDate;
}
public class EntityDb2 : IDatedEntity
{
public DateTime CreatedDate { get; set; }
public DateTime MyDate => CreatedDate;
}
public interface IDatedEntity
{
DateTime MyDate{ get; }
}
Then execute the query
public class Test
{
public static async Task<IEnumerable<DateLineGraphItem>> ExecuteQuery(IQueryable<IDatedEntity> entityList)
{
return await entityList
.GroupBy(o =>new { o.MyDate.Date.Month, o.MyDate.Date.Year })
.Select(g => new DateLineGraphItem(){ Legend = new DateTime(g.Key.Year, g.Key.Month, 1), Number = g.Count() })
.ToListAsync();
}
}
public class DateLineGraphItem
{
public DateTime Legend { get; set; }
public int Number { get; set; }
}

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);

LINQ, Group by property while keeping other property sort together

So I am not entirely sure how to explain what it is I am trying to do here. I am attempting to take some data (represented by the Excel file screenshot below), and basically sort by Connection2, while keeping similar items in Connection1 together. (Explained a bit in screen shot below)
Here is what I have as of right now:
var wires = RedConductorWires
.OrderBy(x => x.Label)
.ThenBy(x => x.Connection1)
.ThenBy(x => x.Connection2)
.ToList();
Class Object being sorted(Matches Excel Screenshot):
public class CustomExcelFormat
{
public string Label { get; set; }
public string WireSize { get; set; }
public string WireColor { get; set; }
public string WirePartNumber { get; set; }
public string Length { get; set; }
public string Connection1 { get; set; }
public string Connection1Torque { get; set; }
public string Connection1Termination { get; set; }
public string Connection1StripLength { get; set; }
public string Checkbox1 { get; set; }
public string Connection2 { get; set; }
public string Connection2Torque { get; set; }
public string Connection2Termination { get; set; }
public string Connection2StripLength { get; set; }
public string Checkbox2 { get; set; }
}
Screen Shot:
THE PROBLEM:
The issue is if you look at the screen shot the brown "A1:TB7:M1" cells need to be grouped together as well, and the Green "K7:10" need to be grouped together while maintaining their Connection2 sort/group.
In other words, the connection 2 side of those, K8:10 and K8:11 need to stay grouped together.
So obviously my LINQ query is not correct, I believe I need to do some sort of grouping and then sorting but am unsure how to approach it or even ask this question exactly (If someone could put it into words for me). I basically need to group by items in connection 2, while still keeping connection 1 sorted and together.
If someone could point me in the direction of the LINQ expression that could do something like this that would be great!
EDIT
So I used the following query:
var wires = RedConductorWires
.OrderBy(x => x.Label)
.GroupBy(x => new { x.Connection2, x.Connection1 })
.Select(grp => grp.ToList()).SelectMany(i => i).ToList();
and got the grouping correct. Now I just need to get it to sort in some alphabetical manner. See picture below.
Imagine this lines
A - B
C - B
C - D
A - D
you can reorder the lines any way you like and either you would have first column grouped or second column grouped. But you can never have both at the same time
I got the grouping to work correctly with the following query. I decided to keep it sorted on label initially.
var wires = RedConductorWires
.OrderBy(x => x.Label)
.GroupBy(x => new { x.Connection2, x.Connection1 })
.Select(grp => grp.ToList()).SelectMany(i => i).ToList();
Group By the values according to Label, Connection1, Connection2 then sort by these 3 fields and finally the desired output is generated.
var wires = RedConductorWires
.GroupBy(a => new { a.Label,a.Connection2, a.Connection1})
.Join(RedConductorWires,
left=>new { left.Key.Label,left.Key.Connection1,left.Key.Connection2},
right => new { right.Label, right.Connection1, right.Connection2 },
(left,right)=>new {left=left.Key,right = right })
.OrderBy(x => x.left.Label)
.ThenBy(x => x.left.Connection2)
.ThenBy(x => x.left.Connection1)
.ToList();
foreach(var item in wires)
{
Console.WriteLine(item.left.Label + "----" + item.left.Connection1 + "-----" + item.left.Connection2);
}
or
var wires = RedConductorWires
.OrderBy(x => x.Label)
.GroupBy(x => new { x.Connection2, x.Connection1 })
.Select(grp => grp.ToList()).SelectMany(i => i)
.OrderBy(x => x.Connection2)
.ThenBy(x => x.Connection1)
.ToList();
foreach(var item in wires)
{
Console.WriteLine(item.Label + "----" + item.Connection1 + "-----" + item.Connection2);
}

Transforming string of data into an object in c#

So I'm trying to solve this issue, which shouldn't be too hard, but I'm stuck on it for far too long now.
This is the data I'm working with var data = "2,6;2,7;4,14;5,20";
It's a string that shows <modifierGroup>,<modifier>;<modifierGroup>,<modifier>;...
This is the model I eventually want to get my data in:
public class ModifierGroup
{
public int Id { get; set; }
public List<Modifier> Modifiers { get; set; }
}
public class Modifier
{
public int Id { get; set; }
}
Right now I keep thinking I need to get my data in this format, so I can eventually push it into the model:
Key=2
Value=6
Value=7
Key=4
Value=14
Key=5
Value=20
But I could be wrong. I'd love to keep the code short. So I'd rather prevent loops in loops and doing if statements over and over. Best case scenario I get a 1 or 2-liner of code, but if it doesn't work, it doesn't work.
You could just use Split and GroupBy with a projection
var data = "2,6;2,7;4,14;5,20";
var result = data
.Split(";")
.Select(x => x.Split(",")
.Select(int.Parse)
.ToArray())
.GroupBy(x => x[0])
.Select(x => new ModifierGroup()
{
Id = x.Key,
Modifiers = x.Select(y => new Modifier() {Id = y[1]}).ToList()
});

Changing GroupBy keys depending on Class Structure

I have a list of items with multiple columns and would like to group them by some fields depending on a boolean:
I have the following class:
public class Item
{
public string Group { get; set; }
public string Person { get; set; }
public string Currency { get; set; }
public string Country { get; set; }
public string County { get; set; }
public string OtherAdd { get; set; }
public string Income { get; set; }
}
which is part of a List:
var results = items.ToList(); //items is IEnumerable<Item>
if int type = 1, then I want to group by more elements:
results = results
.GroupBy(e => new { e.Group, e.Person, e.Branch, e.Currency, e.Country, e.County, e.OtherAdd})
.Select(g => new Item
{
Group = g.Key.Group,
Person = g.Key.Person,
Currency = g.Key.Currency,
Currency = g.Key.Country,
Currency = g.Key.County,
Currency = g.Key.OtherAdd,
Income = g.Sum(p => double.Parse(p.Income, System.Globalization.CultureInfo.InvariantCulture)).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)
})
.ToList();
if int type = 2, then I want to group by fewer elements (e.g. because OtherAdd would be an empty String):
results = results
.GroupBy(e => new { e.Group, e.Person, e.Branch, e.Currency})
.Select(g => new Item
{
Group = g.Key.Group,
Person = g.Key.Person,
Currency = g.Key.Currency,
Income = g.Sum(p => double.Parse(p.Income, System.Globalization.CultureInfo.InvariantCulture)).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)
})
.ToList();
etc.
Is there a way for me to change the GroupBy key depending on my integer type without repeating the code?
Well, you could use the old SQL trick, conditional values:
.GroupBy(e => new { e.Group, Person = (e.Type == 1 ? e.Person : Guid.NewGuid().ToString()), ... }
While this will still include the columns in the group by, all the items will have unique keys, so it doesn't quite matter. Sadly, I don't think there's a way around generating the unique keys, unlike in SQL (where you could just use NULL).
A better way might be to implement your own grouping class, instead of using an anonymous type. You could then use your own equality and hashing semantics, to make sure whether you include all the fields or not. However, that is arguably going to be more work than just having the similar code repeated.
Or, you might want to revise your whole design. It doesn't sound like what you're trying to do makes much sense - it's already quite suspicious that you're using the same type for two different things, and using strings for all the fields doesn't help either. Maybe you could try a different object design?

How to order Nested Collections in Linq and EF

i would like to make a treelistview for my Data.
Tree should look like this
Accounts
-> Providers
-> Accounts
public sealed class AccountRoot
{
public AccountRoot()
{
Providers = new Collection<Hoster>();
}
public long AccountRootId { get; set; }
public ICollection<Hoster> Providers { get; set; }
}
public sealed class Hoster
{
public Hoster()
{
Accounts = new Collection<Account>();
}
[Key]
public long HosterId { get; set; }
public long AccountRootId { get; set; }
public string Name { get; set; }
public ICollection<Account> Accounts { get; set; }
}
public sealed class Account
{
[Key]
public long AccountId { get; set; }
public long HosterId { get; set; }
public Hoster Hoster { get; set; }
public string Name { get; set; }
}
I would like to order my query.
should be sth like
Accounts
Providers A-Z
Accounts A-Z
what i got until now is..
var query = _entity.AccountRoot.Local
.Select(x => new AccountRoot()
{
AccountRootId = x.AccountRootId,
Providers = x.Providers.OrderBy(y => y.Name).ToList()
}).ToList();
What is missing is the orderby for the next nested collection.
Thank you for your help ! :-)
It can be a bit different approaches depending on if you already have a result set, and want to just sort it in code, or if you want to construct IQueryable<> for EF which will be successfully compiled to SQL and executed with actual sorting in database.
First, assume you already have the collection in code. In this case, you have object AccountRoot, which contains collection of Providers, each of which has collection of Accounts. Obviously, you cannot return the same objects, as you need to reorder collection properties, so all you need is to just construct new ones. I would just sort the collections, but you could construct completely new entities, if you need:
var query = ...
.Select(x => new AccountRoot
{
// add copy properties here
// ....
Providers = x.Providers
.Select(y =>
{
// Here we can construct completely new entity,
// with copying all properties one by one,
// or just reorder existing collection as I do here
var result = y;
result.Accounts = y.Accounts.OrderBy(z => z.Name).ToArray();
return result;
})
.OrderBy(y => y.Name)
.ToArray()
})
.ToArray();
Second case, if you need to get it directly from SQL, is a bit different, as you cannot use all that var result = ...; ... return result stuff in lambda - it won't compile to SQL. But idea is the same - you need to construct projection from data sets. It should be something like this:
var query = ...
.Select(x => new AccountRoot
{
AccountRootId = x.AccountRootId,
// Other properties to copy
// ...
Providers = x.Providers
.Select(y => new Hoster
{
HosterId = y.HosterId,
// Other properties to copy
// ...
Accounts = y.Accounts.OrderBy(z => z.Name).ToArray(),
})
.OrderBy(y => y.Name)
.ToArray()
})
.ToArray();

Categories