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.
Related
I have this SQL query that successfully returns the desired result.
Basically for the given NameId there are multiple unique PlaceId.
SQL query:
Select
NameId, PlaceId
From
db_schemaA_tableA a
Left Join
db_schemaB_tableB b On a.Id = b.NameId
Left Join
db_schemaC_tableC c On a.ItemId = c.ItemId
Where
a.Id = 'C330ads'
NOTE: NameId and PlaceId are of datatype GUID
SQL result:
NameId | PlaceId
---------+----------
C330ads | 705ddf
C330ads | 618rre
In C# this is what I have
Entity class
public class Name
{
public Guid NameId {get; set:}
public Guid PlaceId {get; set;}
}
Class with DB connection method
public List<Name> GetNames(Guid someId)
{
SqlConnection connection = new SqlConnection();
var result = new List<Name>();
string query = #"Select NameId, PlaceId from db_schemaA_tableA a
left join db_schemaB_tableB b on a.Id = b.NameId
left join db_schemaC_tableC c on a.ItemId = c.ItemId
where a.Id= #NameId";
//HOW CAN I GET ALL PlaceIds ?
using(connection)
{
result = connection.Query<Name>(query, new {NameId = someId});
}
return result;
}
How can I get all the corresponding PlaceId for the given NameId in C#?
Thank you
I think you are trying to use Dapper. you can do it in this way.
Add Dapper package if not already added.
using Dapper;
using (var connection = new SqlConnection(connectionString))
{
CommandDefinition command = new CommandDefinition(query, null, null, null, CommandType.Text);
result = connection.Query<List<Name>>(command).ToList();
}
This wasn't a Dapper issue, as I initially thought. It was something much simpler.
I had the columns NameId and PlaceId in sql as such:
NameId | PlaceId
-------------------
C330ads | 705ddf
C330ads | 618rre
In C#, I had to provide the variable/variables I needed to map values to
public Guid Name {get; set;}
public Guid Place{get; set;}
So in my query I had to select both variables:
Select NameId as Name, PlaceId as Place from db_schemaA_tableA ...
I have an application using which the students can select the colleges they wish to join, in the application we currently give option to select max of 3 colleges and we say 1st preferred, 2nd preferred and 3rd preferred, we have a stored procedure which returns the data and the columns of that result set are as below.
UserId | FirstName | LastName | Email | Mobile | City |
FirstPreferredCollegeId | FirstPreferredCollegeName |
FirstPreferredCollegeGrade | FirstPreferredCollegePincode |
SecondPreferredCollegeId | SecondPreferredCollegeName |
SecondPreferredCollegeGrade | SecondPreferredCollegePincode |
ThirdPreferredCollegeId | ThirdPreferredCollegeName |
ThirdPreferredCollegeGrade | ThirdPreferredCollegePincode |
If we look at the above result set UserId, FirstName, LastName, Email, Mobile, City are unique where are PreferredCollegeId, PreferredCollegeName, PreferredCollegeGrade, PreferredCollegePinCode are repeating thrice.
My c# model looks like below.
Public Class User
{
Public int UserId {get;set;}
Public string FirstName {get;set;}
public string LastName {get;set;}
public string Email {get;set;}
public string Mobile {get;set;}
public string City {get;set;}
public List<Choice> Choices {get;set;}
}
public class Choice
{
public string PreferredCollegeId {get;set;}
public string PreferredCollegeName {get;set;}
public string PreferredCollegeGrade {get;set;}
public string PreferredCollegePincode {get;set;}
}
Now ADO.NET code looks like below, reader is SqlDataReader object.
objUser.UserId = (string)reader["UserId"];
objUser.FirstName = (string)reader["FirstName"];
objUser.LastName = (string)reader["LastName"];
objUser.Email = (string)reader["Email"];
objUser.Mobile = (string)reader["Mobile"];
objUser.City = (string)reader["City"];
/* Here I need to loop through reader and get College details
Choice ch = new Choice();
Now I need to loop through the SqlDataReader and get the PreferredCollegeId, PreferredCollegeName, PreferredCollegeGrade, PreferredCollegePincode and dynamically create an object of Choice class and bind these values to the Choice object - how do I achieve this?
Try something like this:
var prefixes = new List<string> {"First", "Second", "Third"};
objUser.Choices = new List<Choice>();
foreach(var prefix in prefixes)
{
objUser.Choices.Add(new Choice {
PreferredCollegeId= (string) reader[prefix + "PreferredCollegeId"],
PreferredCollegeName = (string) reader[prefix + "PreferredCollegeName"],
PreferredCollegeGrade = (string) reader[prefix + "PreferredCollegeGrade"],
PreferredCollegePincode = (string) reader[prefix + "PreferredCollegePincode"]
});
}
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 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.
Consider i have a datatable retrieved from oracle database in the following format
SNo. | Product | Cost
-------------------------------------------------
1 | colgate,closeup,pepsodent | 50
2 | rin,surf | 100
I need to change this into the following format using linq.Need to separate the product column with the help of comma by keeping the other columns same.
SNo. | Product | Cost
-------------------------------------
1 | colgate | 50
1 | closeup | 50
1 | pepsodent | 50
2 | rin | 100
2 | surf | 100
Please try this:
List<Product> uncompressedList = compressedProducts
.SelectMany(singleProduct => singleProduct.ProductName
.Split(',')
.Select(singleProductName => new Product
{
SNo = singleProduct.SNo,
ProductName = singleProductName,
Cost = singleProduct.Cost
}))
.ToList();
EDIT:
Product class is defined as follows:
public class Product
{
public Int32 SNo { get; set; }
public String ProductName { get; set; }
public Int32 Cost { get; set; }
}
and compressedProducts is just the initial list of products from your first example.
I know that this is not a single-line Linq statement but, try this.
var output = new List<Product>();
foreach (var p in SqlResult)
{
var products = p.Product.Split(',');
output.AddRange(products.Select(product => new Product { SNo = p.SNo, ProductName = product, Cost = p.Cost }));
}
By-the-way, SqlResult is your results set from the database.
This appears to work from my limited testing...
var test = p1.Product.Split(',').Select(p => new { product = p }).Select(r => new { p1.SNo, r.product, p1.Cost, })
This is only for a single line, but can easily be explanded to include mutiple lines....
I would favour a simple foreach because it is known than LINQ is slower that regular looping statements, but if you really want to go that path you could use something like which to you, maybe be easier to read:
Given
class ProductOracle
{
public int SNo { get; set; }
public string Product { get; set; }
public decimal Cost { get; set; }
}
class ProductEntity
{
public int SNo { get; set; }
public string Product { get; set; }
public decimal Cost { get; set; }
}
Execution
var entities = new List<ProductOracle>
{
new ProductOracle{SNo=1,Product="colgate,closeup,pepsodent", Cost=50},
new ProductOracle{SNo=2,Product="rin,surf", Cost=100}
};
With
var products = new List<ProductEntity>();
entities.ForEach(element =>
{
element.Product.Split(',').ToList().ForEach(product =>
{
products.Add(new ProductEntity { SNo = element.SNo, Product = product, Cost = element.Cost });
});
});
Or
var products = entities.SelectMany(element =>
{
var ProductEntities = new List<ProductEntity>();
element.Product.Split(',').ToList().ForEach(product =>
{
ProductEntities.Add(new ProductEntity { SNo = element.SNo, Product = product, Cost = element.Cost });
});
return ProductEntities;
});