I am trying to create an Iqueryable method which returns the number of connections to a service for each day. this data is read from a SQL Server database.
Here is ConnectionItem class
public class ConnectionItem
{
public DateTime CreatedDate { get; set; }
public int NumberOfConnections { get; set; }
}
And here is my iqueryable
private IQueryable<ConnectionItem> ListItems(DataContext dataContext)
{
return dataContext.Connections
.Join(dataContext.Configurations,
connections => connections.ConfigID,
config => config.ConfigID,
(connections, config) => new { cx = connections, cf = config })
.Join(dataContext.Users,
config => config.cf.UserID,
users => users.UserID,
(config, users) => new { cf = config, su = users})
.Where(q => q.su.AccountEventID == 123 && q.cf.cx.Successful == true)
.GroupBy(g => g.cf.cx.CreatedDate.ToShortDateString())
.Select(s => new ConnectionItem
{
CreatedDate = ????,
NumberOfConnections = ????
});
}
How do I access the grouped date value and the number of items per group?
Also, is there an easier way to write this kind of statements? I am not 100% sure on how the aliases cx,cf etc work.
Any input is appreciated.
Group by the Date portion of the DateTime objects. The Date property simply drops the time part. You're converting your dates to strings so you're losing the fidelity of a DateTime object.
var eventId = 123;
return dataContext.Connections.Join(dataContext.Configurations,
conn => conn.ConfigID,
cfg => cfg.ConfigID,
(conn, cfg) => new { conn, cfg })
.Join(dataContext.Users,
x => x.cfg.UserID,
u => u.UserID,
(x, u) => new { x.conn, u })
.Where(x => x.conn.Successful && x.u.AccountEventID == eventId)
.GroupBy(x => x.conn.CreatedDate.Date)
.Select(g => new ConnectionItem
{
CreatedDate = g.Key,
NumberOfConnections = g.Count(),
});
The above could be more nicely expressed using query syntax.
var eventId = 123;
return
from conn in dataContext.Connections
join cfg in dataContext.Configurations on conn.ConfigID equals cfg.ConfigID
join u in dataContext.Users on cfg.UserID equals u.UserID
where conn.Successful && u.AccountEventID == eventId
group 1 by conn.CreatedDate.Date into g
select new ConnectionItem
{
CreatedDate = g.Key,
NumberOfConnections = g.Count(),
};
The .GroupBy linq method returns an IGrouping<TKey, TValue>, which is basically a List with a Key property that you've just grouped by.
So here
Select(s => new ConnectionItem
{
CreatedDate = ????,
NumberOfConnections = ????
});
Your iterating through a IEnumerable<IGrouping<TKey,TValue>>so you can do this
Select(s => new ConnectionItem
{
CreatedDate = s.Key
NumberOfConnections = s.Count()
});
edited as per comment I realized your looking for the number not an actual list
Just call s.Key and s.Count() ,You can get it like:
private IQueryable<ConnectionItem> ListItems(DataContext dataContext)
{
return dataContext.Connections
.Join(dataContext.Configurations,
connections => connections.ConfigID,
config => config.ConfigID,
(connections, config) => new {cx = connections, cf = config})
.Join(dataContext.Users,
config => config.cf.UserID,
users => users.UserID,
(config, users) => new {cf = config, su = users})
.Where(q => q.su.AccountEventID == 123 && q.cf.cx.Successful == true)
.GroupBy(g => g.cf.cx.CreatedDate.ToShortDateString())
.Select(s => new ConnectionItem
{
CreatedDate = s.Key,
NumberOfConnections = s.Count()
});
}
The group clause returns a sequence of IGrouping
objects that contain zero or more items that match the key value for
the group. For example, you can group a sequence of strings according
to the first letter in each string. In this case, the first letter is
the key and has a type char, and is stored in the Key property of each
IGrouping object. The compiler infers the type of the
key.
Group clause docs
Related
I'm working with a gRPC service and I have a list of Hotels where It could happen that there is the same hotel repeated but with different rooms or quotes, I would like to group those Hotels with same id and add the different Rooms inside it. I'm trying to reach it with Linq but getting this error:
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable' to 'Google.Protobuf.Collections.RepeatedField'
This is the block of code.
private Grpc.CheckRateResponse BuildGrpcResponseResult(List<Hotels> hotelsList)
{
var listResult = hotelsList.GroupBy(h => h.Id)
.Select(g => new Grpc.Hotels
{
Id = g.Key,
Name = g.Where(c => c.Id == g.Key).FirstOrDefault().Name,
CategoryCode = g.Where(c => c.Id == g.Key).FirstOrDefault().CategoryCode
Rooms = g.SelectMany(h => h.Rooms).GroupBy(r => r.RoomId).Select(x => new Grpc.Room
{
RoomId = x.Key,
RoomName = x.Where(l => l.RoomId == x.Key).FirstOrDefault().RoomName,
Rates = x.SelectMany(r => r.Rates).Select(c => new Grpc.Rate
{
RateKey = c.RateKey,
RateType = c.RateType,
BoardCode = c.BoardCode
})
})
});
Grpc.CheckRateResponse result = new Grpc.CheckRateResponse();
result.Hotels.Add(listResult);
return result;
}
I am working on a LINQ query which includes some pivot data as below
var q = data.GroupBy(x => new
{
x.Med.Name,
x.Med.GenericName,
}).ToList().Select(g =>
new SummaryDto
{
Name= g.Key.Name,
GenericName = g.Key.GenericName,
Data2012 = g.Where(z => z.ProcessDate.Year == 2012).Count(),
Data2013 = g.Where(z => z.ProcessDate.Year == 2013).Count(),
Data2014 = g.Where(z => z.ProcessDate.Year == 2014).Count(),
Data2015 = g.Where(z => z.ProcessDate.Year == 2015).Count(),
Data2016 = g.Where(z => z.ProcessDate.Year == 2016).Count(),
Data2017 = g.Where(z => z.ProcessDate.Year == 2017).Count(),
TotalCount = g.Count(),
}).AsQueryable();
return q;
The above LINQ takes too long as it queries grp q.Count()*6 times. If there are 10000 records, then it queries 60000 times
Is there a better way to make this faster?
Add year to the group key, then group again, and harvest per-group counts:
return data.GroupBy(x => new {
x.Med.Name
, x.Med.GenericName
, x.ProcessDate.Year
}).Select(g => new {
g.Key.Name
, g.Key.GenericName
, g.Key.Year
, Count = g.Count()
}).GroupBy(g => new {
g.Name
, g.GenericName
}).Select(g => new SummaryDto {
Name = g.Key.Name
, GenericName = g.Key.GenericName
, Data2012 = g.SingleOrDefault(x => x.Year == 2012)?.Count ?? 0
, Data2013 = g.SingleOrDefault(x => x.Year == 2013)?.Count ?? 0
, Data2014 = g.SingleOrDefault(x => x.Year == 2014)?.Count ?? 0
, Data2015 = g.SingleOrDefault(x => x.Year == 2015)?.Count ?? 0
, Data2016 = g.SingleOrDefault(x => x.Year == 2016)?.Count ?? 0
, Data2017 = g.SingleOrDefault(x => x.Year == 2017)?.Count ?? 0
, TotalCount = g.Sum(x => x.Count)
}).AsQueryable();
Note: This approach is problematic, because year is hard-coded in the SummaryDto class. You would be better off passing your DTO constructor an IDictionary<int,int> with counts for each year. If you make this change, the final Select(...) would look like this:
.Select(g => new SummaryDto {
Name = g.Key.Name
, GenericName = g.Key.GenericName
, TotalCount = g.Sum(x => x.Count)
, DataByYear = g.ToDictionary(i => i.Year, i => i.Count)
}).AsQueryable();
I suggest grouping inside the group by year and then converting to a dictionary to access the counts. Whether it is faster to group with year first and then count in-memory depends on the distribution of the initial grouping, but with the database it may depend on how efficiently it can group by year, so I would test to determine which seems fastest.
In any case grouping by year after the initial grouping is about 33% faster than your query in-memory, but again it is vastly dependent on the distribution. As the number of initial groups increase, the grouping by Year queries slow down to match the original query. Note that the original query without any year counts is about 1/3 the time.
Here is grouping after the database grouping:
var q = data.GroupBy(x => new {
x.Med.Name,
x.Med.GenericName,
}).ToList().Select(g => {
var gg = g.GroupBy(d => d.ProcessDate.Year).ToDictionary(d => d.Key, d => d.Count());
return new SummaryDto {
Name = g.Key.Name,
GenericName = g.Key.GenericName,
Data2012 = gg.GetValueOrDefault(2012),
Data2013 = gg.GetValueOrDefault(2013),
Data2014 = gg.GetValueOrDefault(2014),
Data2015 = gg.GetValueOrDefault(2015),
Data2016 = gg.GetValueOrDefault(2016),
Data2017 = gg.GetValueOrDefault(2017),
TotalCount = g.Count(),
};
}).AsQueryable();
I'm currently working through figuring out how to replace all of our cross-database joins with Entity Framework and Linq, I've managed to get a section of my code working, but what really makes it a bit annoying is the verbosity and complexity of the joins. Is there any method to simplify the code some, or am I stuck with long, verbose, messy code?
An example:
using (var context = new CustomerContext(CustomerID))
using (var e = new eContext())
{
var globalUserList = e.GlobalLoginCustomerBridges
.Join(e.GlobalLogins,
glcb => glcb.glcbr_gl_id,
gl => gl.gl_id,
(glcb, gl) => new { glcb, gl })
.Where(n => n.glcb.glcbr_customer_id == CustomerID)
.Select(n => new User2
{
ID = (int)n.glcb.glcbr_user_id,
GlobalLogin = n.gl.gl_login_name,
GUID = n.gl.gl_GUID
}).ToList();
var customer = e.Customers
.Join(e.DatabaseConnectionStrings,
c => c.DatabaseConnectionID,
d => d.DatabaseConnectionID,
(c, d) => new { c, d })
.Select(n => new Customer2
{
ID = n.c.CustomerID,
Name = n.c.CustomerName,
DatabaseConnectionName = n.d.DatabaseConnectionName,
DatabaseConnectionString = n.d.DatabaseConnectionString1,
GUID = n.c.cust_guid,
}).ToList().FirstOrDefault(n => n.ID == CustomerID);
var orgs = context.Organizations
.Select(o => new Organization2
{
ID = o.org_id,
Name = o.org_name,
}).ToList();
var users = context.Users
.Select(n => new User2
{
ID = n.UserID,
FirstName = n.UserFirstName,
}).ToList();
var userList = users
.Join(globalUserList,
u => u.ID,
gl => gl.ID,
(u, gl) => new { u, gl })
.Join(context.OrganizationObjectBridges,
u => u.u.ID,
oob => oob.oob_object_id,
(u, oob) => new { u, oob })
.Where(o => o.oob.oob_object_type_id == 9)
.Select(n => new User2
{
ID = n.u.u.ID,
GlobalLogin = n.u.gl.GlobalLogin,
FirstName = n.u.u.FirstName,
GUID = n.u.gl.GUID,
Customer = customer,
Organization = orgs.FirstOrDefault(o => o.ID == n.oob.oob_org_id)
}).Where(n => !isDisabled != null && n.Disabled == isDisabled).ToList();
return userList;
}
In the snippet above, I've removed ~80% of the code as most of it is simply field mappings, but it's considerably longer than what's displayed.
It seems that the first 2 queries don't need to be joined, because they belong to the same database. Couldn't you use navigation properties instead? Remember to only use join between objects which don't have a physical (navigation properties) relation.
About the last query, you could use linq queries instead of chain methods( which makes the code more readable, in my opinion). It would be something like this:
var userList = from user in users
join gul in globalUserList on user.ID = gul.ID
join oob in context.OrganizationObjectBridges on user.ID = oob.oob_object.id
where oob.oob_object_type_id == 9
select new User2
{
ID = user.ID,
GlobalLogin = gul.GlobalLogin,
FirstName = user.FirstName,
GUID = gul.GUID,
Customer = customer,
Organization = orgs.FirstOrDefault(o => o.ID == n.oob.oob_org_id)
};
Untested code, I'm sure it won't work. I'm just giving you some ideas.
I have a piece of code where I want to group by two fields and do a ToDictionary on it with the two field as a tuple key. I am not sure of the syntax. Following is what I have, But the problem is it creates a Tuple with single item.
var count = this.Db.Query<EmployeeCount>(#"select
employername, ein, month, headcount
from employerInfo A inner join MonthlyInfo B on (A.Id = B.Id)
where A.ClientId = #Client",
new { run.Client })
.GroupBy(r => new { r.EIN, r.EmployerName})
.ToDictionary(pair => Tuple.Create<string>(pair.Key.ToString()), pair => pair.ToDictionary(r => (Months)r.month, r => r.headcount));
And my EmployeeCount class is
private class CountQuery
{
public string EmployerName;
public string EIN;
public int Month;
public int HeadCount;
}
I try to do a Tuple.Create, but i am not sure how to notify that the params would be EIN and Employername for the Tuple.
I figured it out myself as below
var count = this.Db.Query<EmployeeCount>(#"select
employername, ein, month, headcount
from employerInfo A inner join MonthlyInfo B on (A.Id = B.Id)
where A.ClientId = #Client",
new { run.Client })
.GroupBy(r => new { r.EIN, r.EmployerName}).ToDictionary(pair => Tuple.Create<string,string>(pair.Key.EIN.ToString(),pair.Key.EmployerName), pair => pair.ToDictionary(r => (Months)r.ReportingMonth, r => r.FTECount))
How can I do GroupBy multiple columns in LINQ
Something similar to this in SQL:
SELECT * FROM <TableName> GROUP BY <Column1>,<Column2>
How can I convert this to LINQ:
QuantityBreakdown
(
MaterialID int,
ProductID int,
Quantity float
)
INSERT INTO #QuantityBreakdown (MaterialID, ProductID, Quantity)
SELECT MaterialID, ProductID, SUM(Quantity)
FROM #Transactions
GROUP BY MaterialID, ProductID
Use an anonymous type.
Eg
group x by new { x.Column1, x.Column2 }
Procedural sample:
.GroupBy(x => new { x.Column1, x.Column2 })
Ok got this as:
var query = (from t in Transactions
group t by new {t.MaterialID, t.ProductID}
into grp
select new
{
grp.Key.MaterialID,
grp.Key.ProductID,
Quantity = grp.Sum(t => t.Quantity)
}).ToList();
For Group By Multiple Columns, Try this instead...
GroupBy(x=> new { x.Column1, x.Column2 }, (key, group) => new
{
Key1 = key.Column1,
Key2 = key.Column2,
Result = group.ToList()
});
Same way you can add Column3, Column4 etc.
Since C# 7 you can also use value tuples:
group x by (x.Column1, x.Column2)
or
.GroupBy(x => (x.Column1, x.Column2))
C# 7.1 or greater using Tuples and Inferred tuple element names (currently it works only with linq to objects and it is not supported when expression trees are required e.g. someIQueryable.GroupBy(...). Github issue):
// declarative query syntax
var result =
from x in inMemoryTable
group x by (x.Column1, x.Column2) into g
select (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity));
// or method syntax
var result2 = inMemoryTable.GroupBy(x => (x.Column1, x.Column2))
.Select(g => (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity)));
C# 3 or greater using anonymous types:
// declarative query syntax
var result3 =
from x in table
group x by new { x.Column1, x.Column2 } into g
select new { g.Key.Column1, g.Key.Column2, QuantitySum = g.Sum(x => x.Quantity) };
// or method syntax
var result4 = table.GroupBy(x => new { x.Column1, x.Column2 })
.Select(g =>
new { g.Key.Column1, g.Key.Column2 , QuantitySum= g.Sum(x => x.Quantity) });
You can also use a Tuple<> for a strongly-typed grouping.
from grouping in list.GroupBy(x => new Tuple<string,string,string>(x.Person.LastName,x.Person.FirstName,x.Person.MiddleName))
select new SummaryItem
{
LastName = grouping.Key.Item1,
FirstName = grouping.Key.Item2,
MiddleName = grouping.Key.Item3,
DayCount = grouping.Count(),
AmountBilled = grouping.Sum(x => x.Rate),
}
Though this question is asking about group by class properties, if you want to group by multiple columns against a ADO object (like a DataTable), you have to assign your "new" items to variables:
EnumerableRowCollection<DataRow> ClientProfiles = CurrentProfiles.AsEnumerable()
.Where(x => CheckProfileTypes.Contains(x.Field<object>(ProfileTypeField).ToString()));
// do other stuff, then check for dups...
var Dups = ClientProfiles.AsParallel()
.GroupBy(x => new { InterfaceID = x.Field<object>(InterfaceField).ToString(), ProfileType = x.Field<object>(ProfileTypeField).ToString() })
.Where(z => z.Count() > 1)
.Select(z => z);
var Results= query.GroupBy(f => new { /* add members here */ });
A thing to note is that you need to send in an object for Lambda expressions and can't use an instance for a class.
Example:
public class Key
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
This will compile but will generate one key per cycle.
var groupedCycles = cycles.GroupBy(x => new Key
{
Prop1 = x.Column1,
Prop2 = x.Column2
})
If you wan't to name the key properties and then retreive them you can do it like this instead. This will GroupBy correctly and give you the key properties.
var groupedCycles = cycles.GroupBy(x => new
{
Prop1 = x.Column1,
Prop2= x.Column2
})
foreach (var groupedCycle in groupedCycles)
{
var key = new Key();
key.Prop1 = groupedCycle.Key.Prop1;
key.Prop2 = groupedCycle.Key.Prop2;
}
group x by new { x.Col, x.Col}
.GroupBy(x => (x.MaterialID, x.ProductID))
.GroupBy(x => x.Column1 + " " + x.Column2)
For VB and anonymous/lambda:
query.GroupBy(Function(x) New With {Key x.Field1, Key x.Field2, Key x.FieldN })