Dapper multimapping with nested objects c# - c#

I have classes like this:
public class BigClass
{
public int Id {get; set;}
public List<First> Firsts {get; set;}
}
public class First
{
public int Id {get ;set;}
public int BigClassId {get; set;}
public int Name {get; set;}
public List<Second> Seconds {get; set;}
public List<Third> Thirds {get; set;}
public List<Fourth> Fourths {get; set;}
}
public class Second
{
public int Id {get; set;}
public string Name {get; set;}
public string Code {get; set;}
}
public class Third
{
public int Id {get; set;}
public string Code {get; set;}
}
public class Fourth
{
public int Id {get; set;}
public string Code {get; set;}
}
Database - BigClass
id_bigclass
-----------
1
Database - First
id_first id_bigclass name_first
-------- ----------- ----------
1 1 hero
Database - Second
id_second id_code first1_name
-------- ------- ----------
1 3333 hero
2 4444 hero
3 8888 villian
Database - Third
id_third id_code
-------- -------
1 3333
2 4444
3 6666
Database - Fourth
id_fourth Code
-------- -------
1 3333
2 4444
3 5555
So one bigclass has several first class instances. First class has several second, third, fourth class instances. But second.name = first.name; and third.code = second.code; fourth.code = second.code;
I wrote dapper multimapping but what it's returning depends on first3 list count not inserting into first class's List.
var sql = "select * from bigclass as bc " +
"LEFT JOIN first AS fr on fr.id_bigclass = bc.id " +
"LEFT JOIN second AS sc on sc.Name = fr.Name " +
"LEFT JOIN third AS th on th.Code = sc.Code " +
"LEFT JOIN fourth AS fo on fo.Code = sc.HandCode ";
var bigClass = conn.Query<BigClass>(sql, new[] { typeof(BigClass), typeof(First), typeof(Second), typeof(Third), typeof(Fourth) }, obj =>
{
BigClass bigClass= obj[0] as BigClass;
First first = obj[1] as First;
Second second = obj[2] as Second;
Third third = obj[3] as Third;
Fourth fourth = obj[4] as Fourth;
bigClass.Firsts = bigClass.Firsts ?? new List<First>();
First.Seconds.Add(second); First.Third.Add(third); First.Fourth.Add(fourth);
return bigClass;}
, splitOn: "id").FirstOrDefault();
My expected result is BigClass Firsts list count is 1, First classes Seconds list count is 2, Third count is 2, Fourth count is 2. How to do it?

Related

Dapper binding doesnt work in multi mapping

