I have a table in which I want to calculate the rank of my data based on Amount.
var result = await context.Table
.Select(i => new Table {
Rank = // want to calculate the rank based on amount
}).OrderByDescending(i => i.Amount)
.ToListAsync();
I tried using the element index but for that, I need to fetch all the data to the client first.
There's no direct implementation for RANK() in EF. You are better off with using a custom LINQ query to do what you want, like,
var query = from t in context.Table
orderby t.Amount descending
select new
{
Rank = (from o in context.Table
where o.Amount > s.Amount
select o).Count() + 1
};
If you know the SQL query you want to execute, you can create a custom function or stored procedure and then execute it using Entity Framework. Please refer this.
Related
How would you implement pagination when the input data needs to be grouped first? I understand how to implement pagination from the link below:
LINQ and pagination
, but I would like to be able to do this where each item in the paginated list is a group (that can be expanded) from the input data. Something similar to the code below - to prevent retrieving all rows of the table into memory, ordersList is IQueryable. The IQueryable returned is what I would like to pass into the pagination function.
from order in ordersList
group order by order.FullName into customers
select customers
However, a query like this runs on the client (and actually throws an exception in Entity Framework Core 3.0+). Is there a way to only retrieve the items on the current page for this situation?
You have to retrieve limited data and then group on the client side:
var keys = ordersList
.Select(o => new {o.FullName})
.Distinct()
.OrderBy(ะพ => o.FullName)
.Skip(pageNumber * pageSize)
.Take(pageSize);
var items =
from order in ordersList
join key in keys on order.FullName equals key.FullName
select order;
var result =
from order in items.AsEnumerable()
group order by order.FullName into customers
select customers;
You must paginate by group. You should use the group number instead of the page number.
//group sequence
int groupSeq = 1;
//select current group
var p = (from order in context.TBLGroups
group order by order.FullName into customers
select customers.Key).OrderBy(a => a).Skip(groupSeq - 1).Take(1).FirstOrDefault();
string FullName = p.ToString();
//get all items in current group
var items = (from order in context.TBLGroups
where order.FullName == FullName
select order).ToList();
I have tow tables - OrderRequisition and Order. I can show all the records from OrderRequisition table using linq query:
var list = (from r in db.OrderRequisition
select new SalesOrderViewModel
{
OrderId = r.OrderId ,
OrderNo = r.OrderNo
}).ToList();
I want to show only those records from OrderRequisition table which are not included in Order table. Any clue
Thanks
Partha
A simple approach that might be efficient enough because your database is able to optimize it:
var list = db.OrderRequisition
.Where(or => !db.Order.Any(o => o.OrderId == or.OrderId))
.ToList();
(skipped the SalesOrderViewModel initialization because not relevant for the question)
I am facing a performance issue when doing a LINQ subquery in C#.
The query is as follows:
var _result = _db.Prices
.Join(_db.Vendors,
e => e.VendorId,
v => v.VendorId,
(e, v) => new {
TotalPrice = e.TotalPrice,
Vendor = v.Title,
AmountPaid = _db.Amounts
.Where(p => p.PrId == e.PrId )
.GroupBy(p => p.PrId )
.Select(grp => (decimal?)grp.Sum(k => k.AmountPaid) ?? 0M)
.FirstOrDefault()
});
Prices, Vendors and Amounts are Entity Framework objects.
Firstly I am doing a join of Prices to Vendors table on VendorId and on the result I am adding a calculated field AmountPaid. The result is a subquery which returns the sum of AmountPaid column in Amounts table for the given record matched by PrId index.
All the above is returned as a JSON object in ASP.NET web api.
The result of the above for the records in a database is only 1992 rows.
The above command neeeds about 20 seconds to return the result. If I remove the AmountPaid calculation it needs only 1 second.
If I run the equivalent SQL command in SQL Server studio it needs only half a second with the AmountPaid calculation included:
SELECT Prices.*, Vendors.Title,
AmountPaid = (SELECT SUM(AmountPaid) from Amounts where Amounts.PrId = Prices.PrId GROUP BY PrId) FROM Amounts
INNER JOIN Vendors ON Amounts.VendorId = Vendors.VendorId
Is there a way to fix this?
I cannot understand the issue since the SQL command does not have an issue in execution.
Is there an error on the LINQ syntax, or another way to get in LINQ the same result set?
I tried the Internet and the SOF but couldn't locate a helpful resource. Perhaps I may not be using correct wording to search. If there are any previous questions I have missed due to this reason please let me know and I will take this question down.
I am dealing with a busy database so I am required to send less queries to the database.
If I access different columns of the same Linq query from different levels of the code then is Entity Framework smart enough to foresee the required columns and bring them all or does it call the db twice?
eg.
var query = from t1 in table_1
join t2 in table_2 on t1.col1 equals t2.col1
where t1.EmployeeId == EmployeeId
group new { t1, t2 } by t1.col2 into grouped
orderby grouped.Count() descending
select new { Column1 = grouped.Key, Column2 = grouped.Sum(g=>g.t2.col4) };
var records = query.Take(10);
// point x
var x = records.Select(a => a.Column1).ToArray();
var y = records.Select(a => a.Column2).ToArray();
Does EF generate query the database twice to faciliate x and y (send a query first to get Column1, and then send another to get Column2) or is it smart enough to know it needs both Columns to be materialised and bring them both at point x?
Added to clarify the intention of the question:
I understand I can simply add a greedy method to the end of query.Take(10) and get it done but I am trying to understand if the approach I try (and in my opinion, more elegant) does work of if not what makes EF to make two queries please.
Yes currently your code will generate 2 queries that will be executed to the database. Reason being is because you have 2 different sqls generated:
First is the top query, taking only 10 records and then only Column1
Second is the top query, taking only 10 records and then only Column2
The reason these are 2 queries is because you have a ToArray over different Select statements -> generating different sql. Most of linq queries are differed executed and will be executed only when you use something like ToArray()/ToList()/FirstOrDefault() and so on - those that actually give you the concrete data. In your original query you have 2 different ToArray on data that has not yet been retrieved - meaning 2 queries (once for the first field and then for the second).
The following code will result in a single query to the database
var records = (from t1 in table_1
join t2 in table_2 on t1.col1 equals t2.col1
where t1.EmployeeId == EmployeeId
group new { t1, t2 } by t1.col2 into grouped
orderby grouped.Count() descending
select new { Column1 = grouped.Key, Column2 = grouped.Sum(g=>g.t2.col4) })
.Take(10).ToList();
var x = records.Select(a => a.Column1).ToArray();
var y = records.Select(a => a.Column2).ToArray();
In my solution above I added a ToList() after filtering out only that data you need (Take(10)) and then at that point it will execute to the database. Then you have all the data in memory and you can do any other linq operation over it without it going again to the database.
Add to your code ToString() so you can check the generated sql at different points. Then you will understand when and what is being executed:
var query = from t1 in table_1
join t2 in table_2 on t1.col1 equals t2.col1
where t1.EmployeeId == EmployeeId
group new { t1, t2 } by t1.col2 into grouped
orderby grouped.Count() descending
select new { Column1 = grouped.Key, Column2 = grouped.Sum(g=>g.t2.col4) };
var generatedSql = query.ToString(); // Here you will see a query that brings all records
var records = query.Take(10);
generatedSql = query.ToString(); // Here you will see it taking only 10 records
// point x
var xQuery = records.Select(a => a.Column1);
generatedSql = xQuery.ToString(); // Here you will see only 1 column in query
// Still nothing has been executed to DB at this point
var x = xQuery.ToArray(); // And that is what will be executed here
// Now you are before second execution
var yQuery = records.Select(a => a.Column2);
generatedSql = yQuery.ToString(); // Here you will see only the second column in query
// Finally, second execution, now with the other column
var y = yQuery.ToArray();
When you are running linq statement on an entity in EF if only prepares the Select statement (thats why the type is IQueryable). The data is loaded lazily. When you try to use a value from that query then only the result gets evaluated using a enumerator.
So when you turn it to a collection (.toList() etc.) explicitly it tries to get data to populate the list and hence the sql command is fired.
It is designed so to enhance the performance. So if a particular property of an entity is to be used EF doesn't get the value for all the columns from that table
I'm trying to solve the "group-wise max" problem in LINQ. To start, I have a database modeled using the Entity Framework with the following structure:
Customer:
---------
CustomerID : Int32
Name : String
Order:
-------
OrderID : Int32
CustomerID : Int32
Total : Decimal
This gives me navigation from a Customer to her orders and an Order to the owner.
I'm trying to create a LINQ query that allows me to find the top-10 customer orders in the database. The simple case was pretty easy to come up with:
var q = (
from order in _data.Orders // ObjectQuery<Order>
orderby order.Amount descending select order
).Take(10);
However, I'd like to only show unique customers in this list. I'm still a bit new to LINQ, but this is what I've come up with:
var q = (
from order in _data.Orders // ObjectQuery<Order>
group order by order.Customer into o
select new {
Name = o.Key.Name,
Amount = o.FirstOrDefault().Amount
}
).OrderByDescending(o => o.Amount).Take(10);
This seems to work, but I'm not sure if this is the best approach. Specifically, I wonder about the performance of such a query against a very large database. Also, using the FirstOrDefault method from the group query looks a little strange...
Can anyone provide a better approach, or some assurance that this is the right one?
You could do:
var q = (
from order in _data.Orders // ObjectQuery<Order>
orderby order.Amount descending select order
).Distinct().Take(10);
I would normally look at the generated SQL, and see what is the best.
Customer
.Select(c=>new {Order= c.Orders.OrderByDescending(o=>o.Total).First()})
.OrderByDescending(o=>o.Total)
.Take(10);