How to translate SQL query with multiple grouping in EF equivalent - c#

I have a database (PostgreSQL) where there is a main table student, additional information amount and 3 dictionaries. I make a query with grouping by three fields of dictionary IDs, output the number of objects and the amount from an additional table with a condition. And how to translate it to EF Core 6?
create table region (id serial primary key, name varchar);
create table district (id serial primary key, name varchar);
create table department (id serial primary key, name varchar);
create table student (
id serial primary key,
name varchar,
region_id bigint references region,
district_id bigint references district,
department_id bigint references department
);
create table amount (
id serial primary key,
student_id bigint references student on delete cascade,
value numeric,
year int
);
My SQL query is working well:
select
t.region_id,
region."name" region_name,
t.district_id,
district."name" district_name,
t.department_id,
department."name" department_name,
t.cnt,
t.value
from (
select
region_id,
district_id,
department_id,
count(distinct s.id) cnt,
sum(a.value) "value"
from student s
join amount a on s.id = a.student_id
where a.year = 2020
group by region_id, district_id, department_id
) t
join region on t.region_id = region.id
join district on t.district_id = district.id
join department on t.department_id = department.id
How do I get names from dictionaries when translating a query to EF?
[Table("student")]
public class Student
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("name")]
public string? Name { get; set; }
[Column("region_id")]
public int? RegionId { get; set; }
[Column("district_id")]
public int? DistrictId { get; set; }
[Column("department_id")]
public int? DepartmentId { get; set; }
[ForeignKey(nameof(RegionId))]
public virtual Region? Region { get; set; }
[ForeignKey(nameof(DistrictId))]
public virtual District? District { get; set; }
[ForeignKey(nameof(DepartmentId))]
public virtual Department? Department { get; set; }
public ICollection<Amount>? Amounts { get; set; }
}
EF query:
var result = await db.Student
.GroupBy(x => new { x.RegionId, x.DistrictId, x.DepartmentId })
.Select(x => new
{
x.Key.RegionId,
x.Key.DistrictId,
x.Key.DepartmentId,
Cnt = x.Count(),
Value = x.Sum(c => c.Amounts.Where(v => v.Year == 2020).Sum(v => v.Value))
})
.ToListAsync();
At the moment I have such a solution, but will such a request be optimal in the end? In addition, you need to add a null check here.
RegionName = x.First().Region.Name,
DistrictName = x.First().District.Name,
DepartmentName = x.First().Department.Name,

This can be done with the following EF Core query:
var query = from student in db.Student
join region in db.Region on student.RegionId equals region.id
join district in db.District on student.DistrictId equals district.id
join department in db.Department on student.DepartmentId equals department.id
join amount in db.Amount on student.Id equals amount.student_id
where amount.Year == 2020
group amount by new
{
student.RegionId,
RegionName = region.Name,
student.DistrictId,
DistrictName = district.Name,
student.DepartmentId,
DepartmentName = department.Name
} into g
select new
{
g.Key.RegionName,
g.Key.DistrictName,
g.Key.DepartmentName,
Cnt = g.Count(),
Value = g.Sum(a => a.Value)
};
var result = await query.ToListAsync();
It is translated into the following SQL:
SELECT r.name AS "RegionName", d.name AS "DistrictName", d0.name AS "DepartmentName",
count(*)::int AS "Cnt", COALESCE(sum(a.value), 0.0) AS "Value"
FROM student AS s
INNER JOIN region AS r ON s.region_id = r.id
INNER JOIN district AS d ON s.district_id = d.id
INNER JOIN department AS d0 ON s.department_id = d0.id
INNER JOIN amount AS a ON s.id = a.student_id
WHERE a.year = 2020
GROUP BY s.region_id, r.name, s.district_id, d.name, s.department_id, d0.name
If you need LEFT JOIN then it will be:
var query = from student in db.Student
join region in db.Region on student.RegionId equals region.id into rg
from r in rg.DefaultIfEmpty()
join district in db.District on student.DistrictId equals district.id into dg
from d in dg.DefaultIfEmpty()
join department in db.Department on student.DepartmentId equals department.id into dpg
from dp in dpg.DefaultIfEmpty()
join amount in db.Amount on student.Id equals amount.student_id
where amount.Year == 2020
group amount by new
{
student.RegionId,
RegionName = r.Name,
student.DistrictId,
DistrictName = d.Name,
student.DepartmentId,
DepartmentName = dp.Name
} into g
select new
{
g.Key.RegionName,
g.Key.DistrictName,
g.Key.DepartmentName,
Cnt = g.Count(),
Value = g.Sum(a => a.Value)
};

