Dapper and DateTime in WHERE clause causes multi-mapped nulls - sql-server

I finally was able to construct a multi-mapped query and return meaningful data. This query returned a list of a custom object which itself is composed of some other objects. But this query worked with a single parameter.
When I modified this query by adding a second parameter, a DateTime, two of the aggregated objects (Part and Color) were null. However, when I captured the SQL in the profiler and ran it in SQL Server, all of the data was there!
How can I better deal with a DateTime parameter in the where clause? It is obviously this which is causing the problem.
The code that follows works with the commented-out where clause but not the existing one.
public IList<PartReceipt> GetReceiptHistory(ListItem supplier, DateTime dateReceived)
{
const string sql =
#"SELECT r.id, r.Quantity, r.UnitCost, r.DateReceived,
s.Id , s.Description,
p.Id, p.Number, p.Description, p.StockClass,
mp.Id , mp.Number, mp.ManufacturerId, mp.Description,
m.Id , m.Description, m.ShortDescription,
c.Id, c.Description,
pc.Id, pc.Name, pc.Description
FROM PartReceipt r
INNER JOIN Supplier s ON r.SupplierId = s.Id
INNER JOIN Part p on r.PartId = p.Id
INNER JOIN ManufacturerPart mp ON p.ManufacturerPartId=mp.Id
INNER JOIN Manufacturer m ON mp.ManufacturerId = m.Id
LEFT JOIN Color c ON p.ColorId=c.Id
LEFT JOIN ProductCategory pc ON p.ProductCategoryId=pc.Id
WHERE s.Id=#supplierId AND r.DateReceived = #dateReceived";
// WHERE s.Id=#supplierId";
IList<PartReceipt> reportData;
using (DbConnection connection = ConnectionFactory.GetOpenConnection())
{
reportData = connection.Query<PartReceipt>(sql,
new
{
supplierId = supplier.Id,
dateReceived
}).ToList();
}
return reportData;
}
And the supporting classes are:
public class PartReceipt
{
public int Id { get; set; }
public Supplier Supplier { get; set; }
public Part Part { get; set; }
public DateTime DateReceived { get; set; }
public Decimal UnitCost { get; set; }
public int Quantity { get; set; }
}
public class Part
{
public Color Color { get; set; }
public string Description { get; set; }
public int Id { get; set; }
public ManufacturerPart ManufacturerPart { get; set; }
public string Number { get; set; }
public string PicturePath { get; set; }
public ProductCategory ProductCategory { get; set; }
public string StockClass { get; set; }
}
public class ManufacturerPart
{
public string Description { get; set; }
public int Id { get; set; }
public int ManufacturerId { get; set; }
public string Number { get; set; }
public Manufacturer Parent { get; set; }
}
public class Manufacturer
{
public string Description { get; set; }
public int Id { get; set; }
public string ShortDescription { get; set; }
}
public class ProductCategory
{
public string Description { get; set; }
public int Id { get; set; }
public string Name { get; set; }
}
public class Color
{
public string Description { get; set; }
public int Id { get; set; }
}

I am sorry I was not more careful before posting this question. I did not construct the multi-mapped query properly. When I do, its works.
public IList<PartReceipt> GetReceiptPart(ListItem supplier, DateTime dateReceived)
{
const string sql =
#"SELECT r.id, r.Quantity, r.UnitCost, r.DateReceived,
s.Id , s.Description,
p.Id, p.Number, p.Description, p.StockClass,
mp.Id , mp.Number, mp.ManufacturerId, mp.Description,
m.Id , m.Description, m.ShortDescription,
c.Id, c.Description,
pc.Id, pc.Name, pc.Description
FROM PartReceipt r
INNER JOIN Supplier s ON r.SupplierId = s.Id
INNER JOIN Part p on r.PartId = p.Id
INNER JOIN ManufacturerPart mp ON p.ManufacturerPartId=mp.Id
INNER JOIN Manufacturer m ON mp.ManufacturerId = m.Id
LEFT JOIN Color c ON p.ColorId=c.Id
LEFT JOIN ProductCategory pc ON p.ProductCategoryId=pc.Id
WHERE s.Id=#supplierId AND r.DateReceived = #dateReceived";
IList<PartReceipt> reportData;
using (DbConnection connection = ConnectionFactory.GetOpenConnection())
{
reportData =
connection
.Query
<PartReceipt, Supplier, Part, ManufacturerPart, Manufacturer, Color, ProductCategory, PartReceipt>(
sql,
(receipt, supp, part, mfgPart, mfg, color, productCategory) =>
{
receipt.Supplier = supp;
receipt.Part = part;
receipt.Part.ManufacturerPart = mfgPart;
receipt.Part.ManufacturerPart.Parent = mfg;
receipt.Part.Color = color;
receipt.Part.ProductCategory = productCategory;
return receipt;
}, new { supplierId = supplier.Id, dateReceived })
.ToList();
}
return reportData;
}

