Query with Where before GroupBy - c#

I have an entity like this:
public class Event
{
public string Code;
public DateTimeOffset DateTime;
}
I want to filter by Code and then group by DateTime.Date. I tried this:
var results = session
.Query<Event>()
.Where(e => e.Code == "123")
.GroupBy(e => e.DateTime.Date)
.ToList();
But I get the following error:
Raven.Client.Exceptions.InvalidQueryException: Field 'Code' isn't neither an aggregation operation nor part of the group by key
Query: from Events group by DateTime.Date where Code = $p0
Parameters: {"p0":"123"}
It can be seen from the resulting query that the where clause is being added after the group by clause, which explains the error.
So how do I perform this query in RavenDB?
EDIT:
The code "123" that I used was just an example. I need it to be a variable that is passed to the query, like this:
var results = session
.Query<Event>()
.Where(e => e.Code == code)
.GroupBy(e => e.DateTime.Date)
.ToList();

To start with, learn about the dynamic aggregation query syntax in:
https://demo.ravendb.net/demos/auto-indexes/auto-map-reduce-index
But, in your case you need to define a Static Map-Reduce Index to calculate this for you:(Sum up the number of (filtered) documents per unique Date)
i.e.
public class Result
{
public string Date { get; set; }
public int NumberOfDocs { get; set; }
}
Map = events => from event in events
where event.Code == "123"
select new Result
{
Date = event.DateTime.Date
NumberOfDocs = 1
}
Reduce = results => from result in results
group result by result.Date into g
select new Result
{
Date = g.Key,
Count = g.Sum(x => x.NumberOfDocs )
}
==> Learn about Static Map-Reduce Index in:
https://demo.ravendb.net/demos/static-indexes/map-reduce-index
Follow the detailed Walkthrough..
----------
Update:
You can use the following map-reduce index that aggregates the number of documents per Code & Date 'couple', and then you can query with 'Code'
public class Result
{
public string Code { get; set; }
public string Date { get; set; }
public int NumberOfDocs { get; set; }
}
Map = events => from event in events
select new Result
{
Code = event.Code
Date = event.DateTime.Date
NumberOfDocs = 1
}
Reduce = results => from result in results
group result by new
{
result.Code,
result.Date
}
into g
select new Result
{
Code = g.Key.Code
Date = g.Key.DateTime.Date,
NumberOfDocs = g.Sum(x => x.NumberOfDocs )
}
and then query
List<Result> queryResults = session.Query< Result, <Index_Name> >()
.Where(x => x.Code == "some-code-number")
.ToList();
and then you can also do in your code
queryResults.GroupBy(x => x.Date)

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

Translating SQL into LinQ SYntax

Below are my intended SQL query and I am having a hard time translating this into LinQ Method Syntax
select top(2) MerchantSubcriptionName,count(*) as occurence
from MerchantSubscription
group by MerchantSubcriptionName
order by occurence desc
I am supposed to select the top 2 subscription which has the most people subscribed
Just try this:
public class Subscription
{
public string MerchantSubscriptionName { get; set; }
public int Count { get; set; }
}
var list = _dbContext.MerchantSubscription.GroupBy(x => x.MerchantSubcriptionName)
.Select(x => new Subscription { MerchantSubscriptionName = x.Key, Count = x.Count() })
.OrderByDescending(x => x.Count)
.Take(2)
.ToList();

Return non-aggregates along with aggregates in Linq select

I am trying to convert the basic SQL query into Linq.
The aggregates are returning fine, but the non-aggregates are not.
SeriesId, Amount, and NumberOfTrades are perfect.
Code and Isin aren't causing errors, but they're returning the type rather than the data.
The query I'm trying to convert:
SELECT
trans_series.id,
trans_series.number,
trans_series.isin,
SUM( trans_trades.amount ),
COUNT( trans.series_id )
FROM
trans_trades
INNER JOIN trans_series ON trans_series.id = trans_trades.series_id
WHERE
trans_trades.series_id IN (
17,
18)
AND trans_trades.first_party_id IS NULL
AND trans_trades.status <> 'closed'
AND trans_trades.status <> 'cancelled'
GROUP BY trans_trades.series_id
The method:
public List<TotalByIsinViewModel> GetIssuerSeries()
{
_context = new MySQLDatabaseContext();
var result = (from ts in _context.TradesSeries
join tts in _context.TradesTrades
on ts.Id equals tts.SeriesId
where myInClause.Contains(tts.SeriesId)
group new { ts, tts } by new { tts.SeriesId } into g
select new TotalByIsinViewModel
{
SeriesId = g.Key.SeriesId,
Code = g.Select(i => i.tts.Number).Distinct().ToString(),
Isin = g.Select(i => i.ts.Isin).Distinct().ToString(),
Amount = (decimal?)g.Sum(pt => pt.tts.Amount),
NumberOfTrades = g.Count()
}).ToList();
return result;
}
The view model:
public class TotalByIsinViewModel
{
public int SeriesId { get; set; }
public string Code { get; set; }
public string Isin { get; set; }
public decimal? Amount { get; set; }
public int NumberOfTrades { get; set; }
}
I'm expecting the actual distinct varchar values from the "number" and "isin" columns, but I'm getting type data returned in my Razor page's cs Onget.
What you need is:
Code = g.First().tts.Number,
Isin = g.First()ts.Isin,
And the reason is in g.Select(i => i.tts.Number) you select a list of values.
Maybe you have only one value in list but C# still sees a list.
And ToString method for arrays is inherited from Object class. So it prints type name.
Fixed this problem by changing the "Isin" and "Code" to this:
Code = g.Select(i => i.ts.Number).Distinct().First(),
Isin = g.Select(i => i.ts.Isin).Distinct().First(),

