Counting relations and getting distinct value - entity-framework

I have a EF core question on how to build my query
class Report
{
public Location Location { get; set; }
public ICollection<Incident> Incidents { get; set; }
public DateTimeOffset StartTime { get; set; }
public DateTimeOffset EndTime { get; set; }
}
class Location
{
public Guid Id { get; set }
public string Name { get; set; }
}
So, I have many reports, each report has a Location, Incidents and a time range of the report.
Now, I want to find distinct locations of reports in certain time range, and return the number of incidents on that locations.
record-id
location-id
start-time
end-time
r1
loc1
2021-01-20
2021-01-22
r2
loc1
2021-01-24
2021-01-25
r3
loc2
2021-02-01
2021-02-02
r4
loc2
2021-02-02
2021-02-03
r5
loc1
2021-02-05
2021-02-07
incident-id
report-id
i1
r1
i2
r1
i3
r1
i4
r2
i5
r2
i6
r4
i7
r4
i8
r5
to find the distinct locations, i query
context.Reports
.Where(r => r.StartTime >= startTime && r.EndTime <= endTime)
.Select(r => r.Location)
.Distinct();
At the end, i want a result set as where i get to locations (loc1, loc2) and incident count of 6 for loc1 and incident count of 2 for loc2
Can anyone point me in the right direction here?
Idea:
Does this make sense?
var query = context.Reports
.Where(r => r.StartTime >= startTime && r.EndTime <= endTime);
var resultSet = await query
.Select(r => new
{
r.Location,
IncidentsCount = query.Sum(x => x.Incidents.Count)
})
.Distinct()
.ToArrayAsync();
What I want
for a query from 2021-01-20 to 2021-01-25, i want one record with
[{
Location = "loc1",
IncidentsCount = 5
}]
for the complete range (2021-01-20 to 2021-02-07) I want
[{
Location = "loc1",
IncidentsCount = 6
},{
Location = "loc2",
IncidentsCount = 2
}]

This query should work:
var query =
from r in context.Reports
from i in r.Incidents
where r.StartTime >= startTime && r.EndTime <= endTime
group i by new { r.Location } into g
select new
{
g.Key.Location,
IncidentsCount = g.Count()
};

I can see a bug in your Report class definition. Add LocationId property:
public Guid LocationId{ get; set }
Try this query
var query = context.Reports
.Where(r => r.StartTime >= startTime && r.EndTime <= endTime);
.Select(r => new
{
LocationId= r.Location.Id,
IncidentsCount = r.Incidents.Count()
})
.GroupBy(o => new {o.LocationId})
.Select(o => new
{
LocationId= o.Key.LocationId,
IncidentsCount = o.Sum(q => q.IncidentsCount)
}).ToArrayAsync();
or if you have a problem and not very many records
you can try this:
var query = context.Reports
.Where(r => r.StartTime >= startTime && r.EndTime <= endTime);
.Select(r => new
{
LocationId= r.Location.Id,
IncidentsCount = r.Incidents.Count()
}).ToArrayAsync();
var result= query.GroupBy(o => new {o.LocationId})
.Select(o => new
{
LocationId= o.Key.LocationId,
IncidentsCount = o.Sum(q => q.IncidentsCount)
}).ToArray();
This will be certainly working but grouping will be on the server.

Related

Getting all months in an Year from Entity Framework core query

I have done following EF.core(3.1/3.2) query
var monthlySalesList = context.Bids.Include(r => r.AllRequest).ThenInclude(r => r.Category).Where(b => b.UID== UID && (b.Status == MyStatus.Awarded || b.Status == MyStatus.Completed))
.GroupBy(a => new { Month =a.InsertedDate.Month })
.Select(g => new MyServiceList()
{
Key = g.Key.ToString(),
Month = g.Key.Month.ToString(),
Total= g.Sum(s => s.totalBudget)
}).ToList();
I am not getting all months in an year instead it displays only 2 months say ( 10,11) with total.In above query Mystatus is an ENUM class and MyserviceList Model class contains get & set such as key,month,sum and total .
I am getting only
-----------------
Months total
------------------
10 1234
11 1212
How can I get remaining months with zero value.
-----------------
Months total
------------------
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
10 1234
11 1212
12 0
Define another list
Make a for loop after "monthlySalesList" and make query from the list.
if you did not get data, set 0 on new list, otherwise set value .
for example :
Class :
public class ReportData
{
public int month { get; set; }
public int total { get; set; }
}
Code :
List reportData = new List();
for (int i = 1; i <= 12; i++)
{
try
{
Bids bids = new Bids();
bids = monthlySalesList.Where(t => t.month == i).FirstOrDefault();
if(bids == null)
{
reportData.Add(new ReportData() {
month = i,
total = 0
});
}
else
{
reportData.Add(new ReportData()
{
month = i,
total = bids.total
});
}
}
catch(Exception ex)
{
}
}

