Join 2 tables to retrieve name and count linq Entity Framework - c#

I have 2 tables Orders and Items and I am trying to query these 2 tables for statistics retrieval.
Orders has columns OrderID[PK], ItemID[FK], OrderStatus etc.
Items has columns ItemID[PK], ItemName, ItemPrice etc.
I am fetching list of orders based on date range and then I am returning their counts based on their status.
Below is my StatisticsResponse.cs to return the response.
public class StatisticsResponse
{
public int CancelledOrderCount { get; set; }
public int CompletedOrderCount { get; set; }
public int InProgressOrderCount { get; set; }
public int TotalOrders { get; set; }
public Dictionary<string,int> ItemOrders { get; set;}
}
This is how I am retrieving Orders between 2 dates.
var orders = _unitOfWork.OrderRepository
.GetMany(x => (x.OrderStatus == "Pending"
&& x.OrderDate.Value.Date >= dtStartDate
&& x.OrderDate.Value.Date < dtEndDate) ||
((x.OrderStatus == "Completed" || x.OrderStatus == "Cancelled")
&& x.DeliveryDate.Date >= dtStartDate || x.DeliveryDate.Date < dtEndDate) || (x.LastUpdated.Value.Date >= dtStartDate || x.LastUpdated.Value.Date < dtEndDate)).ToList();
if (orders != null)
{
return new StatisticsResponse()
{
TotalOrders = orders.Count(),
CancelledOrderCount = orders.Where(x => x.OrderStatus == "Cancelled").Count(),
CompletedOrderCount = orders.Where(x => x.OrderStatus == "Completed").Count(),
InProgressOrderCount = orders.Where(x => x.OrderStatus != "Completed" && x.OrderStatus != "Cancelled").Count()
}
}
Now, in the ItemOrders property, which is of type Dictionary<string,int>, I want to group each item with their name and count. I have ItemID in my orders list, and I would like to join 2 tables to get the name before storing.
I have tried to use GroupBy as below but am totally stuck on how to get the name for the Item after grouping
ItemOrders = new Dictionary<string, int>
{
orders.GroupBy(x=>x.ItemID)// Stuck here
}
I also read about GroupJoin but couldn't quite make sure whether it can fit in here.
Could someone please let me know how I can join these 2 tables to get their name based on their ID?

You can use something along this:
using System.Entity.Data; //to use Include()
...
Dictionary<string,int> itemOrders = dbContext.Orders.Include(o=> o.Item)
.GroupBy(o=> o.Item)
.ToDictionary(g => g.Key.Name, g => g.Count());
This is assuming:
There is a navigation property set up from Order to Item
Each Order has one Item

So, I was able to achieve this with GroupJoin through various online example as below:
if (orders != null)
{
var items = _unitOfWork.ItemRepository.GetAll();
var itemOrders = items.GroupJoin(orders,
item => item.ItemID,
ord => ord.ItemID,
(item, ordrs) => new
{
Orders = ordrs,
itemName = item.ItemName
});
StatisticsResponse statsResponse = new StatisticsResponse()
{
//...
};
foreach (var item in itemOrders)
{
statsResponse.ItemOrders.Add(item.itemName, item.Orders.Count());
}
return statsResponse;
}

Related

Parent Child Search at same table LINQ C#

I have a query to search in single table where records are at Parent, Child Relationship like below
Table
Id
ParentId
Name
Status
so my query is like
var projects = from p in _projectSetupRepository.Table
from all in _projectSetupRepository.Table
where p.Status == status && p.ParentId == null &&
all.Status == status && ((all.ParentId == null && all.Id == p.Id) || all.ParentId == p.Id)
select p;
if (!string.IsNullOrEmpty(search))
projects = projects.Where(c => c.Name.Contains(search)).OrderBy(c => c.Name);
but I don't get actual results of parents if search with the child's name. What was the issue in the query?
PS
table contains thousands of data and performance is very important
PS
public class ProjectSetup
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Name { get; set; }
public bool Status { get; set; }
public ProjectSetup Project{ get; set; }
public virtual ICollection<ProjectSetup> SubProjects { get; set; }
}
Id
ParentId
Name
Status
1
null
P1
true
2
1
T1
true
3
1
T2
true
4
3
Q1
true
You can find all parents with specific child name(this query searches Level1 childs only):
var status = true;
var search = "T";
var projects = (from parent in context.Projects
join child in context.Projects on parent.Id equals child.ParentId into joinedT
from pd in joinedT.DefaultIfEmpty()
where parent.Status == status
&& parent.ParentId == null //filter top level parents only?
&& pd.Name.Contains(search)
select parent).Distinct().OrderBy(c => c.Name);
foreach(var p in projects)
{
Console.WriteLine(p.Id+":"+p.Name);
}
Console.WriteLine("Found results:"+ projects.Count());
here's fiddle: https://dotnetfiddle.net/PaCidA
If you are looking for multi-level solution I suggest you to take a look at hierarchyid data type and its usage in EF LINQ
https://softwarehut.com/blog/tech/hierarchyid-entity-framework
https://kariera.future-processing.pl/blog/hierarchy-in-the-entity-framework-6-with-the-hierarchyid-type/
Check for performance the folloging algorithm. Idea that we can Include several Project and check on the client what to load again. Algorithm uses EF Core navigation properties fixup.
var query = in_projectSetupRepository.Table
.Include(p => p.Project.Project.Project) // you can increase count of loaded parents
.Where(p => p.Status == status)
.AsQueryable();
var loadedDictionary = new Dictionary<int, ProjectSetup>>();
var projects = query;
if (!string.IsNullOrEmpty(search))
projects = projects.Where(c => c.Name.Contains(search));
while (true)
{
var loaded = projects.ToList();
// collect loaded with parents
foreach(var p in loaded)
{
var current = p;
do
{
if (!loadedDictionary.ContainsKey(current.Id))
loadedDictionary.Add(current.Id, current);
current = current.Project;
}
while (current != null)
}
// collect Ids of incomplete
var idsToLoad = loadedDictionary.Values
.Where(x => x.ParentId != null && x.Project == null)
.Select(x => x.ParentId.Value)
.ToList();
// everything is ok
if (idsToLoad.Count == 0)
break;
// make query to load another portion of objects
projects = query.Where(p => idsToLoad.Contains(p.Id));
}
var result = loadedDictionary.Values
.OrderBy(c => c.Name)
.ToList();