Related

Populate two table data using ASP.NET Core

I want to get two table data in ASP.NET Core. I can get one table detail by using model class. then I can show data by using below code.
[HttpGet]
public async Task<ActionResult<IEnumerable<OrderMaster>>> GetOrderDetails()
{
return await _context.OrderDetails.ToListAsync();
}
So my question is how to get two tables data to the above method? As a example I want to retrieve data for below query:
select a.ItemDescription,a.Quantity,a.Amount, a.CustomerCode, b.CustomerName,b.CustomerAddress,b.MobileNumber,b.Email from OrderDetails as a left join CustomerDetails as b ON a.CustomerCode=b.CustomerCode
Thank you
My model classes
public class CustomerMaster
{
[Key]
public int CustomerCode { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string CustomerName { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string CustomerAddress { get; set; }
[Column(TypeName = "nvarchar(10)")]
public string MobileNumber { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string Email { get; set; }
}
public class OrderMaster
{
[Key]
public int OrderId { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string ItemCode { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string ItemName { get; set; }
[Column(TypeName = "nvarchar(MAX)")]
public string ItemDescription { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string Quantity { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string OrderDate { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string Amount { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string CustomerCode { get; set; }
}
this is my Context class
public class AppDbcontext : DbContext
{
public AppDbcontext(DbContextOptions<AppDbcontext> options) : base(options)
{
}
public DbSet<CustomerMaster> CustomerDetails { get; set; }
public DbSet<OrderMaster> OrderDetails { get; set; }
}
}
For how to join two tables,a simple demo you could follow:
var model = (from a in _context.CustomerDetails
join b in _context.OrderDetails
on a.ID equals b.ID
select new {
Name = a.Name,
Address = b.Address
}).ToList();
Update 1:
You use left join sql,you need change like below:
var model = (from a in _context.OrderDetails
join b in _context.CustomerDetails
on a.CustomerCode equals b.CustomerCode.ToString() into ab
from b in ab.DefaultIfEmpty()
select new
{
ItemDescription = a.ItemDescription,
Quantity = a.Quantity,
Amount = a.Amount,
CustomerCode = a.CustomerCode,
CustomerName = b.CustomerName,
CustomerAddress = b.CustomerAddress,
MobileNumber = b.MobileNumber,
Email = b.Email,
}).ToList();
Update 2:
For how to return the two tables data,I think a better way is to create a view model to display:
public class OrderDetailViewModel
{
public string ItemDescription { get; set; }
public string Quantity { get; set; }
public string Amount { get; set; }
public string CustomerCode { get; set; }
public string CustomerName { get; set; }
public string CustomerAddress { get; set; }
public string MobileNumber { get; set; }
public string Email { get; set; }
}
[HttpGet]
public IEnumerable<OrderDetailViewModel> GetOrderDetails()
{
var model = (from a in _context.OrderDetails
join b in _context.CustomerDetails
on a.CustomerCode equals b.CustomerCode.ToString() into ab
from b in ab.DefaultIfEmpty()
select new OrderDetailViewModel
{
ItemDescription = a.ItemDescription,
Quantity = a.Quantity,
Amount = a.Amount,
CustomerCode = a.CustomerCode,
CustomerName = b.CustomerName,
CustomerAddress = b.CustomerAddress,
MobileNumber = b.MobileNumber,
Email = b.Email,
}).ToList();
return model;
}
You have to get the two models connected through navigation properties. Something like
public class Table1 {
int Table1Id;
string Name;
ICollection<Table2> Tables2;
}
public class Table2 {
int Table2Id;
string Address;
Table1 Table1;
}
Then you can have your business layer code
[HttpGet]
... GetNameWithAddresses(int id) {
return (from c in _context.Table1 where c.Table1Id == id
select new { c.Name, c.Tables2 }).ToList();
}
If the tables have primary-foreign key relationship, it will be done automatically for you if you scaffold them. And if you don't have FK relationship, you have to ask yourself why!
Use this code
using (var context = new AppDbcontext())
{
var table1 = context.CustomerDetails.ToList();
var table2 = context.OrderDetails.ToList();
}

EF Core LINQ with conditional JOIN

I'm battling to transfer a SQL statement into a linq statement for my EF Core project. I'm probably battling with the navigation properties, but it seems what I am doing in SQL - won't be possible in EF Core maybe.
I have an Transaction, which has a DebitAccount and a Credit account. A transaction is made of 1:many TransactionLines. And this is where the amount is stored. So a transaction sums up the amounts to get the transaction value.
In EF, I have defined my tables, and attempted to link them correctly with navigation properties. These are how the three tables are declared.
Account Entity:
internal class Account : EntityAudit
{
[Key, Required, Column(Order = 0)]
public int Id { get; set; }
[Required, Column(Order = 1)]
public Guid ExternalId { get; set; }
[Required, Column(Order = 2)]
public AccountTypes AccountTypeId { get; set; }
[Required, Column(Order = 3)]
public UserAccount UserAccount { get; set; }
[Required, Column(Order = 4)]
public string Name { get; set; }
// Navigation Properties
public virtual ICollection<Transaction> TransactionDebitAccount { get; set; }
public virtual ICollection<Transaction> TransactionCreditAccount { get; set; }
}
Transaction Entity:
internal class Transaction : EntityAudit
{
[Key, Required, Column(Order = 1)]
public int Id { get; set; }
[Required, Column(Order = 2)]
public Guid ExternalId { get; set; }
[Required, Column(Order = 3)]
public DateTime Date { get; set; }
[Column(Order = 4)]
public string Description { get; set; }
[Required, Column(Order = 3), InverseProperty("TransactionDebitAccount")]
public virtual Account DebitAccount { get; set; } // Nav
[Required, Column(Order = 4), InverseProperty("TransactionCreditAccount")]
public virtual Account CreditAccount { get; set; } // Nav
[Required]
public virtual List<TransactionLine> TransactionLines { get; set; } //Nav
}
}
And the TransactionLine entity:
internal class TransactionLine
{
[Key, Required, Column(Order = 0)]
public int Id { get; set; }
[Required, Column(Order = 1)]
public Guid ExternalId { get; set; }
[Required, Column(Order = 2)]
public virtual Transaction Transaction { get; set; } // Nav
[Column(Order = 5), Range(0.01, 999999.99, ErrorMessage = "Amount must be between 0.01 and 999,999.99")]
public decimal Amount { get; set; }
[Column(Order = 6)]
public virtual Budget Budget { get; set; }
[Column(Order = 7)]
public virtual Subcategory Subcategory { get; set; }
}
I am now trying to get a list of account balances. I can achieve this with a SQL statement, which uses a conditional join, and this is maybe where I am stuck on EF Core.
FROM Account a
INNER JOIN [Transaction] t
ON (t.CreditAccountId = a.Id OR t.DebitAccountId = a.Id )
That conditional join is what is stopping me. I need to join the transactions to the account, based on one of two columns.
The whole query looks like this:
Select a.ExternalId AS ExternalId, a.Name,
SUM(tl.Amount *
case when t.CreditAccountId = a.Id then 1
when t.DebitAccountId = a.Id then -1 end)
as Amt
FROM Account a
INNER JOIN [Transaction] t
ON (t.CreditAccountId = a.Id OR t.DebitAccountId = a.Id )
INNER JOIN TransactionLine tl
ON tl.TransactionId = t.id
WHERE a.AccountTypeId = 1
GROUP BY a.Id, a.ExternalId, a.Name
I can't find a way to achieve this in EF Core using linq. Is this possible? Or would I need to restructure tables or maybe execute the SQL instead? I'd prefer to stay with EF Core / linq.
I am trying:
var accounts = (from a in context.Account
from t in context.Transaction
where t.DebitAccount.Id == a.Id || t.CreditAccount.Id == t.Id
from tl in context.TransactionLine
where tl.Transaction.Id == t.Id
select new { a.ExternalId, a.Name })
.GroupBy(x=> new { x.ExternalId, x.Name, })
But failing... The 'GroupBy' isn't available to me when trying this syntax.

Problem with NewtonJson deserialization json from stored procedure which return result for json

I have a stored procedure in SQL Server 2016 like this:
SELECT
a.Id as [ProdList.ProdId],
#Main1 as [ProdList.Main],
a.Hit as [ProdList.Hit],
a.New as [ProdList.New],
a.Sale as [ProdList.Sale],
a.Price as [ProdList.Price],
a.Price_old as [ProdList.OldPrice],
a.Sort as [ProdList.Sort],
a.ShName as [ProdList.Name],
c.Big as [ProdList.ImagePath],
a.SaleSize as [ProdList.SaleSize],
'' as [ProdList.QueryString],
0 as [ProdList.RANK],
(SELECT
('/products/product/'+cast(f.Id as nvarchar(255))) as [Link],
g.Big as [Img]
FROM
Products f
INNER JOIN
GroupProducts d ON d.ProdId = f.Id
INNER JOIN
ProdImages g ON g.ProdId = f.Id
WHERE
d.ParentId = a.Id AND g.Main = 1
FOR JSON PATH) AS [GroupProduct]
FROM
Products a
INNER JOIN
ProdInCategory b ON a.Id = b.ProdId
INNER JOIN
ProdImages c ON a.Id = c.ProdId
INNER JOIN
GroupProducts e ON a.Id = e.ProdId
WHERE
c.Main = 1 AND b.CatId = #CatId AND e.ParentId = 0
UNION
SELECT
a.Id as [ProdList.ProdId],
#Main1 as [ProdList.Main],
a.Hit as [ProdList.Hit],
a.New as [ProdList.New],
a.Sale as [ProdList.Sale],
a.Price as [ProdList.Price],
a.Price_old as [ProdList.OldPrice],
a.Sort as [ProdList.Sort],
a.ShName as [ProdList.Name],
g.Big as [ProdList.ImagePath],
a.SaleSize as [ProdList.SaleSize],
'' as [ProdList.QueryString],
0 as [ProdList.RANK],
null as [GroupProduct]
FROM
Products a
INNER JOIN
ProdImages g ON g.ProdId = a.Id
INNER JOIN
ProdInCategory pc ON a.Id = pc.ProdId
WHERE
a.Id NOT IN (SELECT ProdId FROM GroupProducts)
AND pc.CatId = #CatId
FOR JSON PAT
In my ASP.NET MVC 5 application I run this stored procedure and deserialize result to object:
string query = String.Format("EXEC [dbo].[GetListProdId3] #CatId={0}", incommingModel.id);
var result = string.Concat(db.Database.SqlQuery<string>(query));
List<ProdList> tovarListJson1 = JsonConvert.DeserializeObject<List<ProdList>>(result);
The first select to union takes all the row filled with GroupProduct. The second select after the union takes the rest of the products from the category that have no group (Not in GroupProduct). If Im comment out half of the selection before the union statement in SP, then everything works(Independently before union select top or bottom). If Im try to run the whole select with the union statement, I get the error:
My class to deserialization :
public class ProdList
{
public int ProdId { get; set; }
public bool Main { get; set; }
public bool Hit { get; set; }
public bool New { get; set; }
public bool Sale { get; set; }
public decimal Price { get; set; }
public decimal OldPrice { get; set; }
public decimal Sort { get; set; }
public string Name { get; set; }
public string ImagePath { get; set; }
public string SaleSize { get; set; }
public int Rank { get; set; }
public string QueryString { get; set; }
public List<NestedProdList> GroupProduct { get; set; }
}
My json from stored procedure(first element and last):
[{"ProdList":{"ProdId":225,"Main":true,"Hit":false,"New":false,"Sale":false,"Price":22.0000,"OldPrice":0.0000,"Sort":23.5200,"Name":"Ручка шариковая \"Tris Lx\", синяя","ImagePath":"\/images2\/44012\/ruchka-sharikovaya-tris-1.jpg","SaleSize":"0% ","QueryString":"","RANK":0},"GroupProduct":"[{\"Link\":\"\\\/products\\\/product\\\/23091\",\"Img\":\"\\\/images2\\\/40554\\\/ruchka-sharikovaya-tris-1.jpg\"},{\"Link\":\"\\\/products\\\/product\\\/32836\",\"Img\":\"\\\/images2\\\/40555\\\/ruchka-sharikovaya-tris-1.jpg\"},{\"Link\":\"\\\/products\\\/product\\\/32918\",\"Img\":\"\\\/images2\\\/44010\\\/ruchka-sharikovaya-tris-1.jpg\"},{\"Link\":\"\\\/products\\\/product\\\/37093\",\"Img\":\"\\\/images2\\\/44011\\\/ruchka-sharikovaya-tris-1.jpg\"},{\"Link\":\"\\\/products\\\/product\\\/56573\",\"Img\":\"\\\/images2\\\/40553\\\/ruchka-sharikovaya-tris-1.jpg\"}]"},{"ProdList":{"ProdId":68812,"Main":true,"Hit":false,"New":false,"Sale":false,"Price":9.2000,"OldPrice":20.8900,"Sort":21.5600,"Name":"MIR FANTASY, ручка шариковая","ImagePath":"\/images2\/63849\/mir-fantasy-ruchka-1.jpg","SaleSize":"0% ","QueryString":"","RANK":0},"GroupProduct":null},{"ProdList":{"ProdId":69329,"Main":true,"Hit":false,"New":false,"Sale":false,"Price":27.0000,"OldPrice":0.0000,"Sort":31.3600,"Name":"Ручка шариковая \"Twisty Safe Touch\", светло-голубая","ImagePath":"\/images2\/11763\/ruchka-sharikovaya-twisty-1.jpg","SaleSize":"0% ","QueryString":"","RANK":0},"GroupProduct":null}]

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