Try the following query:
var query =
from s in db.Student
from a in s.Amounts
where a.Year == 2020
group a by new
{
s.RegionId,
RegionName = s.Region.Name,
s.DistrictId,
DistrictName = s.District.Name,
s.DepartmentId,
DepartmentName = s.Department.Name
} into g
select new
{
x.Key.RegionId,
x.Key.DepartmentName,
x.Key.DistrictId,
x.Key.DistrictName,
x.Key.DepartmentId,
x.Key.DepartmentName,
Cnt = x.Select(v => v.StudentId).Distinct().Count(),
Value = x.Sum(v => v.Value)
};
var result = await query.ToListAsync();
Not sure that Cnt = x.Select(v => v.StudentId).Distinct().Count() will be translated, it depends on EF Core version.
UPDATE - added equivalent to the SQL query:
var groupingQuery =
from s in db.Student
from a in s.Amounts
where a.Year == 2020
group a by new
{
s.RegionId,
s.DistrictId,
s.DepartmentId,
} into g
select new
{
x.Key.RegionId,
x.Key.DistrictId,
x.Key.DepartmentId,
Cnt = x.Select(v => v.StudentId).Distinct().Count(),
Value = x.Sum(v => v.Value)
};
var query =
from g in groupingQuery
join region in db.Region on g.RegionId equals region.id
join district in db.District on g.DistrictId equals district.id
join department in db.Department on g.DepartmentId equals department.id
select new
{
g.RegionId,
RegionName = region.Name,
g.DistrictId,
DistrictName = district.Name,
g.DepartmentId,
DepartmentName = department.Name,
g.Cnt,
g.Value
};
var result = await query.ToListAsync();

Related

Create Row_Number in linq query

I have a query which I wish to replace with a Linq as query syntax. Below is my SQL query which I'm trying to create I've got it all working except the row_number:
SELECT
ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS ID,
r.StateProvinceRegion,
AVG(s.Longitude) AS Longitude,
AVG(s.Latitude) AS Latitude
FROM
RestaurantAddress r
INNER JOIN
Restaurant s ON s.RestaurantId = r.RestaurantId
GROUP BY
StateProvinceRegion
Query result:
ID StateRegion Longitude Latitude
-----------------------------------------------------
1 Auckland 174.759541622222 -36.8552809611111
2 Mumbai -73.9904097 40.7036292
3 New York -73.9904097 40.7036292
This is the current code I've implemented which has the same output except doesn't have the ROW_NUMBER (ID) which I'm trying to figure out how to output.
var region = from restaurantAddress in _context.RestaurantAddress
join restaurant in _context.Restaurant on restaurantAddress.RestaurantId equals restaurant.RestaurantId
group restaurant by new { restaurantAddress.StateProvinceRegion } into g
select new { g.Key.StateProvinceRegion, Latitude = g.Average(p => p.Latitude), Longitude = g.Average(p => p.Longitude) };
This is what I've tried:
int number = 0;
var region = from restaurantAddress in _context.RestaurantAddress
join restaurant in _context.Restaurant on restaurantAddress.RestaurantId equals restaurant.RestaurantId
group restaurant by new { restaurantAddress.StateProvinceRegion } into g
select new { id = number++ , g.Key.StateProvinceRegion, Latitude = g.Average(p => p.Latitude), Longitude = g.Average(p => p.Longitude) };
but number++ returns an error:
An expression tree may not contain an assignment operator
It's simple:
var region =
(
from restaurantAddress in _context.RestaurantAddress
join restaurant in _context.Restaurant
on restaurantAddress.RestaurantId equals restaurant.RestaurantId
group restaurant by new { restaurantAddress.StateProvinceRegion } into g
select new
{
g.Key.StateProvinceRegion,
Latitude = g.Average(p => p.Latitude),
Longitude = g.Average(p => p.Longitude),
}
)
.AsEnumerable()
.Select((x, id) => new
{
id,
x.StateProvinceRegion,
x.Latitude,
x.Longitude,
});

C# EntityFramework 6.0 Querying Decimal > 0 Gives No Result

