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.
Related
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.
I am having employee table:
public class Employee
{
public int Id {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int SupervisorId {get;set;}
}
SupervisorId is a foreign key from the same table (Employee) that points to other employee.
Then I have something like "EmployeeSupervisorDto"
public class EmployeeSupervisorDto
{
public int Id {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public string FullNameSupervisor {get;set;}
}
What I want to achievie is to use automapper to set FullNameSupervisor automaticly to combination of FirstName and LastName of supervisor.
Tried so far to do something like this:
cfg.CreateMap<Employee, EmployeeSupervisorDto>()
.ForMember(e => e.FullNameSupervisor, m => m.MapFrom(p => $"{p.LastName} {p.FirstName}"));
But I have no idea how to do reference to Id that points out to employee id that is supervisor of given employee.
To use the below solution, you will need to inject your data context to the auto mapper profile class (via constructor parameter), and also, in the ConfigureServices, add the DI of the automapper profile as shown in https://stackoverflow.com/a/49198279/9907597.
Create a method in the AutoMapper profile class:
public string GetEmployerFullName(int supervisorEmpId)
{
var supervisor = db.Employees.Find(supervisorEmpId);
return supervisor.FirstName + " " + supervisor.LastName;
}
Then create the mapping in the automapper profile class constructor as:
CreateMap<Employee, EmployeeSupervisorDto>()
.ForMember(e => e.FullNameSupervisor, m => m.MapFrom(p => GetEmployerFullName(p.SupervisorId)));
You can use either ValueResolver or something like this code if you want to use it once and not generally:
Mapper.CreateMap<Employee, EmployeeSupervisorDto>()
.ForMember(e => e.FullNameSupervisor, o => o.ResolveUsing(m => { return m.LastName + m.FirstName}));
May help you,
i try with linq approach :
public class Employee
{
public int Id {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int SupervisorId {get;set;}
}
it give you the list of employe with sup FullNameSupervisor
var Employeelist = new List<Employee>() {...};
var EmployeeWithSup = from ep in Employeelist
join epsup in Employeelist on ep.SupervisorId equals epsup.Id
select new { Id = ep.Id,FirstName = ep.FirstName,LastName = ep.LastName,
SupervisorId = ep.SupervisorId,FullNameSupervisor = epsup.FirstName + " " + epsup.LastName };
If you want to do joins use automapper ,try the follow link :
AutoMapper to join records from 2 tables into single IEnumerable viewmodel
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?
I'm creating a web api. I have two tables
Employee_details
SlNo Employeeid R_ID
1 2022 AC1
2 2023 AC2
Id_details
SlNo R_ID L_ID
1 AC1 L001
2 AC1 L002
3 AC1 L003
4 AC2 L004
5 AC2 L005
I created a web api with RESTful services. I need to get all the L_ID of a particular Employeeid. For example if I request for employeeid 2022 I should get L001 L002 and L003. Help me please. I created the tables using code first and enabling migration
Code
Model class
Employee_details.cs
public class Employee_details
{
public int slNo {get; set;}
public string Employeeid {get; set;}
public string R_ID {get; set;}
//Navigation
public Id_details Id_details{get; set;}
}
Id_details.cs
public class Id_details
{
public int slNo {get; set;}
public string R_ID {get; set;}
public string L_ID {get; set;}
}
Controller
public IQueryable<Employee_id> Getdetails(string employeeid )
{
return db.Employee_details
.Where(b => b.Employeeid.Equals(employeeid, StringComparison.OrdinalIgnoreCase)).Include(c => c.Id_details);
}
I did not make foreign key reference since R_ID field in Table 2 is not a primary key.
If you use Native SQL this query is your return data.
SELECT ID.L_ID
FROM Id_details AS ID
INNER JOIN Employee_details ED
ON ID.R_ID = ED.R_ID
WHERE ED.Employeeid = '2022'
or if you use EntitiyFramework
this query return equels R_ID records then set Employee_details condition
var res = db.Id_details.Join(db.Employee_details, x => x.R_ID , y => y.R_ID , (x, y) => x).ToList();
or you can select any R_ID in containsList
var conditionList =db.Employee_details.where(p=>p.Employeeid.equals('2022')).select(p=>p.R_ID);
then set condition using conditionList search
db.Id_details.where(p=> conditionList.contains(p.R_ID)).select ( ... );
I have two classes:
public class classA
{
public string keyA {get; set;}
public string propAA {get; set;}
public string propAB {get; set;}
public string propAC {get; set;}
}
public class classB
{
public string keyB {get; set;}
public string foreignkeyA {get; set;}
public string propBA {get; set;}
public string propBB {get; set;}
public string propBC {get; set;}
}
If I do a join between them..
var query = db.ClasseA.
Join(db.ClasseB, a => a.keyA, b => b.foreignkeyA,
(ca, cb) => new { ca, cb })
.Where(q => q.cb.propBC == 'some value')
.Select();
The result is a combination (list) of classA and classB objects.
But, I need that the result is only a list of class A.
Something like:
List<classA> query = db.ClasseA.
Join(db.ClasseB, a => a.keyA, b => b.foreignkeyA,
(ca, cb) => new { ca, cb })
.Where(q => q.cb.propBC == 'some value')
.Select(????);
How can I do that??
One example
var query = from obj1 in db.Object1
join obj2 in db.Object2 on obj1.key equals obj2.somekey
where obj1.something = "something"
select obj1
This will join the two but only bring back obj1 items
reference: https://msdn.microsoft.com/en-us/library/bb311040.aspx