Querying into a complex object with Dapper - c#

I have a Customer class with the following properties:
public int Id { get; set; }
public string Name { get; set; }
public int AddressId { get; set; }
public Address Address { get; set; }
My goal is to write a Dapper query that will use an Inner Join to populate the entire Address property within each Customer that is returned.
Here is what I have and it is working but I am wondering if this is the cleanest/simplest way to do it:
StringBuilder sql = new StringBuilder();
using (var conn = GetOpenConnection())
{
sql.AppendLine("SELECT c.Id, c.Name, c.AddressId, a.Address1, a.Address2, a.City, a.State, a.ZipCode ");
sql.AppendLine("FROM Customer c ");
sql.AppendLine("INNER JOIN Address a ON c.AddressId = a.Id ");
return conn.Query<Customer, Address, Customer>(
sql.ToString(),
(customer, address) => {
customer.Address= address;
return userRole;
},
splitOn: "AddressId"
).ToList();
}
I have some concern about adding another property such as:
public Contact Contact { get; set; }
I am not sure how I would switch the syntax above to populate both Address and Contact.

I have coded using Dapper version 1.40 and I have written queries like the way below, I haven't got any issues to populate mote more than one object, but I have faced a limit of 8 different classes those I can map in a query.
public class Customer {
public int Id { get; set; }
public string Name { get; set; }
public int AddressId { get; set; }
public int ContactId { get; set; }
public Address Address { get; set; }
public Contact Contact { get; set; }
}
public class Address {
public int Id { get; set; }
public string Address1 {get;set;}
public string Address2 {get;set;}
public string City {get;set;}
public string State {get;set;}
public int ZipCode {get;set;}
public IEnumerable<Customer> Customer {get;set;}
}
public class Contact {
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Customer> Customer {get;set;}
}
using (var conn = GetOpenConnection())
{
var query = _contextDapper
.Query<Customer, Address, Contact, Customer>($#"
SELECT c.Id, c.Name,
c.AddressId, a.Id, a.Address1, a.Address2, a.City, a.State, a.ZipCode,
c.ContactId, ct.Id, ct.Name
FROM Customer c
INNER JOIN Address a ON a.Id = c.AddressId
INNER JOIN Contact ct ON ct.Id = c.ContactId",
(c, a, ct) =>
{
c.LogType = a;
c.Contact = ct;
return c;
}, splitOn: "AddressId, ContactId")
.AsQueryable();
return query.ToList();
}

Take a look in my example with a big query, note that Each Query line It's a different object.
public List<Appointment> GetList(int id)
{
List<Appointment> ret;
using (var db = new SqlConnection(connstring))
{
const string sql = #"SELECT AP.[Id], AP.Diagnostics, AP.Sintomns, AP.Prescription, AP.DoctorReport, AP.AddressId,
AD.Id, AD.Street, AD.City, AD.State, AD.Country, AD.ZIP, Ad.Complement,
D.Id, D.Bio, d.CRMNumber, D.CRMNumber, D.CRMState,
P.Id,
S.Id, S.Name,
MR.Id, MR.Alergies, MR.BloodType, MR.DtRegister, Mr.HealthyProblems, MR.HealthyProblems, MR.Height, MR.MedicalInsuranceNumber, MR.MedicalInsuranceUserName, MR.Medications, MR.Weight,
MI.Id, MI.Name
from Appointment AP
inner join [Address] AD on AD.Id = AP.AddressId
inner join Doctor D on D.Id = AP.DoctorId
inner join Patient P on P.Id = AP.PatientId
left join Speciality S on S.Id = D.IDEspeciality
left join MedicalRecord MR on MR.Id = P.MedicalRecordId
left join MedicalInsurance MI on MI.Id = MR.MedicalInsuranceId
where AP.Id = #Id
order by AP.Id desc";
ret = db.Query<Appointment, Address, Doctor, Patient, Speciality, MedicalRecord, MedicalInsurance, Appointment>(sql,
(appointment, address, doctor, patient, speciality, medicalrecord, medicalinsurance) =>
{
appointment.Address = address;
appointment.Doctor = doctor;
appointment.Patient = patient;
appointment.Doctor.Speciality = speciality;
appointment.Patient.MedicalRecord = medicalrecord;
appointment.Patient.MedicalRecord.MedicalInsurance = medicalinsurance;
return appointment;
}, new { Id = id }, splitOn: "Id, Id, Id, Id, Id, Id").ToList();
}
return ret;
}

