Custom Transformer for raw SQL query using NHibernate - c#

I have a table of customerorders that are tied to a table of purchases. Every order can have multiple purchases, it's peculiar but imagine it as if you paid for each item separately at checkout. Every order has a customer name and date, every purchase has a payment type and total on them.
I have a nifty query that provided you know the name of the customer, you can find their most recent unique purchase types.
For example:
Customer A made 3 orders total, 2 via credit card and 1 via cash.
I ask the database "what's the newest unique orders by payment type for A?" Database returns 2 results - the most recent credit card order & the 1 cash order.
This is query:
String sqlQueryStr = $#"SELECT ee.PaymentType, ee.Total
FROM
(
SELECT e.CustomerName, ee.PaymentType, ee.Total, MAX(e.Date) as MaxTimestamp
FROM customerorders ee
INNER JOIN purchases e ON e.Id=ee.OrderId WHERE CustomerName='{customerName}'
GROUP BY 1, 2, 3
) AS sub
INNER JOIN purchases e ON e.Date = sub.MaxTimestamp AND e.CustomerName = sub.CustomerName
INNER JOIN customerorders ee ON ee.OrderId=e.Id AND ee.PaymentType = sub.PaymentType;"
ISQLQuery query = session.CreateSQLQuery(sqlQueryStr);
return query.SetResultTransformer(new AliasToBeanResultTransformer(typeof(Purchase)))
.List<Purchase>();
This works great on its own. The result would be as follows for Customer A, and I have what I want:
'PaymentType: Credit Total:100'
'PaymentType: Cash Total:50'
However, now I want to do something where I don't provide 'customerName'. I want to get everyone's in the same way.
Now if you recall what I said before, Purchase does not have a CustomerName. So I can't just remove the WHERE and use the transformer anymore!
When I remove the 'WHERE' and add an additional SELECT for e.CustomerName, I get this output in MySQL using the query:
'CustomerName: A PaymentType: Credit Total:100'
'CustomerName: A PaymentType: Cash Total:50'
'CustomerName: B PaymentType: Credit Total:20'
'CustomerName: C PaymentType: Credit Total:15'
I was thinking of making a custom transformer, but I'm not sure what kind of transformer will allow me to process this kind of result. It's now not not just a Purchase, it's got the Name also. And I would probably want them grouped together right (not on different rows)?