So I have set the model here:
public class Stock
{
[Key]
//[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Guid StockId { get; set; } = Guid.NewGuid();
.....
public decimal Remaining { get; set; }
}
Also with fluent mapping:
protected override void OnModelCreating(DbModelBuilder mb)
{
base.OnModelCreating(mb);
mb.HasDefaultSchema("dbo");
mb.Entity<Stock>().Property(m => m.Remaining).HasPrecision(16, 3);
....
}
So the precision is 3 decimal points.
It is so weird when I querying with linq:
IQueryable<StockDetail> stocks = (from s in db.Stocks .... where s.Remaining > 0);
stocks.Any() --> is false;
The where s.Remaining > 0 is the problem, how to fix this?
EDIT
The actual query:
IQueryable<StockDetail> stocks = (from s in db.Stocks
join items in db.Items on s.Item equals items.ItemId
join types in db.ItemTypes on s.ItemType equals types.ItemTypeId
join colors in db.ItemColors on s.ItemColor equals colors.ItemColorId
join units in db.ItemUnits on s.Unit equals units.ItemUnitId
join buyers in db.Buyers on new { Buyer = s.Buyer } equals new { Buyer = (Guid?)buyers.BuyerId } into temp2
from buyers in temp2.DefaultIfEmpty(null)
join suppliers in db.Suppliers.DefaultIfEmpty() on new { Supplier = s.Supplier } equals new { Supplier = (Guid?)suppliers.SupplierId } into temp3
from suppliers in temp3.DefaultIfEmpty(null)
join op in db.UserProfiles on s.Operator equals op.UserId
join curr in db.Currencies on s.Currency equals curr.CurrencyId
let sales = db.Sales.Where(m => m.SId == s.Sales).FirstOrDefault()
join parent in db.Stocks on s.Parent equals parent.StockId
where s.Remaining > 0
select new StockDetail()
{
Buyer = buyers != null ? (Guid?)buyers.BuyerId : null,
BuyerName = buyers != null ? buyers.Name : null,
Supplier = suppliers.SupplierId,
SupplierName = suppliers.Name,
Code = s.Code,
Color = colors.ItemColorId,
ColorCode = colors.Code,
ColorName = colors.Color,
DateCreated = s.DateCreated,
Gramation = s.Gramation,
Item = items.ItemId,
ItemName = items.Name,
LastEdited = s.LastEdited,
Operator = op.UserId,
OperatorName = op.UserName,
PO = s.PO,
Remaining = s.Remaining,
SC = s.SC,
Qty = s.Qty,
Setting = s.ItemSetting,
StockId = s.StockId,
Type = types.ItemTypeId,
TypeName = types.Type,
Unit = units.ItemUnitId,
UnitName = units.Unit,
Lot = s.Lot,
AvgPrice = s.AvgPrice,
Currency = curr.CurrencyName,
CurrencyId = s.Currency,
Note = s.Note,
Purchase = s.Purchase,
Sales = s.Sales,
POIn = s.POIn,
FromFactory = s.FromFactory,
OutDeliveryNo = sales != null ? sales.InvoiceNo : "",
Spec = s.Spec,
DesignCode = s.DesignCode
});

Why can't I select data from my LINQ sub query join?

I am trying to get data from 2 tables using a left join to a nested query. This allows me to get data from Item table but not the cart(nested query) table:
var q = from s in db.Items
join sub in (from c in db.Carts
where c.CartID == 1
group c by c.ItemID into g
select new
{
ItemID = g.Key,
Qty = g.Select(s => s.Qty)
}) on s.ItemID equals sub.ItemID into a
select new ItemViewModel
{
CategoryID = s.CategoryID,
Description = s.Description,
Price = s.Price,
**This being the issue------>>>>>>>** //Qty = a.Select(j => j.Qty),
ItemID = s.ItemID,
ItemName = s.ItemName
};
viewModel = q.ToList();
The query i am trying to acheive is:
select Items.*, Cart.Qty
from Items
left join (select ItemID, Qty from carts where CartID = 1 ) Cart
on Items.ItemID = Cart.ItemID
You can use GroupJoin with SelectMany for LEFT JOIN SQL Query and get the desired output.
var result = db.Items.GroupJoin(db.Carts.Where(x => x.CartID == 1), item => item.ItemID, cart => cart.ItemID,
(item, cart) => new { item, cart })
.SelectMany(x => x.cart.DefaultIfEmpty(), (it, ca) =>
{
return new ItemViewModel
{
ItemName = it.item.ItemName,
Price = it.item.Price,
ItemID = it.item.ItemID,
// ... .... ....
// Fill the required columns from it.Item property..
Qty = ca != null ? ca.Qty : 0
};
}).ToList();
EDIT: The LINQ version with SelectMany.
var result = from s in db.Items
join sub in (from c in db.Carts
where c.CartID == 1
select c)
on s.ItemID equals sub.ItemID into joined
from row in joined.DefaultIfEmpty()
select new ItemViewModel
{
CategoryID = s.CategoryID,
Description = s.Description,
Price = s.Price,
Qty = row != null ? row.Qty : 0,
ItemID = s.ItemID,
ItemName = s.ItemName
};
The C# Fiddle with sample data.
If I'm understanding correctly, and assuming that ItemViewModel.Qty property is just an int, the simplest form of the query you want is:
var q = from item in items
join cart in
(from cart in carts where cart.CartID == 1 select cart)
on item.ItemID equals cart.ItemID into itemCarts
select new ItemViewModel
{
ItemID = item.ItemID,
Qty = itemCarts.Sum(cart => cart.Qty)
};
If you want to only slightly modify/fix your query:
var q = from s in db.Items
join sub in (from c in db.Carts
where c.CartID == 1
group c by c.ItemID into g
select new
{
ItemID = g.Key,
Qty = g.Sum(s => s.Qty)
// or Qty = g.Select(s => s.Qty)
// and below: Qty = a.SelectMany(x => x.Qty).Sum()
})
on s.ItemID equals sub.ItemID into a
select new ItemViewModel
{
CategoryID = s.CategoryID,
Description = s.Description,
Price = s.Price,
Qty = a.Sum(x => x.Qty),
ItemID = s.ItemID,
ItemName = s.ItemName
};