LINQ searching a List that contains a Class with objects

I'm new at C#, I know how to do a LINQ search to a List with one field/type, but not with many types of an object. I created a List
List<Reader> results = new List<Reader>();
That contain this class:
public class Reader
{
public int ID { get; set; }
public string Name { get; set; }
public string Course { get; set; }
public int Grade { get; set; }
public Reader(int id, string name, string course, int grade)
{
ID = id;
Name = name;
Course = course;
Grade = grade;
}
}
I want to search it with LINQ and match the ID and Name of a user that entered the site.
If this two fields are the same I want to take from the List the users Course and Grade.
Any suggestion how to do it ?
A simple Where for condition(s) and Select for representation should do:
List<Reader> results = ...
var data = results
.Where(item => item.ID == userID && item.Name == userName)
// .OrderBy(item => item.Course) // uncomment if you want to order by course
.Select(item => $"Course: {item.Course} Grade: {item.Grade}");
foreach (var record in data)
Console.WriteLine(record);
First, let's assume that you have two variables that hold the values introduced by the user. Those variables are userName of type string and id of type integer. If you just want a variable that holds the course and the Grade you could select a new anonymous type and do the query like this:
var values= results
.Where(item => item.ID == userID && item.Name == userName)
.Select(item => new { Course = item.Course, Grade = item.Grade });
then you could use the values like:
values.Grades
values.Course
var Selecteduser = results.Where(x => x.Name == selectedname && x.ID == ID).ToList();
if (Selecteduser.Count != 0)
{
//found match ..(Selecteduser[0])
string selectedcourse = Selecteduser[0].Course;
int selectedgrade = Selecteduser[0].Grade;
}
else
{
//coudlnt find match
}

C# LINQ. Searching for object by object name property or name part