TThis is my dto.Worker table:
| id | first_name | current_branch |
|----|------------|----------------|
| 1 | Adam | 5 |
This is how I fetch data from this table:
public async Task<WorkerDTO> GetById(int workerId)
{
using (var connection = connectionFactory.GetConnection())
{
string sQuery = $#"SELECT * FROM dto.Worker WITH(NOLOCK) WHERE id = #WorkerId";
return await connection.QueryFirstAsync<WorkerDTO>(sQuery, new { WorkerId = workerId }).ConfigureAwait(false);
}
}
The WorkerDTO class has the same structure as dto.Worker table, but properties doesn't contain _ character:
public class WorkerDTO
{
public int Id {get; set;}
public string FirstName {get; set;}
public int CurrentBranch {get; set;}
}
There is also a dto.Job table:
| id | job_name |
|----|----------|
| 1 | foo |
And dto.WorkerJob table:
| worker_id | job_id |
|-----------|--------|
| 1 | 1 |
I want to fetch all workers and its jobs. I already established that I need to use multi mapping dapper feature. I came with something like this:
public async Task<IEnumerable<WorkerJobDTO>> GetAllWorkersJobs()
{
using (SqlConnection connection = connectionFactory.GetConnection())
{
var sQuery = $#"SELECT worker.*, job.* FROM dbo.Worker worker
LEFT JOIN dbo.WorkerJob workerJob ON workerJob.worker_id = worker.id
LEFT JOIN dbo.Job job ON job.id = workerJob.job_id";
var workers = await connection.QueryAsync<WorkerJobDTO, JobDTO, (WorkerJobDTO workerJob, JobDTO job)>(sQuery, (worker, job) => (worker, job), parameters).ConfigureAwait(false);
return workers.GroupBy(x => x.worker.Id)
.Select(group =>
{
var worker = group.First().worker;
worker.Jobs = group
.Select(x => x.job)
.Where(x => x != null)
.ToList();
return worker;
});
}
}
Unfortunately this doesn't work, because there is a mismatch between WorkerJobDTO properties names and database column names. To fix this, I have to change WorkerJobDTO class from:
public class WorkerJobDTO
{
public int Id {get; set;}
public string FirstName {get; set;}
public int CurrentBranch {get; set;}
public List<JobDTO> Jobs {get; set;}
}
to:
public class WorkerJobDTO
{
public int id {get; set;}
public string first_name {get; set;}
public int current_branch {get; set;}
public List<JobDTO> Jobs {get; set;}
}
Is there any way to fix this without modyfing DTO model's properties names?
You can change the names returned from the query, if you specify each column and an alias. Selecting '*' is anyway not considered best practice as there are some performance hits in the server in comparison with specifying the columns explicitly. Change your query to this:
var sQuery = $#"SELECT worker.id AS Id,
worker.first_name AS Name,
worker.current_branch AS CurrentBranch,
job.* FROM dbo.Worker worker
LEFT JOIN dbo.WorkerJob workerJob ON workerJob.worker_id = worker.id
LEFT JOIN dbo.Job job ON job.id = workerJob.job_id";
That would be a quick fix. However, your multimapping code doesn't look like standard and I don't see what you need WorkerJobDTO for, it is after all, just a link table. I would change the whole thing to something like this:
public async Task<IEnumerable<WorkerJobDTO>> GetAllWorkersJobs()
{
using (SqlConnection connection = connectionFactory.GetConnection())
{
var workerDictionary = new Dictionary<int, WorkerDTO>();
var sQuery = $#"SELECT worker.*, job.* FROM dbo.Worker worker
LEFT JOIN dbo.WorkerJob workerJob ON workerJob.worker_id = worker.id
LEFT JOIN dbo.Job job ON job.id = workerJob.job_id";
var workers = await connection.QueryAsync<WorkerDTO, JobDTO, WorkerDTO>(sQuery,
(worker, job) =>
{
WorkerDTO workerEntry;
if (!workerDictionary .TryGetValue(worker.Id, out workerEntry))
{
workerEntry = worker;
workerEntry.Jobs = new List<JobDTO>(); // only if it's not done in the default constructor
workerDictionary.Add(worker.Id, workerEntry);
}
workerEntry.Jobs.Add(job);
return null; // This doesn't matter, the result will be in workers
}, parameters).ConfigureAwait(false);
}
}
That's pretty much the standard multi-mapping pattern in Dapper and requires no after-burning.

Lambda Method Query Syntax with a List within a List