Related

How can I join multiple tables including a join-table (auto-generated by Entity Framework) using LINQ

I need to do a query with multiple joins which include a join-table that was generated from a many-to-many relation. It works if I query the db directly, but I need to do the query in one of my controllers and since I don't have a model for the join-table I'm not able to do a simple join with a DbSet and I've been stuck for hours trying to find out how it works.
This are my Tables in the Database:
Exams
(PK) Id
(FK) ClassId
Relation: * to 1
Classes
(PK) Id
Relation: 1 to *
ClassesStudents
(PK) ClassId
(PK) StudentId
Relation: * to 1
Student
(PK) Id
(FK) PersonId
Relation: 1 to 1
Person
(PK) Id
FirstName
LastName
Those are my models:
public class Exam
{
public int Id { get; set; }
public short ClassId { get; set; }
public Class Class { get; set; }
}
public class Class
{
public Class()
{
this.Students = new HashSet<Student>();
}
public int Id { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
public class Student
{
public Student()
{
this.Classes = new HashSet<Class>();
}
public int Id { get; set; }
public virtual ICollection<Class> Classes { get; set; }
}
public class Person
{
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
I am trying go get this Query...
SELECT stu.Id, peo.FirstName, peo.LastName
FROM Exams AS exa
JOIN Classes AS cla
ON cla.Id = exa.ClassId
JOIN ClassesStudents AS clastu
ON clastu.ClassId = cla.Id
JOIN Students AS stu
ON stu.Id = clastu.StudentId
JOIN People AS peo
ON peo.Id = stu.PersonId
WHERE exa.Id = 1;
... to work somewhat like that in my Controller:
public ActionResult CreateMark(int id)
{
var students = (from exams in _context.Exams
join classes in _context.Classes
on exams.ClassId equals classes.Id
join classesstudents in _context.ClassesStudents // that
on classes.Id equals classesstudents.ClassId // part
join students in _context.Students // doesn't
on classesstudents.StudentId equals students.Id // work
join people in _context.People
on students.PersonId equals people.Id
where exams.Id == id
select new
{
Id = students.Id,
FullName = people.FirstName + " " + people.LastName
}).ToList();
I'd be extremely thankful for any help!
EDIT:
I managed to make it work with the following code, but is there a way to do it with less code in just one query?
public ActionResult Create(int id)
{
var class = (from exa in _context.Exams
join cla in _context.Classes
on exa.ClassId equals cla.Id
where exa.Id == id
select new
{
ClassId = cla.Id,
Students = cla.Students
}).ToList();
var classWithStudents= (from cla in class
from stu in cla.Students
select new
{
Id = stu.Id,
PersonId = stu.PersonId
}).ToList();
var students = (from stu in classWithStudents
join peo in _context.People
on stu.PersonId equals peo.Id
select new FullNameStudentViewModel
{
Id = stu.Id,
FullName = peo.FirstName + " " + peo.LastName
}).ToList();
Use the following query:
var query =
from exams in _context.Exams
from students in exams.Class.Students
join people in _context.People on students.PersonId equals people.Id
where exams.Id == id
select new
{
Id = students.Id,
FullName = people.FirstName + " " + people.LastName
};

"MultiLevels" Mapping Dapper using SplitOn

Most of examples/question just introduce solutions to map "only one" level of the query using split on like this:
var sql = "SELECT P.Id, P.FirstName, P.LastName, " +
"A.Id AS AddressId, A.StreetNumber, A.StreetName, A.City, A.State " +
"FROM People P INNER JOIN Addresses A ON A.AddressId = P.AddressId; ";
db.Query<Person, Address, Person>( sql, (person, address) => {
person.Address = address;
return person; }, splitOn: "AddressId" ).ToList();
I have a query like this one (just an example):
Select * from Country C
inner join State S
on C.CountryId = S.CountryId
inner join City Ct
on S.StateId = Ct.StateId
How could I map it using dapper to my Model/Class?
There is no out of box solution for your needs in Dapper or its extensions. Dapper maps every single row in result set separately. So you need some extra mapping in order to do something like what you want. You can do it manually after getting results of Query with multiple splitOn. Or use some mapping tool. Please, consider this question with various answers. Adapted to your case the solution(with Slapper.Automapper mapping) would be:
[Test]
public async Task MultipleSplitOn()
{
// Arrange
using (var conn =new SqlConnection("Data Source=YourDb"))
{
await conn.OpenAsync();
var sql = #"SELECT TOP 10 c.[Id] as CountryId
,c.[Name]
,s.[Id] as States_StateId
,s.[Name] as States_Name
,ct.[Id] as States_Cities_CityId
,ct.[Name] as States_Cities_Name
FROM Country c
JOIN State s ON s.[CountryId] = c.[Id]
JOIN City ct ON ct.[StateId] = s.[Id] ";
// Act
dynamic result = await conn.QueryAsync<dynamic>(sql);
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(Country), new [] { "CountryId" });
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(State), new [] { "StateId" });
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(City), new [] { "CityId" });
var countries = (Slapper.AutoMapper.MapDynamic<Country>(result) as IEnumerable<Country>).ToList();
//Assert
Assert.IsNotEmpty(countries);
foreach (var country in countries)
{
Assert.IsNotEmpty(country.States);
foreach (var state in country.States)
{
Assert.IsNotEmpty(state.Cities);
}
}
}
}
public class Country
{
public int CountryId { get; set; }
public string Name { get; set; }
public List<State> States { get; set; }
}
public class State
{
public int StateId { get; set; }
public string Name { get; set; }
public List<City> Cities { get; set; }
}
public class City
{
public int CityId { get; set; }
public string Name { get; set; }
}

LINQ - Groupy by for complex xml

I have following XML:-
<customer>
<id>ALFKI</id>
<name>Alfreds Futterkiste</name>
<address>Obere Str. 57</address>
<city>Berlin</city>
<postalcode>12209</postalcode>
<country>Germany</country>
<phone>030-0074321</phone>
<fax>030-0076545</fax>
<orders>
<order>
<id>10643</id>
<orderdate>1997-08-25T00:00:00</orderdate>
<total>814.50</total>
</order>
<order>
<id>10692</id>
<orderdate>1997-10-03T00:00:00</orderdate>
<total>878.00</total>
</order>
</orders>
</customer>
I want to fetch data in following format:-
Country Name: Germany
City Name : Berlin
orders: orderid1, orderid2...
City Name: Mannheim
orders: orderid1, orderid2 etc..
i.e. for each country, its respective cities and for that city all the orderids.
I am using the below query by which i am able to group country and its cities but i am not able to fetch the orders for that country:-
List<Customer> Customers = GetCustomerData();
var query = (from cust in Customers
from ord in cust.Orders
group cust by cust.Country into CountryGroup
select new
{
CountryName = CountryGroup.Key,
CityGroup = (from c in CountryGroup
where c.Country == CountryGroup.Key
group c by c.City into CityGroup
select new
{
CityName = CityGroup.Key
})
});
Code for GetCustomerData:-
CustomerList = (from e in XDocument.Load(#"D:\Console Application\LINQDemo\GroupingOperators\GroupingOperators\XMLFiles\Customers.xml").Root.Elements("customer")
select new Customer
{
CustomerID = (string)e.Element("id"),
CustomerName = (string)e.Element("name"),
Address = (string)e.Element("address"),
City = (string)e.Element("city"),
Region = (string)e.Element("region"),
PostalCode = (string)e.Element("postalcode"),
Country = (string)e.Element("country"),
PhoneNo = (string)e.Element("phone"),
Fax = (string)e.Element("fax"),
Orders = (from o in e.Element("orders").Elements("order")
select new Order
{
OrderID = (int)o.Element("id"),
OrderDate = (DateTime)o.Element("orderdate"),
OrderTotal = (decimal)o.Element("total")
}).ToArray()
}).ToList();
Please Help!
TIA.
If you do correct parsing of customers, then your query should look like:
var query = from cust in Customers
group cust by new { cust.Country, cust.City } into g
select new
{
CountryName = g.Key.Country,
CityGroup = g.Key.City,
Orders = g.SelectMany(c => c.Orders)
};
And here is parsing (just in case):
private List<Customer> GetCustomerData()
{
XDocument xdoc = XDocument.Load(path_to_xml);
return xdoc.Descendants("customer")
.Select(c => new Customer()
{
Id = (string)c.Element("id"),
Name = (string)c.Element("name"),
Address = (string)c.Element("address"),
Country = (string)c.Element("country"),
City = (string)c.Element("city"),
Orders = c.Descendants("order")
.Select(o => new Order()
{
Id = (int)o.Element("id"),
Date = (DateTime)o.Element("orderdate"),
Total = (decimal)o.Element("total")
}).ToList()
}).ToList();
}
Where I used following classes (without postal code, fax and phone)
public class Customer
{
public string Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Country { get; set; }
public string City { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public DateTime Date { get; set; }
public decimal Total { get; set; }
}

Get Linq Subquery into a User Defined Type

I have a linq query, which is further having a subquery, I want to store the result of that query into a user defined type, my query is
var val = (from emp in Employees
join dept in Departments
on emp.EmployeeID equals dept.EmployeeID
select new Hello
{
EmployeeID = emp.EmployeeID
Spaces = (from order in Orders
join space in SpaceTypes
on order.OrderSpaceTypeID equals space.OrderSpaceTypeID
where order.EmployeeID == emp.EmployeeID group new { order, space } by new { order.OrderSpaceTypeID, space.SpaceTypeCode } into g
select new
{
ID = g.Key.SpaceTypeID,
Code = g.Key.SpaceTypeCode,
Count = g.Count()
})
}).ToList();
Definition for my Hello class is
public class Hello
{
public IEnumerable<World> Spaces { get; set; }
public int PassengerTripID { get; set; }
}
Definition for my World class is
public class World
{
public int ID { get; set; }
public string Code { get; set; }
public int Count { get; set; }
}
You are creating anonymous object but you need to specify type name World
select new World
{
ID = g.Key.SpaceTypeID,
Code = g.Key.SpaceTypeCode,
Count = g.Count()
})

Populating child property with Entity Framework SqlQuery

Given the following POCO Code First Entities
public class Customer
{
public int CustomerId { get; set; }
public string CustomerTitle { get; set; }
public string CustomerFirstName { get; set; }
public string CustomerLastName { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class Order
{
public int OrderId { get; set; }
...
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
Using linq you can get the Orders filled in by using the Include property as in
var cust = (from cust in context.Customer
where cust.CustomerId == 1
select cust)
.Include(ord => ord.Orders)
.FirstOrDefault();
I am trying to get the same result using paramaterised sql, using
Customer co = context.Customer.SqlQuery(
#"select [Customer].[CustomerId],
...
[Order].[OrderId] AS [OrderId],
...
from Customer join Order on Customer.CustomerId = Order.CustomerId where Customer.CustomerId = #custid", sqlParm)
.FirstOrDefault();
How do I get the Orders in co.Orders to be populated with the above command, it seems like I can't use the Include statement with SqlQuery. This is a very simplified example for illustrative purposes only, the actual queries will be more involved.
This is not possible at all. Direct SQL execution doesn't offer filling of navigation properties and you really can't use Include. You must execute two separate SQL queries to get Cutomer and her Orders.
I have used the following class structure as workaround:
public class Customer
{
public int CustomerId { get; set; }
}
public class Order
{
public Customer Customer { get; set; }
private int _customerId;
private int CustomerId { get { return _customerId; } set { Customer.CustomerId = _customerId = value; } }
public Order()
{
Customer = new Customer();
}
}
In this case you don't need to run the query twice, and the following query would give the order with customer:
db.Database.SqlQuery<Order>(
"select cu.CustomerId, ord.OrderId from Orders ord join Customer cu on cu.CustomerId=ord.CustomerId")
.ToList();
What worked for me was to access the related member before the using is closed.
public static Customer GetCustomer (int custid)
{
Customer co = null;
using (var context = new YourEntities())
{
// your code
co = context.Customer.SqlQuery(
#"select [Customer].[CustomerId],
...
[Order].[OrderId] AS [OrderId],
...
from Customer join Order on Customer.CustomerId = Order.CustomerId where Customer.CustomerId = #custid", sqlParm)
.FirstOrDefault();
// my addition
// cause lazy loading of Orders before closing the using
ICollection<Order> orders = co.Orders;
}
// can access co.Orders after return.
return (co);
}

Categories