Filters not working in my repository in ASP.NET Core

I have these parameters in a class:
public class UserParams
{
public string Gender {get; set;}
public int MinAge {get; set;} = 1;
public int MaxAge {get; set;} = 19;
}
The query is done in the repository as shown below. First is to query for the child sex or gender and the second is to query for the child sex or gender
var query = _context.Children.AsQueryable();
query = query.Where(c => c.Sex == userParams.Gender);
var minchildDob = DateTime.Today.AddYears(-userParams.MaxAge - 1);
var maxchildDob = DateTime.Today.AddYears(-userParams.MinAge);
query = query.Where(u => u.DateOfBirth >= minchildDob && u.DateOfBirth <= maxchildDob);
return await PagedList<Child>.CreateAsync(query.AsNoTracking(), userParams.PageNumber, userParams.PageSize);
The gender filter returns empty array of children and the minchildDob and maxchildDob too not working
if (!string.IsNullOrEmpty(temp.Gender))
{
all = all.Where(u => new[] { "men", "women" }.Contains(u.sex));
//all = all.Where(t => t.sex == temp.Gender);
}
=======================Update=======================
var temp = new UserParams();
temp.Gender = "men";
var minchildDob = DateTime.Today.AddYears(-temp.MaxAge - 1);
var maxchildDob = DateTime.Today.AddYears(-temp.MinAge);
IEnumerable<Table> all = from m in _context.data
select m;
_logger.LogError("all data");
foreach (var item in all)
{
_logger.LogError(item.name);
}
_logger.LogError("============================================");
if (!string.IsNullOrEmpty(temp.Gender)) {
all = all.Where(t => t.sex == temp.Gender);
}
_logger.LogError("filter gender");
foreach (var item in all) {
_logger.LogError(item.name);
}
_logger.LogError("============================================");
if (temp.MaxAge > 0) {
all = all.Where(t => t.birthday >= minchildDob && t.birthday <= maxchildDob);
}
_logger.LogError("filter age");
foreach (var item in all)
{
_logger.LogError(item.name);
}
_logger.LogError("============================================");

EF Core - how to select count from child table based on foreign key

TableB has a field TableAId which is linked to the Id of TableA. I want to select the count from TableB based on TableAId like -
SELECT *,
(SELECT COUNT(*) FROM tableB where tableB.TableAId = tableA.Id) as count
FROM tableA
So far I have the code:
var data = _context.TableA.AsQueryable();
...
data = data.Select(l => l.TableAId= p.Id).Count();
But on the last line, p is not recognized as a variable.
How do I make this work?
EDIT :
my original query is quiet complex and already filtering data
var data = _context.TableA.AsQueryable();
data = data.Include(p => p.SomeClassA)
.Include(p => p.SomeClassB);
data = data.Where(p => p.Id == somevalue);
data = data.Where(p => p.SomeClassA.Name.Contains(someothervalue));
data = data.Where(p => p.SomeClassA.SomeField.Contains(yetanothervalue));
I tried adding this but it cannot compile
(TableAId & Count do not exist):
data = data.Join(
_context.TableB,
groupByQuery => groupByQuery.TableAId ,
TableA => TableA.Id,
(groupByQuery, TableAItem) => new
{
TableAId = groupByQuery.Id,
Count = groupByQuery.Count,
TableAItem = TableAItem
}
);
If you are just interested in count, then do the following:
var data = _context.TableB.AsQueryable();
var groupByCountQuery = data.GroupBy(a=>a.TableAId, (tableAId, tableBItems) => new
{
TableAId = tableAId,
Count = tableBItems.Count()
});
var result = groupByCountQuery.ToList(); // or change to ToListAsync()
This will give you the count based on TableAId.
If you need the tableA items as well in the result, following ca be done:
var groupByCountQuery = data.GroupBy(a=>a.TableAId, (tableAId, tableBItems) => new
{
TableAId = tableAId,
Count = tableBItems.Count()
}).Join(_context.TableA,
groupByQuery => groupByQuery.TableAId,
tableA => tableA.Id,
(groupByQuery , tableA) => new {
TableAId = groupByQuery.TableAId,
Count = groupByQuery.Count,
TableAItem = tableA
} );
Let's say TableA has properties - Id, FieldA1 and FieldA2.
First, you have to include TableB in your query so that you can take its count, like -
var data = _context.TableA.Include(p=> p.TableB).AsQueryable();
Then in the Select method you have to create a new object with TableA's properties and TableB's count, like -
var list = data.Select(p =>
new
{
Id = p.Id,
A1 = p.FieldA1,
A2 = p.FieldA2,
Count = p.OrderLines.Count
}).ToList();
Notice, this is assigned to a new variable list. That is because it does not return a list of TableA, it returns a list of an anonymous object with properties - Id, A1, A2 and Count. Therefore, you cannot assign it to the previously declared data variable, because data is of type IQueryable<TableA>.
Alternatively, you can declare a class to hold the data values, like -
public class MyData
{
public int Id { get; set; }
public string A1 { get; set; }
public string A2 { get; set; }
public int Count { get; set; }
}
and use it like -
var list = data.Select(p =>
new MyData
{
Id = p.Id,
A1 = p.FieldA1,
A2 = p.FieldA2,
Count = p.OrderLines.Count
}).ToList();
If you want to start from TableA, you can use the following linq query:
var data = _context.TableAs.AsQueryable();
var x = (from a in data
join b in _context.TableBs on a.Id equals b.TableAId
group a.TableB by a.Id into g
select new
{
TableAId = g.Key,
TableBItem = g.FirstOrDefault(),
Count = g.Count()
}).ToList();
Result:

How to combine GroupedObservables in rx.net?

I have one observable that I use GroupBy on to get a number of streams. I actually want a Scan result over each sub-stream. Let's say the observable is over product prices and the scan result is average price per product type.
I have another stream of events pertaining to those 'products' (let's say "show product price" events) and I want to combine it with the previous stream's latest product price. So the Scan output per group needs to be combined with each element of the event stream to get the latest average price for that event's product.
For some reason I cannot get the right syntax and I have been bashing away at this all day. Can someone please help?
Update
I am adding the code below to illustrate the approximate intent.
public class Node
{
private List<int> Details = new List<int>();
public void AddInfo(int x)
{
Details.Add(x );
}
public Node(int x)
{
Details.Add(x);
}
public int Index => Details[0]%10; //just to simplify the grouping and debugging
public int Latest => Details.Last();
}
public class Message
{
private static Random _random = new Random();
public int MessageNodeInfo { get; private set; }
public Message()
{
MessageNodeInfo = _random.Next();
}
}
public class AccumulatingInfoTest
{
private static Random _random=new Random();
private IObservable<Message> MessageStream()
{
TimeSpan timeSpan = TimeSpan.FromSeconds(0.5);
var ret= Observable.Generate(0,
_ => { return true; },
_ => { return 0; },
_ => { return new Message(); },
_=> timeSpan)
.Publish()
.RefCount();
return ret;
}
public class ArbitraryCommonClass
{
public int K { get; set; }
public Message M { get; set; }
public Node D { get; set; }
public ArbitraryCommonClass Combine(ArbitraryCommonClass a)
{
return new ArbitraryCommonClass()
{
K = this.K,
M = this.M ?? a.M,
D = this.D ?? a.D
};
}
}
public void Start()
{
var inputStream = MessageStream();
inputStream.Subscribe(y => Console.WriteLine("Input: K " + y.MessageNodeInfo % 10 + " V " + y.MessageNodeInfo));
var nodeInfoStream = inputStream
.Select(nodeInfo => new Node(nodeInfo.MessageNodeInfo))
.GroupBy(node => node.Index)
.Select(groupedObservable => new
{
Key = groupedObservable.Key,
Observable = groupedObservable
.Scan(
(nodeAcc, node) => { nodeAcc.AddInfo(node.Latest); return nodeAcc; }
)
.Select(a => new ArbitraryCommonClass() { K = a.Index, M = (Message)null, D = a })
}
);
var groupedMessageStream =
inputStream
.GroupBy(
m => new Node(m.MessageNodeInfo).Index
)
.Select(a => new
{
Key =a.Key,
Observable = a.Select(b => new ArbitraryCommonClass() { K = a.Key, M = b, D = null })
});
var combinedStreams = nodeInfoStream
.Merge(groupedMessageStream)
.GroupBy(s => s.Key)
.Select(grp => grp
.Scan(
(state, next) => new { Key = state.Key, Observable = Observable.CombineLatest(state.Observable, next.Observable, (x, y) => { return x.Combine(y); }) }
)
)
.Merge()
.SelectMany(x => x.Observable.Select(a=>a));
combinedStreams.Where(x=>x.M!=null).Subscribe(x => Console.WriteLine(x.K + " " + x.M.MessageNodeInfo + " " + x.D.Latest));
}
}
Assuming the following class:
public class Product
{
public string Type { get; set; } = "Default";
public decimal Price { get; set; }
}
Here's a use of GroupBy with Scan (shows the average product price grouped by type). The trick is to Select over the grouped observable to get to the individual groupings, do whatever, then (presumably) merge them back together. You could collapse the Select and the Merge into a single SelectMany, but it can be easier to read when separated:
var productSubject = new Subject<Product>();
var printSignal = new Subject<Unit>();
var latestAverages = productSubject.GroupBy(p => p.Type)
.Select(g => g
.Scan((0, 0.0m), (state, item) => (state.Item1 + 1, state.Item2 + item.Price)) //hold in state the count and the running total for each group
.Select(t => (g.Key, t.Item2 / t.Item1)) //divide to get the average
)
.Merge()
.Scan(ImmutableDictionary<string, decimal>.Empty, (state, t) => state.SetItem(t.Key, t.Item2)); //Finally, cache the average by group.
printSignal.WithLatestFrom(latestAverages, (_, d) => d)
.Subscribe(avgs =>
{
foreach (var avg in avgs)
{
Console.WriteLine($"ProductType: {avg.Key}. Average: {avg.Value}");
}
Console.WriteLine();
});
var productsList = new List<Product>()
{
new Product { Price = 1.00m },
new Product { Price = 2.00m },
new Product { Price = 3.00m },
new Product { Price = 2.00m, Type = "Alternate" },
new Product { Price = 4.00m, Type = "Alternate" },
new Product { Price = 6.00m, Type = "Alternate" },
};
productsList.ForEach(p => productSubject.OnNext(p));
printSignal.OnNext(Unit.Default);
productSubject.OnNext(new Product { Price = 4.0m });
printSignal.OnNext(Unit.Default);
productSubject.OnNext(new Product { Price = 8.0m, Type = "Alternate" });
printSignal.OnNext(Unit.Default);
This uses nuget package System.Collections.Immutable.

Entity Framework - How to get filtered Master records along with filtered Detail records?

My class relationship: ClassMaster 1----* ClassSessions
My Goal: Return all ClassMasters that have ClassSessions with StartDateTime greater than the current date, along with these ClassSessions (filtered by date).
In T-SQL I'd do this:
select *
from ClassSession
join ClassMaster on ClassMaster.ClassId = ClassSession.ClassId
Where ClassSession.StartDateTime > getdate()
I need to realize the same results in an Entity Framework 4 query. This is what I thought it would work, but does not:
var classes = ctx.ClassSessions
.Include("ClassMasters")
.Where(s => s.StartDateTime > DateTime.Now)
.Select(s => s.ClassMaster)
.ToList();
This give's me all the ClassMasters that have some ClassSession with StartDateTime > current date, but the ClassSessions property comes filled with all ClassSessions, including older than current date. I only want ClassSessions with StartDateTime > current date.
What am I doing wrong?
Thank you very much!
var masters = ctx.ClassMasters
.Where(cm => cm.ClassSessions.Any(cs => cs.StartDateTime > DateTime.Now))
.Select(cm => new { ClassMaster = cm, ClassSessions = cm.ClassSessions.Where(cs => cs.StartDateTime > DateTime.Now)
.ToList().Select(e => e.ClassMaster);
In case you loaded all ClassSession objects somewhere in context, you can still do this:
var masters = ctx.ClassMasters
.Where(cm => cm.ClassSessions.Any(cs => cs.StartDateTime > DateTime.Now))
.Select(cm => new ClassMasterWithSessionModel{ ClassMaster = cm, ClassSessions = cm.ClassSessions.Where(cs => cs.StartDateTime > DateTime.Now)
.ToList();
public class ClassMasterWithSessionModel {
ClassMaster ClassMaste { get; set; }
IEnumerable<ClassSession> ClassSessions { get; set; }
}