I have my model as:
// Person
public class Person {
public int Id {get; set}
public string FirstName {get; set}
public string LastName {get; set}
public ICollection<PersonCourse> PersonCourses {get; set;}
}
// PersonCourse
public class PersonCourse {
public int PersonId {get; set}
public Person Person {get; set;}
public int CourseId {get; set}
public Course Course {get; set}
}
// Course
public class Course {
public int Id {get; set}
public string Name {get; set}
public ICollection<PersonCourse> PersonCourses {get; set;}
}
I have the following data:
// Person
ID FIRSTNAME LASTNAME
1 John Doe
2 Jane Doe
// PersonCourse
PersonId CourseId
1 1
1 2
2 1
2 3
// Course
ID NAME
1 Course1
2 Course2
3 Course3
How can I write a Lambda Method query to get the data as an IEnumerable as:
John Doe Course1
John Doe Course2
Jane Doe Course1
Jane Doe Course3
Currently, I have the courses listed in a CSV format like below:
// NOTE:
// MUST start query from DBSet<Person> not from DBSet<PersonCourse>!
var data = db.Persons.Select(x => new {
FirstName = x.FirstName,
LastName = x.LastName,
CourseName = string.Join(",", x.PersonCourses.Select(c => c.Course.Name)
});
The best approach for this is:
var data = db.Persons.SelectMany(c => c.PersonCourses.Select(x => c.FirstName + " " + c.LastName + " " + x.Course.Name));
Assuming that everything is not null. I prefer always to manage Null safety, but that depends on the final result that you want (discard if is a single null or fill with empty string)
To get multiple rows per Person you need a Join or a SelectMany.
Using the navigation properties you can do this:
var data = db.Persons.SelectMany
(
p => p.PersonCourses,
(p, pc) => new { p.FirstName, p.LastName, pc.Course.Name }
);
Or you can do the uglier but more general join, avoiding the navigation properties:
var data = db.Persons
.Join
(
db.PersonCourses, p => p.Id, pc => pc.PersonId,
(p, pc) => new { p, pc }
)
.Join
(
db.Courses, j => j.pc.CourseId, c => c.Id,
(j, c) => new { j.p.FirstName, jn.p.LastName, c.Name }
);
Joins work when the navigation properties aren't available (POCOs in memory for instance) and aren't all that hard to work with... except for that pesky intermediate j but you get used to it.

Joining 2 lists of objects based on object id

In my web application I am fetching 2 lists of objects from database.
First list of objects Employee
1
Name: Tom
Week1: 1
Week2: 3
Week3: 7
2
Name: Mike
Week1: 2
Week2: 1
Week3: 7
Second list of objects listOfId
1
id: 1
color: green
symbol: AT
2
id: 2
color: red
symbol: TB
3
id: 3
color: blue
symbol: TD
I would like to be able now to display it in a form of table, where for each of the weeks, I display this weeks color and symbol (each week matched on Employee.Week# = listOfId.id)
Something like this
Name | Week1 | Week1 Color | Week1 Symbol | Week2 | Week 2 Color etc...
Tom 1 green AT 3 blue
Mike 2 red TB 1 green
In total I will have constant 20 weeks for each employee.
I considered writing a SQL query which would take in week id, and return color and symbol. But for 50 people * 20 weeks... I would need to run this query 1000 times.
I am looking for better approach to solving this issue
my Models:
public class WeekViewModel
{
public int Id{ get; set; }
public string ShortNAme { get; set; }
public string Description { get; set; }
public int Color { get; set; }
}
}
public class EmployeeWeekViewModel
{
public string full_name { get; set; }
public string location { get; set; }
public int week1 { get; set; }
public int week2 { get; set; }
public int week3 { get; set; }
}
If I'm getting your correct, you have two lists coming from DB:
employeesList which represents the employees with their corresponding weeks ID
weeksList which represents the weeks for an employee.
Now, you want to join these two lists to display the information in a simple table format. I would do something like that:
public class EmployeeWeekViewModel{
public string EmployeeName{get;set;}
public WeekViewModel Week1 {get;set;}
public WeekViewModel Week2 {get;set;}
...
public WeekViewModel Week20 {get;set;}
}
public class WeekViewModel{
public int Id{get;set;}
public string Color{get;set;}
public string Symbol{get;set;}
}
employeesList.Select(t=> new EmployeeWeekViewModel(){
EmployeeName = t.Name,
Week1 = weeksList.FirstOrDefault(w => w.id == t.Week1).Select(w => new WeekViewModel(){
Id = w.id,
Color = w.color,
Symbol = w.symbol
}),
Week2 = weeksList.FirstOrDefault(w => w.id == t.Week2).Select(w => new WeekViewModel(){
Id = w.id,
Color = w.color,
Symbol = w.symbol
}),
...
});
In order to not make a request for each week, I would suggest send to you query a list of week Ids to get them once (checkout this example).

Join Parent, Child and GrandChild tables with latest record from grandchild table using LinQ in C#

Please note this is just to simulate the example but not the actual tables itself.
Apologies for long question
I have three tables Vendor, Product and Orders. They are linked like below.
Vendor --> Id, Name, Location (this table has all info regarding vendor)
Product --> Id, VendorId, Name, Description (this table has all the product that vendor sells)
Orders --> Id,ProductId,Date (this table has all the Orders placed for that product)
Vendor has One to One relationship with Product table but some of the vendors may not have product at all.
Product table has one to many relationship with Orders table
I am trying to get the latest Order placed for a Vendor using linq.
Example.
Vendor :
1 | Microsoft | Seattle
2 | Apple | California
3 | Amazon | Seattle
Product :
1 | 1 | MS Office | Office Suite
2 | 2 | iPhone | Smart Phone
Order:
1 | 1 | 05/27/2016
2 | 1 | 06/07/2016
3 | 2 | 04/17/2016
4 | 2 | 06/01/2016
Expected Result:
1 | Microsoft | Seattle
1 | 1 | MS Office | Office Suite
2 | 1 | 06/07/2016
2 | Apple | California
2 | 2 | iPhone | Smart Phone
4 | 2 | 06/01/2016
3 | Amazon | Seattle
null
null
var vendor = db.Vendor.Include(x => x.Product.Select(s => s.Order)).OrderBy(a => a.Name).Select(s => new VendorModel
{
Id = c.Id,
Name = c.Name,
Location = c.Location
Product = c.Product.Select(m => new ProductModel
{
Id = m.Id,
VendorId = m.VendorId,
Name = m.Name,
Description = m.Description,
}).FirstOrDefault(),
Order = c.Order.Select(m => new OrderModel
{
Id = m.Id,
ProductId = m.ProductId,
Date = m.Date,
}).OrderBy(o=>o.Date).FirstOrDefault(),
});
public class VendorModel
{
public ApplicationModel() { }
public ApplicationModel(int id, string name, string location)
{
this.Id = id;
this.Name = name;
this.Location = location;
}
public int Id { get; set; }
public string Name { get; set; }
public string Location{ get; set; }
public virtual ProductModel Product { get; set; }
public virtual OrderModel Order { get; set; }
}
public class ProductModel
{
public ProductModel() { }
public ProductModel(int id, int vendorId, string name, string description)
{
this.Id = id;
this.VendorId = vendorId;
this.Name = name;
this.Description = description;
}
public int Id { get; set; }
public int VendorId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public VendorModel Vendor { get; set; }
}
public class OrderModel
{
public OrderModel() { }
public OrderModel(int id, int productId, DateTime date)
{
this.Id = id;
this.ProductId = productId;
this.Date = date;
}
public int Id { get; set; }
public int ProductId { get; set; }
public DateTime Date { get; set; }
public ProductModel Product { get; set; }
}
As Vendor might have null value, I think you should use Left outer joins. Here is how you could implement left outer joins with LINQ.
var query = from v in Vendor
join p in Product on v.id equals p.vendorId into t1
from vp in t1.DefaultIfEmpty()
join o in Orders on p.id equals o.productId t2
from vpo in t2.DefaultIfEmpty()
orderby v.Name, vpo.date
select new
{
v.Name, v.Location,
productId=(int?)vp.id, // To handle null value if id(ProductId) is specified as not null in Product table.
vp.Name, vp.Description,
orderId = (int?)vpo.id, // To handle null value if id(OrderId) is specified as not null in Orders table.
vpo.date
}.ToList();

Merge two DataTables with LINQ

My question is similar too THIS
I have two DataTables:
DataTable 1:
Column 1: Date
Column 2: Requests 1
DataTable 2:
Column 1: Date
Column 2: Requests 2
I need the bellow result:
New DataTable:
Column 1: Date
Column 2: Requests 1
Column 3: Requests 2
Expected Result:
Date Requests 1 Requests 2 Total
15/08/2013 25 40 60
14/08/2013 40 60 100
13/08/2013 40 0 25
12/08/2013 0 80 80
What I did until now:
DataTable Lista_1 = ds.Tables[0];
DataTable Lista_2 = ds.Tables[1];
var myLINQ = from l1 in Lista_1.AsEnumerable()
join l2 in Lista_2.AsEnumerable()
on l1.Field<DateTime>("Date") equals l2.Field<DateTime>("Date")
select new
{
dateRelatorio = l1.Field<DateTime>("Date"),
request1Relatorio = l1.Field<int>("Total"),
request2Relatorio = l2.Field<int>("contagem"),
total = l1.Field<int>("Total") + l2.Field<int>("contagem")
};
And I would like to return an IList collection (System.Collections.IList)
UPDATE
listReturn = new List<Stats>();
public class Stats
{
public DateTime dateRelatorio { get; set; }
public int request1Relatorio { get; set; }
public int request2Relatorio { get; set; }
public int total { get; set; }
}
UPDATE 2
May have dates in List_1 that there is not in List_2 and vice versa, so the Request need to be 0.
If you need this thing to be encapsulated in a method, you should create a class to be returned:
public class DailyReport
{
public DateTime Date {get; set;}
public int Requests1 {get; set;}
public int Requests2 {get; set;}
public int Total // this can be calculated on the fly
{
get {return Requests1 + Requests2; }
}
}
And then do
public List<DailyReport> GetDailyReports(..parameters if needed...)
{
...
var myLINQ = from ...
select new DailyReport {
Date = l1.Field<DateTime>("Date"),
Requests1 = l1.Field<int>("Total"),
Requests2 = l2.Field<int>("contagem"),
};
return myLINQ.ToList();
}
There are some dirty hacks that could enable you to return an collection of anonymous objects, but I would advise against it.

Categories