LINQ left join, group by and Count generates wrong result

I'm struggling with linq (left join - group - count). Please help me.
Below is my code and it gives me this result.
Geography 2
Economy 1
Biology 1
I'm expecting this...
Geography 2
Economy 1
Biology 0
How can I fix it?
class Department
{
public int DNO { get; set; }
public string DeptName { get; set; }
}
class Student
{
public string Name { get; set; }
public int DNO { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Department> departments = new List<Department>
{
new Department {DNO=1, DeptName="Geography"},
new Department {DNO=2, DeptName="Economy"},
new Department {DNO=3, DeptName="Biology"}
};
List<Student> students = new List<Student>
{
new Student {Name="Peter", DNO=2},
new Student {Name="Paul", DNO=1},
new Student {Name="Mary", DNO=1},
};
var query = from dp in departments
join st in students on dp.DNO equals st.DNO into gst
from st2 in gst.DefaultIfEmpty()
group st2 by dp.DeptName into g
select new
{
DName = g.Key,
Count = g.Count()
};
foreach (var st in query)
{
Console.WriteLine("{0} \t{1}", st.DName, st.Count);
}
}
}
var query =
from department in departments
join student in students on department.DNO equals student.DNO into gst
select new
{
DepartmentName = department.DeptName,
Count = gst.Count()
};
I don't think any grouping is required for answering your question.
You only want to know 2 things:
- name of department
- number of students per department
By using the 'join' and 'into' you're putting the results of the join in the temp identifier gst. You only have to count the number of results in the gst.
var query = from dp in departments
from st in students.Where(stud => stud.DNO == dp.DNO).DefaultIfEmpty()
group st by dp.DeptName into g
select new
{
DName = g.Key,
Count = g.Count(x => x!=null)
};
You want to group the students by the department name but you want the count to filter out null students. I did change the join syntax slightly although that really does not matter to much.
Here is a working fiddle
Well, see what #Danny said in his answer, it's the best and cleanest fix for this case. By the way, you could also rewrite it to the lambda syntax:
var query = departments.GroupJoin(students,
dp => dp.DNO, st => st.DNO,
(dept,studs) => new
{
DName = dept.DNO,
Count = studs.Count()
});
I find this syntax much more predictable in results, and often, shorter.
BTW: .GroupJoin is effectively a "left join", and .Join is "inner join". Be careful to not mistake one for another.
And my answer is similar to #Igor
var query = from dp in departments
join st in students on dp.DNO equals st.DNO into gst
from st2 in gst.DefaultIfEmpty()
group st2 by dp.DeptName into g
select new
{
DName = g.Key,
Count = g.Count(std => std != null)
};
g.Count(std => std != null) is only one change you should take.

Grouping with 3 tables in Linq

I am very new to LINQ and having great trouble grouping my tables so everything adds up together. I am trying to get this to display all the customers that have purchased more than $50 EVEN IF THEY HAVE MULTIPLE PURCHASES. in the SQL database using linq. Here is what I have so far, please help.
public class TopCustomerVM
{
public string Name { get; set; }
public decimal DollarsSold { get; set; }
public static List<TopCustomerVM> GetResults()
{
ACEEntities db = new ACEEntities();
return (from c in db.Customers
join o in db.Orders on c.CustomerId equals o.CustomerId
join l in db.OrderLines on o.OrderId equals l.OrderId
into x
select new TopCustomerVM {
Name = c.FirstName + c.LastName,
DollarsSold = x.Sum(asdf => asdf.UnitCost * asdf.Quantity)
})
.OrderByDescending(lkj => lkj.DollarsSold)
.Where(lkj => lkj.DollarsSold > 50)
.ToList();
}
}
I believe something like this will give you the desired output:
from record in
(from c in Customers
join o in Orders on c.Id equals o.CustomerId
join ol in OrderLines on o.Id equals ol.OrderId
let customerAndLine = new { Name = c.FirstName + ' ' + c.LastName, DollarsSold = ol.UnitCost * ol.Quantity }
group customerAndLine by customerAndLine.Name into grp
select new { Name = grp.Key, DollarsSold = grp.Sum(r => r.DollarsSold) })
where record.DollarsSold > 50
orderby record.DollarsSold descending
select record

Categories

Resources