I'm using 2 similar LINQ queries to return a result, the only difference is the where clause (&& s.OptIn == "Yes"). Is there a way to execute this with only one query?
Instead of having a result of
A 2
B 3
and another result of
A 1
B 1
I want to have
A 2 1
B 3 1
Here's the LINQ:
var result = from s in pdc.ScanLogs
from e in pdc.Exhibits
from ce in pdc.ClientEvents
where s.ExhibitID == e.ExhibitID
&& e.ClientEventID == ce.ClientEventID
group 1 by new { ce.EventID } into d
select new {
EventID = d.Key.EventID,
Count = d.Count()
};
var result = from s in pdc.ScanLogs
from e in pdc.Exhibits
from ce in pdc.ClientEvents
where s.ExhibitID == e.ExhibitID
&& e.ClientEventID == ce.ClientEventID
&& s.OptIn == "Yes"
group 1 by new { ce.EventID } into d
select new {
EventID = d.Key.EventID,
Count = d.Count()
};
You can supply a predicate in the Count method. An example is below:
List<int> list = new List<int> { 1, 2, 3, 4, 5 };
var counts = new { CountAll = list.Count(), CountEven = list.Count(i => i % 2 == 0) };
Console.WriteLine(counts.CountEven);
A similar query written for Linq-To-Entities also worked and produced working SQL.
I haven't fully reconstructed your sample, but you should be able to rework it to something like this.
var result = from s in pdc.ScanLogs
from e in pdc.Exhibits
from ce in pdc.ClientEvents
where s.ExhibitID == e.ExhibitID
&& e.ClientEventID == ce.ClientEventID
group new { s, e, ce } by new { ce.EventID } into d
select new
{
EventID = d.Key.EventID,
Count = d.Count(),
CountOptIn = d.Count(item => item.s.OptIn == "Yes")
};
IQueryable<ScanLog> scanlogs = pdc.ScanLogs;
if (filter) scanlogs = scanlogs.Where(...);
var result = from s in scanlogs
...
Related
Is it possible to use one LINQ query to do the same?
var ints = new []{1,2,3,4,5};
var odd = from i in ints where i%2==1 select i;
var even = from i in ints where i%2==0 select i;
var q = from s in new[]{""}
select new {oddCount = odd.Count(), evenCount = even.Count()};
Console.Write(q);
Edit: Want to get this
Count() already allows you to specify a predicate. So you can combine the above in one linq like this:
var ints = new[] { 1, 2, 3, 4, 5 };
Console.Write($"Odd={ints.Count(i => i % 2 == 1)}, Even={ints.Count(i => i % 2 == 0)}");
Also note that it will be considerably faster than doing a Where() as counting is easier to perform than actually returning matching elements.
Edit
If all you want is a single linq query, you could do the following clever trick:
var ints = new[] { 1, 2, 3, 4, 5 };
var Odd = ints.Count(i => i % 2 == 1);
Console.Write($"Odd={Odd}, Even={ints.Length - Odd}");
Sounds like a perfect candidate for Aggregate:
var ints = new[] { 1, 2, 3, 4, 5 };
var info = ints.Aggregate(
new { oddCount = 0, evenCount = 0 }, (a, i) =>
new { oddCount = a.oddCount + (i & 1), evenCount = a.evenCount + ((i & 1) ^ 1) });
Console.WriteLine(info);
prints
{ oddCount = 3, evenCount = 2 }
You could do it one query like this:
var q = ints.Select(i => new { Number = i, Type = (i % 2 == 0) ? "Even" : "Odd" }).GroupBy(i => i.Type).Select(g => new { Type = g.Key, Count = g.Count() });
This would return a list though, with 'Type' and 'Count', as shown below.
If you're looking for a simple object as you currently have, you can use something simpler like this:
var q = new { OddCount = ints.Count(i => i % 2 != 0), EvenCount = ints.Count(i => i % 2 == 0) };
This would be a single object with "OddCount" and "EventCount" properties.
Here's another way that does only a single iteration over the original list.
var ints = new []{1,2,3,4,5};
string[] parities = { "even", "odd" };
var result = ints
.GroupBy(i => i % 2)
.Select(g => new { Name = parities[g.Key], Count = g.Count() });
You just move your queries directly into the select
var q = from s in new[] { "" }
select new {
oddCount = (from i in ints where i % 2 == 1 select i).Count(),
evenCount = (from i in ints where i % 2 == 0 select i).Count()};
int odd = 0;
int even = 0;
(from s in ints
let evens = s % 2 == 0 ? even++ : even
let odds = s % 2 != 0 ? odd++ : odd
select true).ToList();
With this you have the values loaded in even and odd.
This approach has the advantage it only iterates the array once
Im trying to consolidate a list of records of in and out times per day to the minimum number of records possible.
What i have done so far, is grouped up the lines into the groups they need to be in, and put the in and out times in a list for each day.
then i want to process the lists and add the first set of in and out lines onto a single line, then process the next entry and either create a new line or fill in the blanks of the previous line.
The bit im stuck with is removing the first item from the linq result after i have processed it.
happy to look at doing it a different way.
here is what i have:
List<LoginRecordLine> condensedLoginRecordLines = new List<LoginRecordLine>();
List<LoginRecordLine> currentLoginRecordLines = GetLoginRecordsForLoginRecordReport(lowerDate, upperDate, sageDatabaseID, loggedInUserID);
var groupedLines = from LoginRecordLine line in currentLoginRecordLines
group line by new { line.TimesheetID, line.WorkPatternPayRateID } into g
select new
{
Lines = g,
TimesheetID = g.Key.TimesheetID,
PayRateID = g.Key.WorkPatternPayRateID
};
foreach (var g in groupedLines)
{
var monTimes = from line in g.Lines
orderby line.MonTimeIn ascending
where line.MonTimeSpan != TimeSpan.Zero
select new
{
TimeIn = line.MonTimeIn,
TimeOut = line.MonTimeOut,
Timesheet = line.Timesheet,
PayRate = line.WorkPatternPayRate
};
var tueTimes = //Same as monday
var wedTimes = //Same as monday
var thuTimes = //same as monday
var friTimes = //same as monday
var satTimes = //same as monday
var sunTimes = //same as monday
while (monTimes.Count() != 0 || tueTimes.Count() != 0 || wedTimes.Count() != 0 || thuTimes.Count() != 0 || friTimes.Count() != 0 || satTimes.Count() != 0 || sunTimes.Count() != 0)
{
LoginRecordLine condensedLine = new LoginRecordLine();
if (monTimes.Count() >0)
{
condensedLine.MonTimeIn = monTimes.First().TimeIn;
condensedLine.MonTimeOut = monTimes.First().TimeOut;
condensedLine.Timesheet = monTimes.First().Timesheet;
condensedLine.WorkPatternPayRate = monTimes.First().PayRate;
//*************** REVELANT PART *************/
//remove first item from monday list
}
// tue
// wed
// etc
}
}
return condensedLoginRecordLines;
Update - Working code - before performance changes
List<LoginRecordLine> condensedLoginRecordLines = new List<LoginRecordLine>();
List<LoginRecordLine> currentLoginRecordLines = GetLoginRecordsForLoginRecordReport(lowerDate, upperDate, sageDatabaseID, loggedInUserID);
var groupedLines = from LoginRecordLine line in currentLoginRecordLines
group line by new { line.TimesheetID, line.WorkPatternPayRateID } into g
select new
{
Lines = g,
TimesheetID = g.Key.TimesheetID,
PayRateID = g.Key.WorkPatternPayRateID
};
foreach (var g in groupedLines)
{
var monTimes = (from line in g.Lines
orderby line.MonTimeIn ascending
where line.MonTimeSpan != TimeSpan.Zero
select new
{
TimeIn = line.MonTimeIn,
TimeOut = line.MonTimeOut,
Timesheet = line.Timesheet,
PayRate = line.WorkPatternPayRate
}).ToList();
var tueTimes = //Same as monday
var wedTimes = //Same as monday
var thuTimes = //same as monday
var friTimes = //same as monday
var satTimes = //same as monday
var sunTimes = //same as monday
while (monTimes.Count != 0 || tueTimes.Count != 0 || wedTimes.Count != 0 || thuTimes.Count != 0 || friTimes.Count != 0 || satTimes.Count != 0 || sunTimes.Count != 0)
{
LoginRecordLine condensedLine = new LoginRecordLine();
if (monTimes.Count >0)
{
condensedLine.MonTimeIn = monTimes.First().TimeIn;
condensedLine.MonTimeOut = monTimes.First().TimeOut;
condensedLine.Timesheet = monTimes.First().Timesheet;
condensedLine.WorkPatternPayRate = monTimes.First().PayRate;
condensedLoginRecordLines.Add(condensedLine);
monTimes.RemoveAt(0);
}
//etc
}
}
return condensedLoginRecordLines;
use the List.RemoveAt Method something like myList.RemoveAt(0) will remove the first item of your list
You should revise your algorithm and maybe data structures.
For anonymous types in queries I would add a DayOfWeek property, so the queries will look like:
var monTimes = from line in g.Lines
orderby line.MonTimeIn ascending
where line.MonTimeSpan != TimeSpan.Zero
select new
{
TimeIn = line.MonTimeIn,
TimeOut = line.MonTimeOut,
Timesheet = line.Timesheet,
PayRate = line.WorkPatternPayRate,
WeekDay = DayOfWeek.Monday
};
Then, the final loop will be replaced by something like:
var condensedLoginRecordLines = monTimes
.Concat(tueTimes)
.Concat(wedTimes)
..//etc
.Select(data => new CondensedLine { WeekDay = data.WeekDay, /* here all the properties are initialized */ })
.ToList();
And that's all.
If you still prefer to use the MonInTime, TueInTime, etc. properties, move the creation of CondensedLine into a separate function which applies a switch on the WeekDay and initializes the relevant properties only. In this case you should declare a private class instead the anonymous types you currently use in order to pass the information from method to another.
I came across a similar problem. In my case, the source data was a CSV file parsed into a string and I wanted to remove the header information before processing. I could have used the approach of casting to a list and removing the first entry, but I discovered that it is possible to add a second parameter to the where query that would give you the row number index. This works with both IQueryable and IEnumerable overloads as well.
public static List<string> GetDataLinesFromCSV(string csv)
{
var csvLines = csv.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
var dataLines = csvLines.AsQueryable().Where((x, idx) => idx > 0).ToList();
return dataLines;
}
First I want to say hello, I'm new to this site ;-)
My problem is to transform the following sql-query into a c# linq-query.
( I HAVE searched hard for an existing answer but I'm not able to combine the solution for
the joins on multiple conditions and the grouping / counting ! )
The sql-query :
DECLARE #datestart AS DATETIME
DECLARE #dateend AS DATETIME
SET #datestart = '01.04.2014'
SET #dateend = '30.04.2014'
SELECT md1.value AS [controller],md2.value AS [action], COUNT(md2.value) AS [accesscount], MAX(re.TIMESTAMP) AS [lastaccess] FROM recorderentries AS re
INNER JOIN messagedataentries AS md1 ON re.ID = md1.recorderentry_id AND md1.position = 0
INNER JOIN messagedataentries AS md2 ON re.ID = md2.recorderentry_id AND md2.position = 1
WHERE re.TIMESTAMP >= #datestart AND re.TIMESTAMP <= #dateend
AND re.messageid IN ('ID-01','ID-02' )
GROUP BY md1.value,md2.value
ORDER BY [accesscount] DESC
Any suggestions are welcome ...
What i have so far is this :
var _RecorderActionCalls = (from r in _DBContext.RecorderEntries
join m1 in _DBContext.MessageDataEntries on
new {
a = r.ID,
b = 0
} equals new {
a = m1.ID,
b = m1.Position
}
join m2 in _DBContext.MessageDataEntries on
new {
a = r.ID,
b = 0
} equals new {
a = m2.ID,
b = m2.Position
}
where r.TimeStamp >= StartDate & r.TimeStamp <= EndDate & (r.MessageID == "VAREC_100_01" | r.MessageID == "VAAUTH-100.01")
group r by new { md1 = m1.Value, md2 = m2.Value } into r1
select new { controller = r1.Key.md1, action = r1.Key.md2, count = r1.Key.md2.Count() }).ToList();
But this throws an exception ( translated from german ) :
DbExpressionBinding requires an input expression with a Listing Result Type ...
UPDATE : Back with headache ... ;-)
I found a solution to my problem :
var _RecorderActionCalls = _DBContext.RecorderEntries
.Where(r => r.TimeStamp >= StartDate & r.TimeStamp <= EndDate & (r.MessageID == "VAREC_100_01" | r.MessageID == "VAAUTH-100.01"))
.GroupBy(g => new { key1 = g.MessageData.FirstOrDefault(md1 => md1.Position == 0).Value, key2 = g.MessageData.FirstOrDefault(md2 => md2.Position == 1).Value })
.Select(s => new {
ControllerAction = s.Key.key1 + " - " + s.Key.key2,
Value = s.Count(),
Last = s.Max(d => d.TimeStamp)
}).ToList();
With this syntax it works for me. Thank you for thinking for me :-)
Something like that:
List<string> messageIdList = new List<string> { "ID-01", "ID-02" };
from re in RecorderEntries
from md1 in MessageDataEntries
from md2 in MessageDataEntries
where re.ID = md1.recorderEntry_id && md1.position == 0
where re.ID = md2.recorderEntry_id && md2.position == 1
where idList.Contains(re.messageid)
let joined = new { re, md1, md2 }
group joined by new { controller = joined.md1.value, action = joined.md2.value } into grouped
select new {
controller = grouped.Key.controller,
action = grouped.Key.action,
accesscount = grouped.Where(x => x.md2.value != null).Count(),
lastaccess = grouped.Max(x => x.re.TimeStamp) }
Take this example:
int[] queryValues1 = new int[10] {0,1,2,3,4,5,6,7,8,9};
int[] queryValues2 = new int[100]; // this is 0 to 100
for (int i = 0; i < queryValues2.Length; i++)
{
queryValues2[i] = i;
}
var queryResult =
from qRes1 in queryValues1
from qRes2 in queryValues2
where qRes1 * qRes2 == 12
select new { qRes1, qRes2 };
foreach (var result in queryResult)
{
textBox1.Text += result.qRes1 + " * " + result.qRes2 + " = 12" + Environment.NewLine;
}
Obviously this code will result in:
1 * 12 = 12
2 * 6 = 12
3 * 4 = 12
4 * 3 = 12
6 * 2 = 12
But what I need is only the first 3 lines. That is I do not want if 2*6 = 12 the query checks if 6*2 is also 12. Is there a way to filter this in the LINQ query or I have to do it in the foreach loop afterward?
My question just is a sample to show what I mean. so I want to know the way of doing such thing no matter what is the type of object being linqed to!
In general the simple solution would be more where conditions since the where clauses are what by definition cause LINQ to skip iterations:
var queryResult =
from qRes1 in queryValues1
from qRes2 in queryValues1
where qRes1 * qRes2 == 12
&& qRes1 <= Math.Sqrt(12)
select new { qRes1, qRes2 };
You could use .Distinct() and create your own IEqualityComparer that compares objects based on what 'equals' means in your case.
So, for your original example:
class PairSetEqualityComparer : IEqualityComparer<Tuple<int, int>>
{
public bool Equals(Tuple<int, int> x, Tuple<int, int> y)
{
return (x.Item1 == y.Item1 && x.Item2 == y.Item2) ||
(x.Item1 == y.Item2 && x.Item2 == y.Item1);
}
public int GetHashCode(Tuple<int, int> obj)
{
return obj.Item1*obj.Item2;
}
}
And, you use it like this:
var queryResult =
(from qRes1 in queryValues1
from qRes2 in queryValues2
where qRes1 * qRes2 == 12
select new Tuple<int, int>(qRes1, qRes2)).Distinct(new PairSetEqualityComparer());
TakeWhile(condition):Returns elements from a sequence as long as a specified condition is true, and then skips the remaining elements.
foreach (var result in queryResult.TakeWhile(x => x.qRes1 <= Math.Sqrt(12)))
I'm trying to transform the SQL Query below into Linq to SQL
select Categorias.IdCategoria, Categorias.Nome, SUM(lancamentos.valor)
from lancamentos
left outer join Categorias on Lancamentos.IdCategoria = Categorias.IdCategoria
where Month(DataLancamento) = 11
and Credito = 1
and Lancamentos.Ocultar = 0
group by Categorias.IdCategoria, Categorias.Nome
This is what I've done
from lancamento in Lancamentos
where lancamento.Credito == true
&& lancamento.Ocultar == false
&& lancamento.DataLancamento.Month == 10
join categoria in Categorias on lancamento.IdCategoria equals categoria.IdCategoria into temp
from lancamentoJoinCategoria in temp.DefaultIfEmpty()
group lancamentoJoinCategoria by new { lancamentoJoinCategoria.IdCategoria, lancamentoJoinCategoria.Nome } into x
select new {
IdCategoria = (int?)x.Key.IdCategoria
, Nome = x.Key.Nome
}
How do I add the SUM(lancamentos.valor) to the linq to sql above ?
It will be:
(from lancamento in Lancamentos
join categoria in Categorias on lancamento.IdCategoria equals categoria.IdCategoria into temp
from lancamentoJoinCategoria in temp.DefaultIfEmpty()
where lancamento.Credito == true
&& lancamento.Ocultar == false
&& lancamento.DataLancamento.Month == 10
group lancamento by new { lancamentoJoinCategoria.IdCategoria, lancamentoJoinCategoria.Nome } into x
select new
{
IdCategoria = (int?)x.Key.IdCategoria,
Nome = x.Key.Nome,
sumValor = x.Sum(a=>a.valor)
});
You use the .Sum() method.
Eg;
Public Sub LinqToSqlCount03()
Dim q = (From o In db.Orders _
Select o.Freight).Sum()
Console.WriteLine(q)
End Sub
according to MSDN there is no query expression equivalent to the Sum() operation.
I provided a little sample how you could use the Method Syntax of Sum() in a query.
Some query operations, such as Count
or Max, have no equivalent query
expression clause and must therefore
be expressed as a method call. Method
syntax can be combined with query
syntax in various ways. For more
information, see LINQ Query Syntax versus Method Syntax (C#).
var example = new[]
{
new { Count = 1, Name = "a" }, new { Count = 2, Name = "b" },
new { Count = 2, Name = "c" }, new { Count = 2, Name = "c" }
};
var result = from x in example
select new
{
x.Name,
Sum = (from y in example
where y.Count.Equals(2)
&& y.Name==x.Name
select y.Count).Sum()
};
var distinct = result.Distinct().ToList();