LINQ how to Filter the data based on the value of the properties

i have a problem with filtering data in LINQ , here is my Model :
public class CoursePlan
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Semester { get; set; }
public string ModuleCode { get; set; }
public string ModuleName { get; set; }
public string Credits { get; set; }
public string OrderNumber { get; set; }
public string ModuleStatus { get; set; }
}
and here is my data Json
the problem here some modules having same OrderNumber which mean they are optional , student must study one of them and if student already study one of them , i should ignore other modules in same order number.
in other way to describe the question
i want to return a list of CoursePlan and on this list if there is two items having same OrderNumber check the ModuleStatus for each one of them and if any one is Completed remove other modules on that order otherwise return them all .
here is my code
var coursePlanList = await _sqLiteAsyncConnection.Table<CoursePlan>().ToListAsync();
var groupedData = coursePlanList.OrderBy(e => e.Semester)
.GroupBy(e => e.OrderNumber)
.Select(e => new ObservableGroupCollection<string, CoursePlan>(e))
.ToList();
for now im solving this by this algorithm and not sure if it's the best
var coursePlanList = await _sqLiteAsyncConnection.Table<CoursePlan>().ToListAsync();
List<CoursePlan> finalList = new List<CoursePlan>();
var counter = 0;
foreach (var itemPlan in coursePlanList)
{
if (counter > 0 && counter < coursePlanList.Count)
if (itemPlan.OrderNumber == coursePlanList[counter - 1].OrderNumber)
{
if (itemPlan.ModuleStatus == "Completed")
{
finalList.RemoveAll(a => a.OrderNumber == itemPlan.OrderNumber);
finalList.Add(itemPlan);
}
Debug.WriteLine(itemPlan.ModuleName + "With -->" + coursePlanList[counter - 1].ModuleName);
}
else
finalList.Add(itemPlan);
counter++;
}
var groupedData = finalList.OrderBy(e => e.ModuleStatus)
.ThenBy(e => e.Semester)
.GroupBy(e => e.Semester)
.Select(e => e)
.ToList();
CoursePlanViewList.BindingContext = new ObservableCollection<IGrouping<string, CoursePlan>>(groupedData);
Any advise or guidance would be greatly appreciated
Let me rephrase your requirement: you want to show all plans per OrderNumber that meet the condition: none of the plans in their group should be "Completed" or the plans themselves should be "Completed". All this grouped by Semester:
var plansQuery =
from p in _sqLiteAsyncConnection.Table<CoursePlan>()
group p by p.Semester into sem
select new
{
PlansInSemester =
from p in sem
group p by p.OrderNumber into gp
select new
{
PlansInOrderNumber =
gp.Where(p => !gp.Any(p1 => p1.ModuleStatus == "Completed")
|| p.ModuleStatus == "Completed")
}
};
This gives you an IQueryable that produces the course plans you want to select, but grouped in two levels, so the final result is obtained by flattening the query twice:
var coursePlanList = await plansQuery
.SelectMany(x => x.PlansInSemester
.SelectMany(y => y.PlansInOrderNumber)).ToListAsync()

Multiple items from .select() linq

I have used a lot of SQL in the past but am new to LINQ. I have the following query which selects the otherID from the relevant table successfully, however when I try to select multiple columns I am unable to do so.
This is my following query:
var getQ = db.Requests.Where(x => temp.Contains(x.carID)).Select(x => x.otherID).ToList();
I have tried
var getQ = db.Requests.Where(x => temp.Contains(x.carID)).Select(x => x.otherID && x.dayID).ToList();
I am unable to get it to work, any help appreciated, thanks
You can use anonymous type to return multiple columns
var getQ = db.Requests.Where(x => temp.Contains(x.carID))
.Select(x => new { OtherID = x.otherID, DayID = x.dayID).ToList();
You can make a custom class, as the anonymous type could not be returned from method.
class YourClass
{
public int OtherID { get; set; }
public int DayID { get; set; }
}
var getQ = db.Requests.Where(x => temp.Contains(x.carID))
.Select(x => new YourClass { OtherID = x.otherID, DayID = x.dayID).ToList();
make the change is the select statement:
var getQ = db.Requests.Where(x => temp.Contains(x.carID)).Select(x => new{x.otherID, x.dayID}).ToList();

Categories