public sealed class CustomTransformer: IResultTransformer
{
public IList TransformList(IList collection)
{
return collection;
}
public object TransformTuple(object[] tuple, string[] aliases)
{
return new UniqueCustomerPurchase()
{
CustomerName = (string)tuple[0],
PaymentType = (string)tuple[1],
Total = (uint)tuple[2]
};
}
}
This is what I have at the moment. This seems to work well, however, I wish there was a way to group the CustomerName to a list of Purchases(paymentType & total) instead of having to do this. I end up having to iterate over the collection a second time to group them like so:
ISQLQuery query = session.CreateSQLQuery(sqlQueryStr);
return query.SetResultTransformer(new CustomTransformer())
.List<UniqueCustomerPurchase>()
.GroupBy(cp => cp.CustomerName)
.Select(g => new KeyValuePair(g.Key, g.Select(cp => cp));

Related

Linq Find the most recurring record

I have a rent a car project.
This is the table where I keep the rented vehicles
I want to find the most rented vehicle in this table. How can I do this?
So I want to find the most mentioned car Id in the table
[I have to do it with context architecture. How can I do this with c # linq?]2
I want to find the most rented vehicle in this table.
So basically you want to find out how many times car 5 is rented, and how many times car 6 is rented, etc, and keep the carId with the highest rental count.
As you are working with a database, my advice would be to use GroupBy to make groups of rentals that have the same CarId, and then keep the group that has the most number of rentals = order by descending count and take the first.
We'll use the overload of Queryable.GroupBy that has a parameter resultSelector, so we can select the number of rentals in the group as result.
// make groups of CarRentals that have the same CarId:
var mostRentedCarId = dbContext.CarRentals.GroupBy(carRental => carRental.CarId,
// parameter resultSelector: for every CarId and all CarRentals that have this CarId,
// make one new object
(carId, carRentalsWithThisCarId) => new
{
CarId = carId,
RentalCount = carRentalsWithThisCarId.Count(),
})
// order the groupBy result by descending rentalCount and take the first or default:
.OrderByDescending(groupByResult => groupByResult.RentalCount)
.FirstOrDefault();
if you mean that you want to find the sql query string for that question u can use the count() to count how many times the id of a specific car is repeated. And since you want the most rented car you can use somthing like this:select top 1 count(CarId) from tableName group by CarId order by count(CarId) desc
this should work for you.
You could write a query like the below, or use the approach as suggested by Abdelghafour Lahrache.
SELECT MAX(CarCount)
FROM
(
SELECT COUNT(CarId) AS CarCount
FROM YourTableName
GROUP BY CarId
) CarIdCounter

LINQ statement to list orders with outstanding items to be shipped

I am having a problem creating a LINQ statement to select orders where there are items remaining to be shipped.
Simplified model as follows:
SalesOrder
SalesOrderId (PK)
SalesOrderItem
SalesOrderItemId (PK)
Description (string)
SalesOrderId (FK)
ProductId(FK)
Qty (int)
Product
ProductId (PK)
Description
Virtual (Bit)
Shipment
ShipmentId (PK)
ShipmentItem
ShipmentItemId (PK)
SalesOrderItemId (FK)
Qty (int)
I need a LINQ statement which generates a list of sales orders where there is an outstanding qty of items to be shipped (taking into account the fact that virtual items do not need to be shipped).
A Sales Order can have multiple shipments related to it. Each shipment item relates to a sales order item.
The Qty of the Shipment Item relates to the qty of the Sales Order Item. It can be for all of the Sales Order Item or only part.
A shipped order is one where all Order Items are shipped (having total qty ordered = total qty shipped). Outstanding Sales Orders still have an outstanding qty left to ship.
I have no idea where to start comparing a sum of a qty from one table to another in a clear, concise and economical LINQ statement.
This is the SQL which achieves my goal:
SELECT a.* FROM
(
SELECT
o.SalesOrderId,
SUM(i.Qty) AS QtyOrdered,
SUM(ISNULL(s.Qty,0)) AS QtyShipped
FROM SalesOrder o
INNER JOIN SalesOrderItem i ON i.SalesOrderId = o.SalesOrderId
INNER JOIN Product p ON p.ProductId = i.ProductId
LEFT JOIN ShipmentItem s ON s.SalesOrderItemId = i.SalesOrderItemId
WHERE p.Virtual = 0
GROUP BY o.SalesOrderId) a
WHERE QtyOrdered > QtyShipped
I have also tried the following EF statement:
var salesOrders = await _context.SalesOrder.Where(p => p.SalesOrderItems.Sum(x => x.Qty) > p.SalesOrderItems.Sum(i => i.ShipmentItems.Sum(f => f.Qty))).ToListAsync();
This results in the error:
SqlException: Cannot perform an aggregate function on an expression containing an aggregate or a subquery. Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
Thank you
What the exception is telling you is that EF cannot translate the linq '.Sum(x => x.Qty) ' to SQL.
In this case, you have to make a decision, either
there is not and will never be that much data and you bring the entire related table entities back and compute it in your .NET runtime - doing it locally in multiple steps
there is too much data to bring back and you are better off writing a SPROC in SQL Server that does the computation for you and bringing only the results that back to the server where your .NET is running and you are left to do only the comparison in your .NET runtime.

Get the id of the latest record in group by with linq

I'm trying to get the latest record for a group in linq but I want the id, not the date as sometimes the dates can be exactly the same as other records.
The following code gives me the key and the last date
var latestPositions = from bs in Scan
group bs by bs.Asset into op
select new
{
Asset = op.Key,
LastSeen = op.Max(x => x.LastSeen)
};
Returns something like this...
Asset LastSeen
BS1 2020-05-10
BS2 2020-07-10
Which is what I need, but I then need to get to the rest of the data from that table row, but if I join it on the two columns I can get multiple rows, is there a way for me to return the id column in the group by, so I can join on that?
Thanks
GroupBy cannot help here because of SQL limitation. But you can write workaround
var latestPositions = from bs in Scan
where !Scan.Any(s => s.Asset == bs.Asset && s.LastSeen > bs.LastSeen)
select bs;
But I have to mention that fastest way is using window functions which are not available in EF Core:
SELET
sc.Id
FROM (
SELECT
s.Id,
ROW_NUMBER() OVER (PARTITION BY s.Asset ORDER BY s.LastSeen DESC) AS RN
FROM Scan s
) sc
WHERE sc.RN == 1
But there is exists EF Core extension linq2db.EntityFrameworkCore which makes it possible via LINQ (I assume that Asset is just ID, not a navigation property)
var queryRn = from bs in Scan
select new
{
Entity = bs,
RN = Sql.Ext.RowNumber().Over()
.PartitionBy(bs.Asset).OrderByDesc(bs.LastSeen).ToValue()
};
// switch to alternative translator
queryRn = queryRn.ToLinqToDB();
var latestPositions = from q in queryRn
where q.RN == 1
select q.Entity;
I think I did what you wanted above and I wrote the full code on this link
If it's not what you want, can you write what you want a little more clearly.
var temp = from l in list
group l by l.Asset into lg
orderby lg.Key
select new { Asset = lg.Key, LastSeen = lg.Max(x => x.LastSeen), ID = lg.Where(x => x.LastSeen == lg.Max(y => y.LastSeen)).Single().ID };
So every Scan has a property Asset, a DateTime property LastSeen, and zero or more other properties.
Requirement: given a sequence of Scans, give me per Asset (all or some of the ) properties of the LastSeen Scan.
The following will do the trick:
var lastSeenScan = dbContext.Scans.GroupBy(scan => scan.Asset,
// parameter resultSelector: take every Asset, and all Scans that have this Asset value
// and order these Scans by descending value of lastSeen:
(asset, scansWithThisAssetValue) => scansWithThisAssetValue
.OrderByDescending(scan => scan.LastSeen)
// Select the scan properties that you plan to use.
// Not needed if you want the complete Scan
.Select(scan => new
{
Id = scan.Id,
Operator = scan.Operator,
...
})
// and keep only the First one, which is the Last seen one:
.FirstOrDefault();
In words: divide your table of of Scans into groups of scans that have the same value for property Asset. Order all scans in each group by descending value of LastSeen. This will make the Scan that has last been seen the first one.
From all scans in the group select only the properties that you plan to use, and take the first one.
Result: for every used Asset, you get the (selected properties of the) scan that has the highest value of LastSeen.

How do you find the group-wise max in LINQ?

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

Linq to Sql - Populate JOIN result into a List

I am not sure if this can be done, but here's the scenario.
I want to turn this sql into linq:
SELECT * FROM Department d
INNER JOIN Employee e ON e.DepartmentID = d.DepartmentID
Department - Employee is 1 to many relationship.
I have created a custom object that I would like to populate the result into.
public class DepartmentSummary
{
public Department Department { get; set; }
public List<Employee> Employees {get; set;}
}
The Linq I came up with is
var result = from d in dba.Department
join e in dba.Employee d.DepartmentID equals e.DepartmentID into j1
select new DepartmentSummary
{
Department = d,
Employees = j1.ToList()
};
I tried it out and it's not working. Can anyone shed some light for me please? I would like to perform an inner join between Department and Employee. For each Department in the resultset, I would like to create one DepartmentSummary object which holds that department and a list of employees belonging to that department.
Does Linq provides an ad hoc solution for this or must I iterates through the result set and create a list of DepartmentSummary manually?
Thanks,
EDIT:
Looks like this works for me
var result = from d in dba.Department
join e in dba.Employee d.DepartmentID equals e.DepartmentID into j1
where j1.Count() > 0
select new DepartmentSummary
{
Department = d,
Employees = j1.ToList()
};
The thing is that you're not really taking one SQL and trying to create a Linq-query out of it.
If you were, you'd notice that your SQL query does not really produce one row per department, but it will repeat the department information for each employee in that department.
Now, an initial naive look would suggest you use a group-by clause, since that would allow you to split the data into individual groupings for each department, but groupings in SQL does not really give you a key+all-matching-rows type of result, rather it allows you to do aggregate calculations, like "for each department, how many employees do I have".
So, in order to do what you want, you need to basically do a normal join, which will give you each employee, coupled with the appropriate department information (ie. each employee will be linked to his/her department), and then you need to construct the rest of the data structure yourself.
Now, having said that, if you have the proper relationships set in your data context related classes, each department should already have some kind of property that contains all employees in that department, so perhaps the simple query is just "give me all departments", and then you can, for each department, retrieve the employees?
Of course, doing that would likely execute one SQL for each department, but in this case, you're back to "give me all employees with their department information" and you have to build code to handle the rest.
LINQ to SQL doesn't understand your ToList() call, but you might be able to select the sequence of joined elements and then use LINQ to Objects (via AsEnumerable()) to map to your DepartmentSummary object:
var qResult = from d in dba.Department
join e in dba.Employee d.DepartmentID equals e.DepartmentID into j1
select new
{
Department = d,
Employees = j1
};
var result = from d in qResult.AsEnumerable()
select new DepartmentSummary()
{
Department = d.Department,
Employees = e.Employees.ToList()
};
Sounds like you're looking to get around lazy loading?
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Department>(d => d.Employees);
using (var dba = new MyDataContext())
{
dba.LoadOptions = dlo;
var result = from d in dba.Department
select d;
}
Now, if you don't have a relationship defined between Department and Employees (the Linq2Sql designer will do this for you if you have database relationships setup) then you should look into doing that. It makes it all dramatically easier. In fact, you don't even need your campaign summary.
This problem is due to the nature of the query. When you join Department to Employee, you'll get back one record for every Employee. This means that your ToList() statement is expecting multiple employees per department, but due to the join, always getting one.
Change your query to
var result =
from d in dba.Department
select new tCampaignSummary
{
Department = d,
Employees = dba.Employee.Where(e => e.DepartmentID ==
d.DepartmentID).ToList()
};
I've tested this and it works.
What it does differently is selects only one record per Department (not per employee) then it gets the zero to many corresponding employees for each dept and converts them to a list.
Good luck!
EDIT
As requested, here is the generated SQL:
SELECT [t0].*, [t1].*
(
SELECT COUNT(*)
FROM [dbo].[Employee] AS [t2]
WHERE [t2].[DepartmentID] = [t0].[DepartmentID]
) AS [value]
FROM [dbo].[Department] AS [t0]
LEFT OUTER JOIN [dbo].[Employee] AS [t1]
ON [t1].[DepartmentID] = [t0].[DepartmentID]
ORDER BY [t0].[DepartmentID], [t1].[IndexID]
The only modification is that LINQ will not do [t0].*, instead it will enumerate each field. Since I had to guess at the fields, I left them out to make the SQL clearer.

Categories