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

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}]

Related

How to map subquery in stored procedure in EF Core

We have the following models (shortenend for brevity)
public class Patient
{
public int Id {get; set;
public string LastName { get; set; }
public string FirstName { get; set; }
public ICollection<Address> Addresses { get; set; } = new List<Address>();
}
public class Address
{
public int PatientId {get; set;
public string Street { get; set; }
public string Number { get; set; }
public string Zip { get; set; }
public string City { get; set; }
}
We like to map the results of a stored procedure ( a list of patients with their addresses) to them using EF.
select
p.* ,
(select a.street from Addresses as a where a.PatientId = p.id) as addresses
from
Patients as p
where
... (a set of clauses and joins to limit the list to the desired patients)
Without the extra select to get the addresses all works fine, well, except we do not get the addresses.
We get the error :
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
Any suggestions ?
You can not return a list into an column in SQL. you can join data with dash - and store in a column like this and you can Split AddressData in c# by - and store in a list.
select
p.* ,
AddressData = COALESCE(STUFF
(
(
select ' - ' + a.street from Addresses as a where a.PatientId = p.id
FOR XML PATH('')
), 1,2, N''
), N'')
from
Patients as p
where
... (a set of clauses and joins to limit the list to the desired patients)
public class Patient
{
public int Id {get; set;
public string LastName { get; set; }
public string FirstName { get; set; }
public string AddressData { get; set; }
public ICollection<Address> Addresses
{
get
{
return AddressData.Split('-').ToList().Select(a => new Address
{
Street = a
}).ToList();
}
}
}

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 and DateTime in WHERE clause causes multi-mapped nulls

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

Can't execute regular sql query through Entity Framework

I'm trying to execute a long sql query through:
IEnumerable<erequest> blah = db.Database.SqlQuery<erequest>().ToList();
query is displayed below for your convenience. When I exectute the query I'm getting the following error:
The data reader is incompatible with the specified 'enrollDBModel.erequest'.
A member of the type, 'er_student_id', does not have a corresponding column in
the data reader with the same name.
However, the er_student_id meber does exist in the erequest.cs Context.tt class
public int er_student_id { get; set; }
(see also bottom of post for complete class)
Query:
SELECT erequests.er_id,
CASE
WHEN current_subject_enrollment.count >= subjects.sj_max_enrollment
THEN 0
WHEN already_passed.count >= 1 THEN 0
WHEN is_already_enrolled.count >= 1 THEN 0
WHEN failure_times.count >= 3 THEN 0
WHEN current_student_enrollment.count >= 4 THEN 0
ELSE 1
END AS can_enroll,
students.st_first_name,
students.st_mid_name,
students.st_last_name,
students.st_student_id,
subjects.sj_subject_name,
subjects.sj_availability,
subjects.sj_max_enrollment,
erequests.er_subject_id,
erequests.er_reason,
erequests.er_status
FROM erequests
RIGHT JOIN students
ON erequests.er_student_id = students.st_student_id
RIGHT JOIN subjects
ON erequests.er_subject_id = subjects.sj_subject_id
AND subjects.sj_availability = 1
LEFT OUTER JOIN (SELECT Count(em_student_id) AS count,
em_subject_id
FROM enrollment
WHERE ( em_enrolled = 1 )
GROUP BY em_subject_id) AS current_subject_enrollment
ON erequests.er_subject_id =
current_subject_enrollment.em_subject_id
LEFT OUTER JOIN (SELECT Count(em_student_id) AS count,
em_student_id,
em_subject_id
FROM enrollment
WHERE ( em_enrolled = 1 )
GROUP BY em_student_id,
em_subject_id) AS is_already_enrolled
ON erequests.er_student_id =
is_already_enrolled.em_student_id
AND erequests.er_subject_id =
is_already_enrolled.em_subject_id
LEFT OUTER JOIN (SELECT Count(em_student_id) AS count,
em_student_id,
em_subject_id
FROM enrollment
WHERE ( em_enrolled = 0
AND em_result >= 50 )
GROUP BY em_student_id,
em_subject_id) AS already_passed
ON erequests.er_subject_id = already_passed.em_subject_id
AND erequests.er_subject_id =
already_passed.em_subject_id
LEFT OUTER JOIN (SELECT Count(em_student_id) AS count,
em_student_id,
em_subject_id
FROM enrollment
WHERE ( em_enrolled = 0
AND em_result < 50 )
GROUP BY em_student_id,
em_subject_id) AS failure_times
ON erequests.er_subject_id = failure_times.em_subject_id
AND erequests.er_subject_id = failure_times.em_subject_id
LEFT OUTER JOIN (SELECT Count(em_subject_id) AS count,
em_student_id
FROM enrollment
WHERE ( em_enrolled = 1 )
GROUP BY em_student_id) AS current_student_enrollment
ON erequests.er_student_id =
current_student_enrollment.em_student_id
WHERE ( subjects.sj_availability = 1 )
AND ( erequests.er_status NOT IN ( 'A', 'D' ) )
erequest.cs:
public partial class erequest
{
public int er_id { get; set; }
public int can_enroll { get; set; }
[Required]
[Range(1, 100000)]
[Display(Name = "Subject Id: ")]
public int er_subject_id { get; set; }
[Required]
[Range(1, 100000)]
[Display(Name = "Student Id: ")]
public int er_student_id { get; set; }
[Required]
[Display(Name = "Reason: ")]
public string er_reason { get; set; }
[Required]
[Display(Name = "Status: ")]
public string er_status { get; set; }
public virtual student student { get; set; }
public virtual subject subject { get; set; }
public IEnumerable<erequest> processedErequests { get; set; }
public IEnumerable<erequest> unprocessedErequests { get; set; }
}
You're interpreting the error the wrong way around. It says:
A member of the type, 'er_student_id', does not have a corresponding column in the data reader
It's telling you that it can't find a value in the datareader for that property. You need to make sure your datareader has a column with that name (eg. change your query)

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