linq-to-sql grouping anonymous type - c#

I have a table the contains appointments. These appointments have different statuses (byte from 1 to 5) and dates; the column for the date is simply called AppointDate. I pass in a list of IDs and I want to group the result based on the status AND whether the date of the appointment is past or not.
TheIDs is a list of longs that's passed in as the parameter. This is what I have so far:
var TheCounterInDB = (from a in MyDC.Appointments
where TheIDs.Contains(a.ID)
group a by a.AppointStatus into TheGroups
select new {
TheStatus = TheGroups.Key,
TheTotalCount = TheGroups.Count(),
TheLateCount = ?,
ThePendingCount = ?
}).ToList();
Basically, I want TheLateCount to be the count of all the appointments where status is 1 AND the date is past and ThePendingCount to be the count where status is 1 AND the date is not past. My anonymous type is good to return the count of all the different statuses (that's where the .Key is) but I'm wondering how to best add the date requirement into the grouping.
Thanks for your suggestions.

var TheCounterInDB = (from a in MyDC.Appointments
where TheIDs.Contains(a.ID)
group a by a.AppointStatus into TheGroups
select new {
TheStatus = TheGroups.Key,
TheTotalCount = TheGroups.Count(),
TheLateCount = TheGroups.Count(x => x.AppointStatus == 1 && x.AppointDate < DateTime.Today),
ThePendingCount = TheGroups.Count(x => x.AppointStatus == 1 && x.AppointDate >= DateTime.Today)
}).ToList();

Related

EntityFramework - Group by unordered pair in LINQ query

I have a table that contains 4 columns **(User_from, User_to,Message,Date)**
I need to get the last message a user have in all participant chats.
var all = from m in db.Peers
where m.User_Id_To == id || m.User_Id_From == id
group m by new { m.User_Id_To, m.User_Id_From } into g
select g.OrderByDescending(x => x.Date).FirstOrDefault();
The code above works to get the last message for each id,
but still having an issue.
Consider having this 2 records below :
User_from User_To Content Date
1 2 hi 01-01-2018
2 1 wlc 02-02-2018
When I am trying to use my query I am getting both records. I just need the last one according to date.
In GroupBy you can create a conditional key so when you want to group by a pair of values where order dosen't matter you can construct key as an anonymous type with the first property set to min value and the second to max value.
var all = from m in db.Peers
where m.User_Id_To == id || m.User_Id_From == id
group m by new
{
Min = m.User_Id_To < m.User_Id_From ? m.User_Id_To : m.User_Id_From,
Max = m.User_Id_To > m.User_Id_From ? m.User_Id_To : m.User_Id_From
}
into g
select g.OrderByDescending(x => x.Date).FirstOrDefault();

How to use Linq query Result in OrderBy?

Here i had scenario to get the data in date wise of this month(Present month)
Excepted Result
Date_time sum(collection.amountreceived ) Sum(bank_deposit.depositamount)
1/07/2014 2000 1000
2/07/2014 3000 3000
Schema
bank_deposit
agentid (nvarchar(30))
depositamount (DECIMAL(10,0))
date_time (TIMESTAMP)
collection
customeridn (varchar(30))
amountreceived (DECIMAL(10,0))
date_time (TIMESTAMP)
agentid (nvarchar(30))
Here I used union to get the datetime column data in one column
var unionDateColumn = ((from agent in db.collections select agent.Date_Time)
.Union(from u in db.bank_deposit select u.Date_Time)).ToList();
How can i use this unionDateColumn data for orderby and to get expected output?
Below is query for sum of amount but here my issue is how to
var model = (from coll in db.collections.Where(e => e.AgentID == item.AgentID)
let depositedAmount = db.bank_deposit.Where(d => d.AgentID == item.AgentID ).Sum(c => c.DepositedAmount) == null ? 0
: db.bank_deposit.Where(d => d.AgentID == item.AgentID).Sum(x => x.DepositedAmount)
let collectionAmount = db.collections.Where(c => c.AgentID == item.AgentID).Sum(c => c.AmountReceived) == null ? 0
: db.collections.Where(v => v.AgentID == item.AgentID).Sum(m => m.AmountReceived)
select new GetBalanceAmount
{
DepositedAmount = depositedAmount,
CollectionAmount = collectionAmount
});
I assume you want to order the result by date_time
var result = unoinDateColumn.OrderBy(t=>t.Date_Time).ToList().;
Why FirstOrDefault ? i asume you want every row.
Sajeetharan's example:
var result = unoinDateColumn.OrderBy(t=>t.Date_Time).ToList();
result is the same as "select * from unionDateColumn order by DateTime"
Your example:
model.ToList().OrderByDescending(unoinDateColumn).FirstOrDefault();
is the same as "Select * from model orderby unionDateColumn"
May i ask why you are handling each column seperately? insted of in one large array ?
Handle it in one array, and do like this model.OrderByDescending(x => x.Date_Time);
it will affect the object model and order it like this model = model.sortedBy(model.Date_Time);

Casting String as DateTime in LINQ

I have a table with a following format.
PID ID Label Value
------------------------------------------
1 1 First Name Jenna
1 2 DOB 10/12/1980
I need to retrieve all PIDs where First name starting with J and Month of DOB is 10.
in my code, I retrieve these in DataTable in C# and then tried to use LINQ to retrieve the results I want. This is just an example. These Labels could be anything user defines.
using LINQ I am able to retrieve all PIDs where First Name start with J, but every time I tried to Cast Value for DOB I get cast not valid error. I cannot change the column type in the database since Value could contain any type of information.
Here's a piece of my code. I am new to LINQ, and still trying to figure out around it.
var resultQuery = from r in query.AsEnumerable()
where (r.Field<string>("Label") == Label &&
r.Field<DateTime>("Value").Month == 10)
select r.Field<int>("PID");
Since not all items in the Value column of the table are convertible to DateTime, what you have will fail on invalid conversions. You can add in a clause that first checks that the value is a DateTime and only if it is, converts it and checks the .Month property.
DateTime d;
var resultQuery = from r in query.AsEnumerable()
where (r.Field<string>("Label") == Label &&
DateTime.TryParse(r.Field<string>("Value"), out d) &&
d.Month == 10)
select r.Field<int>("PID");
To potentially improve readability, you could also extract this out into a separate method:
var resultQuery = from r in query.AsEnumerable()
let d = TryGetDate(r.Field<string>("Value"))
where (r.Field<string>("Label") == Label &&
d != null &&
d.Month == 10)
select r.Field<int>("PID");
private DateTime? TryGetDate(string value)
{
DateTime d;
return DateTime.TryParse(value, out d) ? d : default(DateTime?);
}
You are going to end up filtering in memory which isn't very efficient.
So first select your data
var data= from r in query.AsEnumerable();
Then filter on the data
var filtered = from item in data
where item.Label == "Label"
&& Convert.ToDateTime(item.DOB).Month == 10
select item.PID;

The group by operation contains an expression that cannot be translated

It compiles normal but when I try to iterate through result of the LINQ query I 've got such exception The group by operation contains an expression that cannot be translated
The query is
var query0 = from c in dc.Prices
where Convert.ToDateTime(c.data).CompareTo(left) >= 0
&& Convert.ToDateTime(c.data).CompareTo(right) <= 0
&& c.idsticker.Equals(x)
group c by new { ((DateTime)c.data).Year, ((DateTime)c.data).Month }
into groupMonthAvg
select new
{
years = groupMonthAvg.Key.Year,
months = groupMonthAvg.Key.Month,
prices = groupMonthAvg.Average(i => i.value)
};
What expression in group by function is wrong?
Try this:
var query0 = from c in dc.Prices
let date = Convert.ToDateTime(c.data)
where date.CompareTo(left) >= 0 && date.CompareTo(right) <= 0 && c.idsticker.Equals(x)
group c by new { date.Year, date.Month } into groupMonthAvg
select new
{
years = groupMonthAvg.Key.Year,
months = groupMonthAvg.Key.Month,
prices = groupMonthAvg.Average(i => i.value)
};
I'm guessing this is because the data column in Price is a String or some other type other than DateTime.
Try changing the (DateTime)c.data) cast to a Convert.ToDateTime(c.data) instead.
I am not sure if the Linq-SQL translator supports casts.
If possible change the underlying data type in the database to a DateTime if it stores a Date Time value.

