Dapper Multi Mapping Result - dapper

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

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.

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

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

how to combine the result from differentviews to IQueryable<T>

i have a list of PlanItems bind to telerik RadGrid and have two views retrieves the result.Each view get different data so how can i get that data into IQueryable
public class PlanItems
{
public int Id{get; set;}
public string Name { get; set; }
public string HV { get; set; }
public string StNumber { get; set; }
public string LDNumber { get; set; }
public string MSId { get; set; }
public string CreatedBy { get; set; }
public string Type { get; set; }
}
I have 2 different views vw_Boys ,vw_Girls and Student Entity
first i got boys_Id and Girls_Id from student Entity
public IQuerable<PlanItems> GetAllStudents()
{
var Id = from v in context.Student
.Include("SBoy")
.Include("SGirl")
where v.CreatedBy_Id == user.Id
select v;
var temp = unp.Select(a=>a.SBoy.boys_Id).Distinct();
var temp1 = unp.Select(a => a.SGirl.girls_Id).Distinct();
var vwboys = from nv in context.vw_boys
where(temp.Contains(nv.SId))
select nv;
var vwgirls= from nv in context.vw_girls
where (temp1.Contains(nv.GId))
select nv;
var Boysresult = from n in vwboys
select new PlanItems
{
Id = n.SId,
Name = n.Name,
HV = n.HV,
StNumber = n.SNumber,
LDNumber = n.LineNumber,
MSId = n.MasterId,
CreatedBy = n.USerName,
Type = n.SubjectType
};
var GirlsResult = from n in vwgirls
select new UnplannedItems
{
Id = n.GiId,
Name = n.Name,
HV = n.GV,
StNumber = n.SujNumber,
LDNumber = n.Lid,
MSId = n.MasterId,
CreatedBy = n.USerName,
Type = n.SubjectType
};
}
my telerikGrid sample
<telerik:GridViewDataColumn Header="StudentID" IsReadOnly="True" DataMemberBinding="{Binding ID, Mdde=OneWay}" />
how can i return the result from above method..how can i combine the result to one.
I would do it something like this.
Add a new property to you class like this. Because you might have to seperate the boys from the girls in a other case:
public class PlanItems
{
public int Id{get; set;}
public string Name { get; set; }
public string HV { get; set; }
public string StNumber { get; set; }
public string LDNumber { get; set; }
public string MSId { get; set; }
public string CreatedBy { get; set; }
public string Type { get; set; }
//new property
public bool IsUnplanned { get; set; }
}
The use a concat between them like this:
var result= (
from n in vwboys
select new PlanItems
{
Id = n.SId,
Name = n.Name,
HV = n.HV,
StNumber = n.SNumber,
LDNumber = n.LineNumber,
MSId = n.MasterId,
CreatedBy = n.USerName,
Type = n.SubjectType,
IsUnplanned=false
}
).Concat
(
from n in vwgirls
select new PlanItems
{
Id = n.GiId,
Name = n.Name,
HV = n.GV,
StNumber = n.SujNumber,
LDNumber = n.Lid,
MSId = n.MasterId,
CreatedBy = n.USerName,
Type = n.SubjectType,
IsUnplanned=true
}
);
Hope this helps
You can union 2 different IQueryable using union extension method :
here's an example
I am not an expert in wpf, but looking at the solution I think you can use a parent class for both Boys and Girls and then add to a list of that class.
lets say both Boy and Girl classes are inherited from Person class, then create
List<Person> personList = new List<Person>()
and add Boys and Girls into this list.
I am not sure whether this is the solution you need

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