FIFO inventory systems - Converting T-SQL to Linq - sql-server

Please how do I convert this T-SQL statement to Linq or lambda? Trying to implement FIFO inventory systems
DECLARE #TakenQty int;
SET #TakenQty = 90;
WITH cte AS
(
SELECT *, SUM(qty) OVER (ORDER BY accept_date, id ASC) AS CumQty
FROM RS_GIN_Master
WHERE qty > 0
)
SELECT TOP ((SELECT COUNT(*) FROM cte WHERE CumQty <#TakenQty)+1)
batch_no, accept_date,
CASE
WHEN CumQty < #TakenQty THEN qty
ELSE #TakenQty - (CumQty - Qty)
END AS TakenOut
FROM
cte
Table definition
The final result is like this

Please how do I convert this T-SQL statement to Linq or lambda?
You don't.
LINQ and SQL share many common query operators, but LINQ has nothing equivalent to
SUM(qty) OVER (ORDER BY accept_date, id ASC) RANGE BETWEEN UNBOUNDED PRECEEDING AND CURRENT ROW
Which is what that expression is shorthand for. And it certainly has no way to write an expression that EF could translate to this TSQL.
So you leave it in TSQL. And if you must have an implementation outside of SQL Server, you start from scratch.

I was able to resolve this
void Main()
{
var data = new List<History>()
{
new History(1,1,20,DateTime.Now.AddDays(-24),"001"),
new History(2,1,2,DateTime.Now.AddDays(-23),"002"),
new History(3,2,2,DateTime.Now.AddDays(-24),"001"),
new History(3,1,29,DateTime.Now.AddDays(-22),"003"),
new History(3,1,50,DateTime.Now.AddDays(-21),"004"),
};
var demo = Results(data, 30);
demo.Dump(); //note using LinqPad
}
public class History
{
public History(int id, int stockId, int qty, DateTime date, string batchNumber)
{
Id = id;
StockId = stockId;
Qty = qty;
Date = date;
BatchNumber = batchNumber;
}
public int Id { get; set; }
public int StockId { get; set; }
public int Qty { get; set; }
public string BatchNumber { get; set; }
public DateTime Date { get; set; }
}
public static List<Result> Results(List<History> data, int takenQty)
{
var runningTotal = 0;
var result = data.Where(p => p.StockId == 1).OrderBy(p => p.Date).ThenBy(p => p.Id)
.Select(x => new
{
x.Id,
x.Date,
x.BatchNumber,
x.Qty,
x.StockId,
CumQty = (runningTotal = runningTotal + x.Qty)
}).ToList();
var query = result.Select(x => new Result
{
StockId =x.StockId,
Id = x.Id,
BatchNumber = x.BatchNumber,
Qty = x.Qty,
Used = x.CumQty < takenQty ? x.Qty : takenQty - (x.CumQty - x.Qty)
}).Take((result.Count(p => p.CumQty < takenQty)) + 1).ToList();
return query;
}
public class Result
{
public int Id { get; set; }
public int StockId { get; set; }
public int Qty { get; set; }
public string BatchNumber { get; set; }
public int Used { get; set; }
public int Left => Qty - Used;
}
And the final output

Related

Dapper multi object query One-Many

Not really sure why I'm not getting the child object populated.
My tables:
Product:
[ProductId]
,[Brand]
,[Model]
StoreProduct:
[StoreId]
,[ProductId]
,[StoreProductId]
Class
public class Product
{
public Guid ProductId { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
public virtual List<StoreProduct> StoreProducts { get; set; }
}
public class StoreProduct
{
public int StoreId { get; set; } //Key 0
public Guid ProductId { get; set; } //Key 1
public Store Store { get; set; }
public Product Product { get; set; }
public string StoreProductId { get; set; } //A new Id specific for each store
}
My Dapper Code
string sql = "SELECT * FROM StoreProduct AS A INNER JOIN Product AS B ON A.ProductId = B.ProductId WHERE A.StoreProductId = #StoreProductId and A.StoreId = #StoreId";
var connection = AppDbContext.Database.GetDbConnection();
return connection.Query<StoreProduct, Product, Product>(
sql,
(StoreProduct, Product) => { StoreProduct.ProductId = Product.ProductId; return Product; },
new { StoreProductId = storeProductId, StoreId = StoreID }, splitOn: "ProductId")
.FirstOrDefault();
What the DB returns:
But... StoreProducts List is null.
Use Dapper the way it works.
var listProduct = new Dictionary<string, Product>();
var listStoreProduct = new Dictionary<string, StoreProduct>();
using var connection = _connectionProvider.GetConnection();
var query = "SELECT * FROM StoreProduct AS A INNER JOIN Product AS B ON A.ProductId = B.ProductId WHERE A.StoreProductId = #StoreProductId and A.StoreId = #StoreId";
var result = connection.Query<Product, StoreProduct, Product>(query, (product, storeProduct) =>
{
if (!listProduct.TryGetValue(product.Id, out var entry))
{
entry = product;
listProduct.Add(entry.Id, entry);
}
if (storeProduct != null && !listStoreProduct.TryGetValue(storeProduct.Id, out var procInstance))
{
listStoreProduct.Add(procInstance.Id, procInstance);
entry.ProcessInstance.Add(procInstance);
}
return entry;
}, splitOn: "ProductId").Distinct().ToList();
I hope I could have helped you.

EF6 does not recognize Func lambda as filter for Where

I have the following entities:
[Table("Customer", Schema = "dbo")]
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
[Table("Payment", Schema = "dbo")]
public class Payment
{
public int PaymentId { get; set; }
public int CustomerId { get; set; }
public DateTime Period { get; set; }
public int Price { get; set; }
[ForeignKey("CustomerId")]
public Customer Customer { get; set; }
}
Now I want to filter Payment table by Period and Price. Each of predicate must be in its own Where method. So, I get the following:
int price = 200;
var period = new DateTime(2020, 10, 3);
using var db = new TestContext();
// Option 1: anonymous lambda
var payments1 = db.Payments
.Where(p => p.Period < period)
.Where(p => p.Price <= price);
foreach (var payment in payments1)
{
listBox.Items.Add(
$"Payment: Payment Id={payment.PaymentId}, " +
$"Customer Id`={payment.CustomerId}, " +
$"Period={payment.Period.ToShortDateString()}, " +
$"Price={payment.Price}");
}
EF6 generates correct SQL:
exec sp_executesql N'SELECT
[Extent1].[PaymentId] AS [PaymentId],
[Extent1].[CustomerId] AS [CustomerId],
[Extent1].[Period] AS [Period],
[Extent1].[Price] AS [Price]
FROM [dbo].[Payment] AS [Extent1]
WHERE ([Extent1].[Period] < #p__linq__0) AND ([Extent1].[Price] <= #p__linq__1)',
N'#p__linq__0 datetime2(7),#p__linq__1 int',
#p__linq__0='2020-10-03 00:00:00',
#p__linq__1=200
However, if I use Func lambda with the same condition:
// Option 2: Func<T, T> lambda
Func<Payment, bool> func = p => p.Period < period;
var payments2 = db.Payments.Where(func).Where(p => p.Price <= price);
I don't get the same SQL, but get this one:
SELECT
[Extent1].[PaymentId] AS [PaymentId],
[Extent1].[CustomerId] AS [CustomerId],
[Extent1].[Period] AS [Period],
[Extent1].[Price] AS [Price]
FROM [dbo].[Payment] AS [Extent1]
As far as I understand, EF switched to client-side evaluation. I wonder, why this happened? I'm using same lambda for filtering!
You need to use an Expression, not just a Func so EF can work out the names of the properties etc.
Try:
Expression<Func<Payment, bool>> func = p => p.Period < period;

Multi-mapping to the same table twice

How do you use Dapper's Multi-Mapping feature on two fields using the same table? i.e ClientInfo has two Address objects.
public class ClientInfo
{
public Guid Id => Guid.NewGuid();
public string FirstName { get; set; }
public string LastName { get; set; }
public Address PostalAddress { get; set; }
public Address BillingAddress { get; set; }
public int ContactNumber { get; set; }
}
public class Address
{
public Guid Id = Guid.NewGuid();
public string FirstLine { get; set; }
public string SecondLine { get; set; }
public string Town { get; set; }
public string PostCode { get; set; }
}
Tables
Relational - Address.Id used in ClientInfo.PostalAddress / BillingAddress
tbl.Address
|Id|FirstLine|SecondLine|Town|PostCode
tbl.ClientInfo
|Id|FirstName|LastName|PostalAddress|BillingAddress|etc..
Current implementation
Results only in all but PostalAddress being mapped.
var sql = #"select * from ClientInfo c left join Address as a on a.Id = c.PostalAddress left join Address as ad on ad.Id = c.BillingAddress";
var clients = connection.Query<ClientInfo, Address, Address, ClientInfo>(
sql,
(client, postal, billing) =>
{
client.PostalAddress = postal;
client.BillingAddress = billing;
return client;
},
splitOn: "PostalAddress,BillingAddress")
.Distinct()
.ToList();
return clients;
The splitOn parameter tells Dapper when/where to start mapping the next object, so you need to ensure that your SQL query returns the information in the correct order. Right now, you return 2 guids for PostalAddress and BillingAddress. Dapper doesn't know how to map them both.
select * from ... join ... will result in the Address data ordered AFTER the ClientInfo.PostalAddress and ClientInfo.BillingAddress columns.
Try: SELECT c.Id, c.FirstName, c.LastName, c.ContactNumber, a.*, ad.* FROM ClientInfo c LEFT JOIN Address AS a ON a.Id = c.PostalAddress JOIN Address AS ad ON ad.Id = c.BillingAddress
As you can see, removing the * effectively excludes the PostalAddress and BillingAddress guids from the results and we can now now splitOn: "Id,Id".
You will of course not have to provide the GUIDs in the select statement, this is just for the test to work.
[Test]
public void TestAbcd()
{
using (var dbConnection = new SqlConnection(_connectionString))
{
const string sql = #"WITH ClientInfo AS (
SELECT * FROM (
VALUES (#ci1, #adr1, #adr2), (#ci1, #adr3, #adr4)
) AS a (Id, PostalAddress, BillingAddress)
),
Address AS (
SELECT * FROM (
VALUES
(#adr1), (#adr2), (#adr3), (#adr4)
) AS a (Id)
)
select * from ClientInfo c left join Address as a on a.Id = c.PostalAddress left join Address as ad on ad.Id = c.BillingAddress";
dbConnection.Open();
var clients = dbConnection.Query<ClientInfo, Address, Address, ClientInfo>(
sql,
(client, postal, billing) =>
{
client.PostalAddress = postal;
client.BillingAddress = billing;
return client;
},
splitOn: "PostalAddress,BillingAddress", param: new {
ci1 = Guid.NewGuid(),
ci2 = Guid.NewGuid(),
adr1 = Guid.NewGuid(),
adr2 = Guid.NewGuid(),
adr3 = Guid.NewGuid(),
adr4 = Guid.NewGuid()
})
.Distinct()
.ToList();
}
}

Dapper Multi Mapping Result

I have two classes
public class Customer
{
public int CustomerId { get; set;}
public string CustomerName { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public int CustomerId { get; set; } //BuyerCustomer
public int CustomerSecondId { get; set; } //ReceiverCustomer
public Customer BuyerCustomer { get; set; }
public Customer ReceiverCustomer { get; set; }
}
Here's my query will look like
SELECT a.*, b.*, c.* FROM dbo.PRODUCTS_ORDER a
INNER JOIN dbo.CUSTOMER b ON a.CustomerId=b.CustomerId
INNER JOIN dbo.CUSTOMER c ON a.CustomerSecondId=b.CustomerId
Dapper Implementation..
List<Order> order= null;
order= (List<Order>)dapperconnection.Query<Order, Customer, Customer, Order>(sql,
(order, customer1,customer2) =>
{
order.BuyerCustomer = customer1;
order.ReceiverCustomer = customer2;
return order;
}, splitOn: "CustomerId,CustomerSecondId ");
The result I'm getting is incomplete, only the RecevierCustomer gets populated while the BuyerCustomer doesn't contain any values at all.
It looks like dapper is confused since i used the CustomerId twice in my query.
Is there any workaround with this without having to change my the Customer class?
There are few issues with your class design and Dapper query.
Customer.CustomerName should be string
I would remove CustomerId and CustomerSecondId from Order. They are redundant. You have both Id's in the Customer.
Remove CustomerSecondId from Split.
Below is a working test:
public class Order
{
public int OrderId { get; set; }
public Customer BuyerCustomer { get; set; }
public Customer ReceiverCustomer { get; set; }
}
public class Customer
{
public int CustomerId { get; set; }
public string CustomerName { get; set; }
}
[Test]
public void TestSplitMany()
{
using (var conn = new SqlConnection(#"Data Source=.\sqlexpress;Integrated Security=true; Initial Catalog=foo"))
{
var result =
conn.Query<Order, Customer, Customer, Order>(#"select OrderId = 1, CustomerId = 2, CustomerName = 'Jim', CustomerId = 3, CustomerName = 'Bob'",
(order, buyer, receiver) =>
{
order.BuyerCustomer = buyer;
order.ReceiverCustomer = receiver;
return order;
}, splitOn: "CustomerId").First();
Assert.That(result.BuyerCustomer.CustomerId == 2);
Assert.That(result.ReceiverCustomer.CustomerId == 3);
Assert.That(result.BuyerCustomer.CustomerName == "Jim");
Assert.That(result.ReceiverCustomer.CustomerName == "Bob");
}
}

Dapper one to many losing Id in the many

Given the following two entities
public class Customer
{
public Customer()
{
Orders = new List<Orders>();
}
public int CustomerId { get; set; }
public string CustomerName { get; set; }
public List<Orders> Orders { get; set; }
}
and
public class Orders
{
public int OrderId { get; set; }
public int CustomerId { get; set; }
public int Quantity { get; set; }
public string OrderDescription { get; set; }
}
And the following code
string sql = #"
select c.CustomerId, c.CustomerName, o.OrderId as OrderId, o.CustomerId, o.Quantity, o.OrderDescription
from Customer c
join Orders o on c.CustomerId = o.CustomerId";
var customers = new List<Customer>();
Customer currentCustomer = null;
var c = connection.Query<Customer, Orders, Customer>(
sql, (customer, order) => {
if (currentCustomer == null || currentCustomer.CustomerId != customer.CustomerId)
{
customers.Add(customer);
currentCustomer = customer;
}
currentCustomer.Orders.Add(order);
return currentCustomer;
}, splitOn: "CustomerId, OrderId");
When I inspect customers the OrderId is always 0. As in customers.Orders[0].OrderId is zero, for all of them. Now I suppose it is me doing something wrong, but I can't figure out what. The strange SQL you see above is my attempts to try and force the OrderId to work.

Resources