Filling in missing dates using a linq group by date query

I have a Linq query that basically counts how many entries were created on a particular day, which is done by grouping by year, month, day. The problem is that because some days won't have any entries I need to back fill those missing "calendar days" with an entry of 0 count.
My guess is that this can probably be done with a Union or something, or maybe even some simple for loop to process the records after the query.
Here is the query:
from l in context.LoginToken
where l.CreatedOn >= start && l.CreatedOn <= finish
group l by
new{l.CreatedOn.Year, l.CreatedOn.Month, l.CreatedOn.Day} into groups
orderby groups.Key.Year , groups.Key.Month , groups.Key.Day
select new StatsDateWithCount {
Count = groups.Count(),
Year = groups.Key.Year,
Month = groups.Key.Month,
Day = groups.Key.Day
}));
If I have data for 12/1 - 12/4/2009 like (simplified):
12/1/2009 20
12/2/2009 15
12/4/2009 16
I want an entry with 12/3/2009 0 added by code.
I know that in general this should be done in the DB using a denormalized table that you either populate with data or join to a calendar table, but my question is how would I accomplish this in code?
Can it be done in Linq? Should it be done in Linq?
I just did this today. I gathered the complete data from the database and then generated a "sample empty" table. Finally, I did an outer join of the empty table with the real data and used the DefaultIfEmpty() construct to deal with knowing when a row was missing from the database to fill it in with defaults.
Here's my code:
int days = 30;
// Gather the data we have in the database, which will be incomplete for the graph (i.e. missing dates/subsystems).
var dataQuery =
from tr in SourceDataTable
where (DateTime.UtcNow - tr.CreatedTime).Days < 30
group tr by new { tr.CreatedTime.Date, tr.Subsystem } into g
orderby g.Key.Date ascending, g.Key.SubSystem ascending
select new MyResults()
{
Date = g.Key.Date,
SubSystem = g.Key.SubSystem,
Count = g.Count()
};
// Generate the list of subsystems we want.
var subsystems = new[] { SubSystem.Foo, SubSystem.Bar }.AsQueryable();
// Generate the list of Dates we want.
var datetimes = new List<DateTime>();
for (int i = 0; i < days; i++)
{
datetimes.Add(DateTime.UtcNow.AddDays(-i).Date);
}
// Generate the empty table, which is the shape of the output we want but without counts.
var emptyTableQuery =
from dt in datetimes
from subsys in subsystems
select new MyResults()
{
Date = dt.Date,
SubSystem = subsys,
Count = 0
};
// Perform an outer join of the empty table with the real data and use the magic DefaultIfEmpty
// to handle the "there's no data from the database case".
var finalQuery =
from e in emptyTableQuery
join realData in dataQuery on
new { e.Date, e.SubSystem } equals
new { realData.Date, realData.SubSystem } into g
from realDataJoin in g.DefaultIfEmpty()
select new MyResults()
{
Date = e.Date,
SubSystem = e.SubSystem,
Count = realDataJoin == null ? 0 : realDataJoin.Count
};
return finalQuery.OrderBy(x => x.Date).AsEnumerable();
I made a helper function which is designed to be used with anonymous types, and reused in as generic way as possible.
Let's say this is your query to get a list of orders for each date.
var orders = db.Orders
.GroupBy(o => o.OrderDate)
.Select(o => new
{
OrderDate = o.Key,
OrderCount = o.Count(),
Sales = o.Sum(i => i.SubTotal)
}
.OrderBy(o => o.OrderDate);
For my function to work please note this list must be ordered by date. If we had a day with no sales there would be a hole in the list.
Now for the function that will fill in the blanks with a default value (instance of anonymous type).
private static IEnumerable<T> FillInEmptyDates<T>(IEnumerable<DateTime> allDates, IEnumerable<T> sourceData, Func<T, DateTime> dateSelector, Func<DateTime, T> defaultItemFactory)
{
// iterate through the source collection
var iterator = sourceData.GetEnumerator();
iterator.MoveNext();
// for each date in the desired list
foreach (var desiredDate in allDates)
{
// check if the current item exists and is the 'desired' date
if (iterator.Current != null &&
dateSelector(iterator.Current) == desiredDate)
{
// if so then return it and move to the next item
yield return iterator.Current;
iterator.MoveNext();
// if source data is now exhausted then continue
if (iterator.Current == null)
{
continue;
}
// ensure next item is not a duplicate
if (dateSelector(iterator.Current) == desiredDate)
{
throw new Exception("More than one item found in source collection with date " + desiredDate);
}
}
else
{
// if the current 'desired' item doesn't exist then
// create a dummy item using the provided factory
yield return defaultItemFactory(desiredDate);
}
}
}
The usage is as follows:
// first you must determine your desired list of dates which must be in order
// determine this however you want
var desiredDates = ....;
// fill in any holes
var ordersByDate = FillInEmptyDates(desiredDates,
// Source list (with holes)
orders,
// How do we get a date from an order
(order) => order.OrderDate,
// How do we create an 'empty' item
(date) => new
{
OrderDate = date,
OrderCount = 0,
Sales = 0
});
Must make sure there are no duplicates in the desired dates list
Both desiredDates and sourceData must be in order
Because the method is generic if you are using an anonymous type then the compiler will automatically tell you if your 'default' item is not the same 'shape' as a regular item.
Right now I include a check for duplicate items in sourceData but there is no such check in desiredDates
If you want to ensure the lists are ordered by date you will need to add extra code
Essentially what I ended up doing here is creating a list of the same type with all the dates in the range and 0 value for the count. Then union the results from my original query with this list. The major hurdle was simply creating a custom IEqualityComparer. For more details here: click here
You can generate the list of dates starting from "start" and ending at "finish", a then step by step check the number of count for each date separately

Categories