I am trying to find all invoices to buyers, searching by buyer name (contains and equals filter). Looking for the cleanest way to do it.
I have a list of Buyers.
List <Buyer> AllBuyers;
And a Buyer is:
public class Buyer
{
public string BuyerIdentifier{ get; set; }
public string Name { get; set; }
}
I have a list of Invoices to buyers.
List <Invoice> AllInvoices;
And an Invoice is
public class Invoice
{
public string InvoiceID { get; set; }
public string BuyerID { get; set; }
public string Amount{ get; set; }
}
What I am doing currently:
List<string> BuyerIDs = new List<string> { };
foreach (Invoice inv in AllInvoices)
{
if (!(BuyerIDs.Contains(inv.BuyerID)))
{
// add BuyerID to list if it's not already there. Getting id's that are present on invoices and whose Buyer names match using contains or equals
BuyerIDs.Add(AllBuyers.First(b => b.BuyerIdentifier == inv.BuyerID
&& (b.Name.IndexOf(SearchValue, StringComparison.OrdinalIgnoreCase) >= 0)).BuyerIdentifier);
}
}
Invoices = AllInvoices.FindAll(i=> BuyerIDs.Contains(i.BuyerID));
LINQ query syntax is a little easier for me to understand than LINQ methods to join. So after replies below I am now doing this:
Invoices = (from buyer in AllBuyers
join invoice in AllInvoices on buyer.BuyerIdentifier equals invoice.BuyerID
where buyer.Name.IndexOf(SearchValue, StringComparison.OrdinalIgnoreCase) >= 0
select invoice).ToList();
If all you need are the invoices, you could join your two collections, filter, and select the invoices
AllBuyers.Join(AllInvoices,
a => a.BuyerIdentifier,
a => a.BuyerID,
(b, i) => new { Buyer = b, Invoice = i })
.Where(a => a.Buyer.Name.Contains("name"))
.Select(a => a.Invoice).ToList();
If you want the buyers as well, just leave out the .Select(a => a.Invoice).
The Contains method of a string will match an equals as well.
Here is a suggestion where I create a dictionary with BuyerIdentifier as keys and a List of Invoices as values:
var dict = AllBuyers.ToDictionary(k => k.BuyerIdentifier,
v => AllInvoices.Where(i => i.BuyerID == v.BuyerIdentifier).ToList());
Then you can access a list of Invoices for a specific buyer like so:
List<Invoice> buyerInvoices = dict[buyerId];
This should work for you:
var InvoiceGrouping = AllInvoices.GroupBy(invoice => invoice.BuyerID)
.Where(grouping => AllBuyers.Any(buyer => buyer.BuyerIdentifier == grouping.Key && buyer.Name.IndexOf(pair.Value, StringComparison.OrdinalIgnoreCase) >= 0));
What you end up with is a grouping which has a buyer's ID as the key and all their invoices as the value.
If you want just a flat list of invoices, you can do like so:
var Invoices = AllInvoices.GroupBy(invoice => invoice.BuyerID)
.Where(grouping => AllBuyers.Any(buyer => buyer.BuyerIdentifier == grouping.Key && buyer.Name.IndexOf(pair.Value, StringComparison.OrdinalIgnoreCase) >= 0))
.SelectMany(grouping => grouping);
Note the added SelectMany at the end which, since IGrouping implements IEnumerable, flattens the groupings into a single enumeration of values.
As an ILookup fanboy, this would be my approach:
var buyerMap = AllBuyers
.Where(b => b.Name.IndexOf(SearchValue, StringComparison.OrdinalIgnoreCase) >= 0)
.ToDictionary(b => b.BuyerIdentifier);
var invoiceLookup = AllInvoices
.Where(i => buyerMap.ContainsKey(i.BuyerID))
.ToLookup(x => x.BuyerID);
foreach (var invoiceGroup in invoiceLookup)
{
var buyerId = invoiceGroup.Key;
var buyer = buyerMap[buyerId];
var invoicesForBuyer = invoiceGroup.ToList();
// Do your stuff with buyer and invoicesForBuyer
}

Optimizing LINQ Query to avoid multiple enumerations

I have written a code like this in my .NET project:
var v = ctx.Items
.Where(x => x.userid== user.userid)
.Select(e => new MyViewModel
{
Title = e.Title,
CurrentPrice = e.CurrenctPrice.Value,
ItemID = e.ItemID.ToString(),
Sales = e.Transactions.Where(p => p.TransactionDate >= intoPast && p.TransactionDate <= endDate).Sum(x => x.QuantityPurchased)
})
.Where(x => x.Sales > 0 && ((filterWord == "") || (filterWord != "" && x.Title.ToLower().Contains(filterWord.ToLower()))));
where "ctx" is my object context variable...
And this is the ViewModelClass that I use:
public class MyViewModel
{
public string Title { get; set; }
public int Sales { get; set; }
public string ItemID { get; set; }
public double CurrentPrice { get; set; }
}
The thing that most bugs me here is the sales property... As you can see i set its value in select statement. This way all my data gets enumerated every time...
What I was thinking here is to create a method called "getsales()"... And then to just simply call the GetSales method in my where statement like this:
.Where(x=>X.GetSales(/*neccesary parameters here*/)...)
In order to avoid having multiple enumerations...
But I'm not really sure how to do it...
Can someone help me out here?
I think this is what you're looking for:
var v = ctx.Items
.Where(x =>
x.userid == user.userid &&
(filterWord == "" || x.Title.ToLower().Contains(filterWord.ToLower())))
.Select(e => new MyViewModel
{
Title = e.Title,
CurrentPrice = e.CurrentPrice.Value,
ItemID = e.ItemID.ToString(),
Sales = e.Transactions
.Where(p =>
p.TransactionDate >= intoPast &&
p.TransactionDate <= endDate)
.Sum(x => x.QuantityPurchased)
})
.Where(x => x.Sales > 0);

Not sure how to filter this data

Let's say I have Order class as follows.
class Order
{
public Guid ID { get; set; }
public int ProductID { get; set; }
public int CategoryID { get; set; }
}
In order, I get a list of orders from database and populate in local list that is declared as follows.
List<Order> Orders = new List<Order>();
I also have a cached list of orders that only contains Order.ID field.
List<Guid> CachedOrderIDs;
Now I want to modify following query to include Orders that are presnted in CachedOrderIDs.
var o = Orders.Where(m => m.ProductID > 200 && m.CategoryID > 500).ToList();
How can I do this?
Use Contains method:
var o = Orders.Where(m => m.ProductID > 200 &&
m.CategoryID > 500 &&
CachedOrderIDs.Contains(m.ID)).ToList();
You could try this one:
var o = Orders.Where(m => m.ProductID > 200 &&
m.CategoryID > 500 &&
CachedOrderIDs.Contains(m.Guid)
).ToList();

Categories

Resources