I want to use Linq with DataTable to get the following
DataTable content
periodstart periodend value
2013-01-01 2013-02-01 10
2013-02-02 2013-03-01 10
2013-03-02 2013-04-01 15
2013-04-02 2013-05-01 20
2013-05-02 2013-06-01 10
2013-06-02 2013-07-02 20
Results
2013-01-01 2013-03-01 10
2013-03-02 2013-04-01 15
2013-04-02 2013-05-01 20
2013-05-02 2013-06-01 10
2013-06-02 2013-07-02 20
Basically I want to group periods by value, but also allow repeating the same value if there is a different grouping for a period in between.
I wanted to go with group by value using min and max value for periods, but that would give me something like
2013-01-01 2013-06-01 10
2013-03-02 2013-04-01 15
2013-04-02 2013-07-02 20
which is incorrect.
How to resolve this problem?
(Adding a separate answer as my now-deleted one is wrong.)
It sounds like you just need to iterate over all rows, keeping a group going until the value part changes, and then taking the start of the first element and the end of the last element. So you could do something like this as an extension method:
public static IEnumerable<IEnumerable<T>> GroupByContiguous<T, TKey>
(this IEnumerable<T> source, Func<T, TKey> groupSelector)
{
List<T> currentGroup = new List<T>();
T groupKey = default(T);
// This could be passed in as a parameter
EqualityComparer<T> comparer = EqualityComparer<T>.Default;
using (var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
{
yield break;
}
groupKey = groupSelector(iterator.Current);
currentGroup.Add(iterator.Current);
while (iterator.MoveNext())
{
var item = iterator.Current;
var key = groupSelector(item);
if (!comparer.Equals(groupKey, key))
{
yield return currentGroup.Select(x => x);
currentGroup = new List<T>();
groupKey = key;
}
currentGroup.Add(item);
}
}
// Trailing group
yield return currentGroup.Select(x => x);
}
Then use this as:
var query = table.AsEnumerable()
.GroupByContiguous(row => row.Field<int>("value"))
.Select(g => new {
Value = g.Key,
EarliestStart = g.First().Field<DateTime>("periodstart"),
LatestEnd = g.Last().Field<DateTime>("periodend")
});
Related
I have been trying for days now to solve the following problem in WCF RIA for lightswitch using linq:
the entity is:
moveDate Direction moveQuantity moveSignedQty
moveSignedQty is positive for In and negative for Out Direction
What I need to do is create a WCF Service in the form
tDate OBalance CBalance
CBalance is the sum of moveSignedQty for a particular moveDate
OBalance is the sum of mpveSignedQt for the previous moveDate; it is zero if there in no previous day value.
My approach below did not work:
Dim close = From c In Me.Context.StockMovements
Order By c.DateOfMovement
Group New With {c} By _
tranDate = CDate(c.DateOfMovement) _
Into g = Group
Let cBal = g.Sum(Function(s) s.c.SignedQuantity_KG)
Let fDate = g.OrderBy(Function(d) d.c.DateOfMovement).FirstOrDefault
Select New accStockBalance With {
.TransactionDate = tranDate, _
.ClosingBalance = cBal}
Dim sBal = close.GroupBy(Function(d) d.TransactionDate).Select( _
Function(b)
Dim subb = b.OrderBy(Function(t) t.TransactionDate)
Return subb.Select( _
Function(s, i) New With {
.TransactionDate = s.TransactionDate, _
.ClosingBalance = subb.ElementAt(i).ClosingBalance, _
.OpeningBalance = If(i = 0, 0, subb.ElementAt(i - 1).ClosingBalance)})
End Function)
Example:
moveDate Direction moveQuantity moveSignedQty
13/02/2013 In 30 30
13/02/2013 Out 4 -4
13/02/2013 Out 10 -10
14/02/2013 Out 4 -4
14/02/2013 Out 4 -4
14/02/2013 In 7 7
15/02/2013 In 15 15
Expected result:
tDate OBalance cBalance
13/02/2013 0 16
14/02/2013 16 15
15/02/2013 15 30
The last bit sBal threw up an error that lambda statements cannot be converted to expression trees.
Kindly guide me. I have read several Q and A in this and other forums help please.
Please forgive my terrible formatting couldnt figure out how to format the example to table format
The function below is composed of 2 statements.
Function(b)
Dim subb = ...
Return ...
This kind of function can't be used in LINQ query.
Moreover, the operators ElementAt and Select using index arguments aren't supported by EntityFramework.
I show you here a solution. It is written in C#. I do not think you'll have difficulty translating the code in VB. Furthermore, I do not use the fluent LINQ syntax that I do not find very understandable. Finally, for pedagogical concern I avoid using anonymous types.
The first thing to do is actually write a query that sums the movements by date. This request must allow to obtain a list of object (MovementSumItem class) each containing the date and the sum of the movements of the day.
class MovementSumItem
{
public DateTime Date { get; set; }
public int? TotalQty { get; set; }
}
The TotalQty property is declared as nullable. I'll explain why later.
I do not understand why your close query is so complicated!?! You just need to use the GroupBy operator once. And there is no interest in using the OrderBy operator.
IQueryable<MovementSumItem> movementSumItemsQuery =
context.StockMovements
.GroupBy(
// group key selector: the key is just the date
m => m.MoveDate,
// project each group to a MovementSumItem
(groupKey, items) => new MovementSumItem {
// date is the key of the group
Date = groupKey,
// items group sum
TotalQty = items.Sum(i => i.SignedQty),
});
Now, we must be able to determine for each item which is the previous item. And this of course without first execute the query below.
Here is the logical expression I propose:
Func<MovementSumItem,MovementSumItem> previousItemSelector = item =>
movementSumItemsQuery // from all items
.Where(b => b.Date < item.Date) // consider only those corresponding to a previous date
.OrderByDescending(b => b.Date) // ordering them from newest to oldest
.FirstOrDefault(); // take the first (so, the newest) or null if collection is empty
This expression does not use any index notion. It is therefore compatible with Entity Framework.
Combining this expression with the query above, we can write the complete query. This final request must allow to obtain a list of object (BalanceItem class) each containing the date, the opening balance (sum of movements from previous item) and the closing balance (sum of movements from current item).
class BalanceItem
{
public DateTime Date { get; set; }
public int OpeningBalance { get; set; }
public int ClosingBalance { get; set; }
}
Logically, the final query can be written:
IQueryable<BalanceItem> balanceItemsQuery =
movementSumItemsQuery
.Select(
item => new BalanceItem() {
Date = item.Date,
OpeningBalance = previousItemSelector(item).TotalQty ?? 0,
ClosingBalance = item.TotalQty ?? 0
});
Unfortunately Entity Framework does not support the invocation of the function previousItemSelector. So we must integrate the expression in the query.
IQueryable<BalanceItem> balanceItemsQuery =
movementSumItemsQuery
.Select(
item => new BalanceItem()
{
Date = item.Date,
OpeningBalance = movementSumItemsQuery
.Where(b => b.Date < item.Date)
.OrderByDescending(b => b.Date)
.FirstOrDefault().TotalQty ?? 0,
ClosingBalance = item.TotalQty ?? 0
});
Finally, to run the query, simply use (for example) the ToList operator.
List<BalanceItem> result = balanceItemsQuery.ToList();
Moreover, balanceItemsQuery being IQueryable, you can specify the query by adding, for example, a filter on the date:
IQueryable<BalanceItem> balanceItemsOfTheYearQuery = balanceItemsQuery
.Where(x => x.Date.Year == 2014);
Finally, you can verify that the query executes well via a single SQL query using the ToString function of the Query object.
Console.WriteLine(balanceItemsQuery.ToString());
Why TotalQty is declared nullable?
Otherwise, the OpeningBalance value expression should be written:
OpeningBalance = movementSumItemsQuery
.Where(b => b.Date < item.Date)
.OrderByDescending(b => b.Date)
.FirstOrDefault() == null ? 0 : movementSumItemsQuery
.Where(b => b.Date < item.Date)
.OrderByDescending(b => b.Date)
.FirstOrDefault().TotalQty
However, the comparison .FirstOrDefault() == null is not supported by Entity Framework.
Basically I am listing all servers named 'ServerName' in DESC created order. If for instance I have 50 of them, I need to obtain a list of the 40 serverIds so that they can be deleted; that way I only keep the latest 10 records (Servers) created. Here is the simple SQL code:
Delete ContosoServers Where serverId In
(
Select
serverId
From
(
Select
serverId
,row_number() Over(Order By created desc) as recordNumber
From
UDS.ContosoServers
Where
name = 'ServerName'
) ServerRecords
Where recordNumber > 10
)
I think I would need to create a List of some anonymous type (serverId, recordNumber). Once I obtain that I could just loop through the list from 11 to 50 and delete all servers records, keep 1 to 10 which are the latest.
I came up with this solution but I think is way too much code. In SQL is very simple but in LINQ it looks like it requires more work. I am just trying to avoid all these loops, here it is:
private static void DeleteOldRecords(string serverName)
{
const int numberOfRecordsToKeep = 10;
using (var context = new MyContext())
{
var servers = context.ContosoServers
.Where(n => n.name == serverName)
.OrderByDescending(o => o.created)
.Select(s => new { s.serverId }).ToList();
//skip the first 10 rows and delete the rest 11,20...
int i = 0;
foreach (var s in servers)
{
if (i > numberOfRecordsToKeep - 1)
{
//delete row
var entityToDelete = context.ContosoServers.Find(s.serverId);
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
context.ContosoServers.Attach(entityToDelete);
}
context.ContosoServers.Remove(entityToDelete);
}
i++;
}
context.SaveChanges();
}
Any ideas of how to improve this? this does not seem "elegant" :-)
Thank you
Assuming that you are using LINQ to SQ, try the code below
DataClasses1DataContext context = new DataClasses1DataContext();
var d = (from s in context.ContosoServers
orderby s.created descending
select s).Take(10);
context.samples.DeleteAllOnSubmit(d);
context.SubmitChanges();
I have a list of following class:
Class Test
{
int A;
String B;
Char C;
}
List<Test> obj:
10 “Abc” 'a'
29 “Bcd” 'b'
36 “Cde” 'c'
45 “Def” 'd'
51 “Efg” 'e'
I want a linq Query that will give me output like this:
1 “Abc”
2 “Bcd”
3 “Cde”
4 “Def”
5 “Efg”
You can use new with an anonymous class to obtain a projection; use the override of Select that gives you the item number to produce the row number, like this:
var projection = obj.Select((o,i) => new {Index = i+1, Value=B});
foreach (var item in projection) {
Console.WriteLine("{0} “{1}”", item.Index, item.Value);
}
I have a linq query like this:
var accounts =
from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new AccountsReport
{
recordIndex = ?
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
}
I want to populate recordIndex with the value of current row number in collection returned by the LINQ. How can I get row number ?
Row number is not supported in linq-to-entities. You must first retrieve records from database without row number and then add row number by linq-to-objects. Something like:
var accounts =
(from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new
{
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
})
.AsEnumerable() // Moving to linq-to-objects
.Select((r, i) => new AccountReport
{
RecordIndex = i,
CreditRegistryId = r.CreditRegistryId,
AccountNumber = r.AccountNo,
});
LINQ to objects has this builtin for any enumerator:
http://weblogs.asp.net/fmarguerie/archive/2008/11/10/using-the-select-linq-query-operator-with-indexes.aspx
Edit: Although IQueryable supports it too (here and here) it has been mentioned that this does unfortunately not work for LINQ to SQL/Entities.
new []{"aap", "noot", "mies"}
.Select( (element, index) => new { element, index });
Will result in:
{ { element = aap, index = 0 },
{ element = noot, index = 1 },
{ element = mies, index = 2 } }
There are other LINQ Extension methods (like .Where) with the extra index parameter overload
Try using let like this:
int[] ints = new[] { 1, 2, 3, 4, 5 };
int counter = 0;
var result = from i in ints
where i % 2 == 0
let number = ++counter
select new { I = i, Number = number };
foreach (var r in result)
{
Console.WriteLine(r.Number + ": " + r.I);
}
I cannot test it with actual LINQ to SQL or Entity Framework right now. Note that the above code will retain the value of the counter between multiple executions of the query.
If this is not supported with your specific provider you can always foreach (thus forcing the execution of the query) and assign the number manually in code.
Because the query inside the question filters by a single id, I think the answers given wont help out. Ofcourse you can do it all in memory client side, but depending how large the dataset is, and whether network is involved, this could be an issue.
If you need a SQL ROW_NUMBER [..] OVER [..] equivalent, the only way I know is to create a view in your SQL server and query against that.
This Tested and Works:
Amend your code as follows:
int counter = 0;
var accounts =
from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new AccountsReport
{
recordIndex = counter++
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
}
Hope this helps.. Though its late:)
I have divAssignments that has potential multiple rows by rNI, an official id, according to a compound key of Indictment and booking numbers.
rNI Booking Indictment
12345 954445 10 12345
12345 954445 10 12346
12345 954445 10 12347
So ID has a count of 3 for a single booking number for this rni.
I get lost attempting to generate a count and a group by booking Number:
var moreThen = from dA in divAssignments
select new { dA.rNI, IndictmentCount = dA.indictmentNumber.Count() };
Most of the examples are dealing with static int[] and don't seem to work in my case.
How do I get a group and then a count? If I could put in a having that would be fantastic.
from a t-sql POV I'd use this:
Select rni, bookingNumber, count(*) IndictmentCount
from divAssignments
group by rni, bookingNumber
having count(*) > 0
TIA
How about something like this:
var query = from item in divAssignments
group item by item.rNI into grouping
select new
{
Id = grouping.Key,
Count = grouping.Count()
}
If you're interested in grouping by both the rNI and the booking number, I would change it to this:
var query = from item in divAssignements
group item by new { item.rNI, a.Booking } into grouping
select new
{
Id = grouping.Key,
Count = grouping.Count
};
OR
var query = from item in divAssignments
group item by item into grouping
select new
{
Id = grouping.Key,
Count = grouping.Count()
}
and implement IEquatable on the divAssignment object to support equality comparison. The other option if you'd like is to write an IEqualityComparer instance to do the composite key comparison. Your query could then look like:
var query =
divAssignments
.GroupBy(i => i, new MyCustomEqualityComparer())
.Select(i => new { Key = i.Key, Count = i.Count());
var query =
from dA in divAssignments
group dA by new { dA.rNI, dA.bookingNumber };
foreach(var grp in query)
{
Console.WriteLine("rNI={0}, bookingNumber={1} => Count={2}", grp.Key.rNI, grp.Key.bookingNumber, grp.Count());
}
If you use a Grouping operator in Linq you will get what you need. The code:
var count = from a in divAssignments
group a by new { a.rNI, a.Booking } into b
select b;
will return a collection of IGrouping objects. This will give you the Key (in my example this will be an anonymous type with an rNI and a Booking property) and a collection of the divAssignments that match the key.
Using Method syntax (much easier to read in my opinion):
First group the records, then select a new result for each group that contains the count.
var groups = divAssignments.GroupBy(d => new { d.rNI, d.Booking });
groups.Select(g=> new { g.Key.rNI, g.Key.Booking, IndictmentCount = g.Count() });