LINQ - Groupy by for complex xml - c#

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

Related

C# How do you query an object with list properties by joining two tables

public class CategoryDomainModel
{
public string? _id { get; set; }
public string? RestaurantID { get; set; }
public string? CategoryName { get; set; }
public Enums.Status Status { get; set; }
}
public class MenuItemDomainModel
{
public string? _id { get; set; }
public string? CategoryID { get; set; }
public string? ImageLink { get; set; }
public string? ItemName { get; set; }
public string? ItemDescription { get; set; }
}
Imagine you have these two tables in mongodb and a category has many menus.
When you want to join the two tables and get all categories + menus by Restaurant ID with a result like this
public class CategoryAndMenusDomainModel
{
public string? _id { get; set; }
public string? RestaurantID { get; set; }
public string? CategoryName { get; set; }
public Enums.Status Status { get; set; }
public List<MenuItemDomainModel>? Menus { get; set; }
}
How do you go about it?
Ive tried:
var categoryCollection = database.GetCollection<CategoryDomainModel>("Categories");
var menuCollection = database.GetCollection<MenuItemDomainModel>("Menus");
var categoriesAndMenus = (from b in categoryCollection.AsQueryable()
join c in menuCollection.AsQueryable()
on b._id equals c.CategoryID
where b.RestaurantID == restautantID
select new CategoryAndMenusDomainModel
{
_id = b._id,
CategoryName = b.CategoryName,
RestaurantID = b.RestaurantID,
Menus = new List<MenuItemDomainModel>
{
new MenuItemDomainModel
{
_id = c._id,
ItemName = c.ItemName,
ItemDescription = c.ItemDescription
}
}
}).ToList();
But its throwing an exception:
"$project or $group does not support new List`1()
Consider this workaround:
var categoriesAndMenus = (from b in categoryCollection.AsQueryable()
join c in menuCollection
on b._id equals c.CategoryID
where b.RestaurantID == restautantID
select new
{
_id = b._id,
CategoryName = b.CategoryName,
RestaurantID = b.RestaurantID,
C_id = c._id,
C_CategoryName = c.ItemName,
C_ItemDescription = c.ItemDescription
}
)
.ToList()
// below step is client side tranformation, but all required data is already received from the server, so it's just minor projection
.Select(i => new CategoryAndMenusDomainModel
{
_id = i._id,
CategoryName = i.CategoryName,
RestaurantID = i.RestaurantID,
Menus = new List<MenuItemDomainModel>
{
new MenuItemDomainModel
{
_id = i._id,
ItemName = i.CategoryName,
ItemDescription = i.C_ItemDescription
}
}
});
The generated MQL query for this case will be:
{
"aggregate": "Categories",
"pipeline": [
{
"$lookup":
{
"from": "Menus",
"localField": "_id",
"foreignField": "CategoryID",
"as": "c"
}
},
{ "$unwind": "$c" },
{ "$match": { "RestaurantID": "1" } },
{
"$project":
{
"_id": "$_id",
"CategoryName": "$CategoryName",
"RestaurantID": "$RestaurantID",
"C_id": "$c._id",
"C_CategoryName": "$c.ItemName",
"C_ItemDescription": "$c.ItemDescription"
}
}
]
}
I'm 99% confident that client side projection can be moved to the server side, for example via a raw MQL query with $map, see MongoDB project into an array, but it will require more investigation
Linq translates the C# code into SQL. SQL doesn't know about C# lists, so it doesn't know how to format that. You need to do this in two steps: 1) get the data from the database in flat rows, then 2) in C#, put in into the structure you want.
Here I've used the standard Northwind database, but you should be able to adapt it to your data.
var rows = (from c in Categories
join p in Products on c.CategoryID equals p.CategoryID
//where c.CategoryID == 1
select new
{
_id = c.CategoryID,
CategoryName = c.CategoryName,
RestaurantID = c.CategoryID,
menu_id = p.ProductID,
ItemName = p.ProductName,
ItemDescription = p.ProductName
}).ToList();
var categoriesAndMenus =
(from r in rows
group r by r._id into grp
let cat = grp.First()
select new
{
_id = cat._id,
cat.CategoryName,
cat.RestaurantID,
Menu = grp.Select(g =>new
{
_id = g.menu_id,
g.ItemName,
g.ItemDescription
} )
}).ToList();

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

Querying into a complex object with Dapper

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

A type that implements IEnumerable 'System.Collections.Generic.List`1' cannot be initialized in a LINQ to Entities query

I am trying to create object of some class inside the Linq query but gives me an error as set title of the question.
My query is:
List<oneViewModel> workOrderInfoList = (from abc in db.ABC
join customer in db.Customers on abc.CustomerId equals customer.CustomerId into customers
select new oneViewModel()
{
CustomerId = abc.CustomerId,
OrderNumber = workOrderInfo.OrderNumber,
OrderDate = abc.OrderDate,
SecondClassList = new List<SecondClass>(),
}).ToList();
I have define the list of class in as object inside oneViewModel.
public class ABC
{
public DateTime? WorkOrderDate { get; set; }
public long CustomerId { get; set; }
public string CustomerName { get; set; }
public List<SecondClass> SecondClassList { get; set; }
}
Initialise the secondClass List inside your ViewModel constructor:
Public oneViewModel()
{
SecondClassList = new List<SecondClass>();
)
Remember to remove the initialisation from the Linq query.
Edit
List<oneViewModel> workOrderInfoList = (from abc in db.ABC
join customer in db.Customers on abc.CustomerId equals customer.CustomerId into customers
select new oneViewModel()
{
CustomerId = abc.CustomerId,
OrderNumber = workOrderInfo.OrderNumber,
OrderDate = abc.OrderDate,
SecondClassList = abc.SecondClassList
}).ToList();
Edit 2
Your oneViewModel should look something like this:
public class oneViewModel
{
public oneViewModel
{
SecondClassList = new List<SecondClass>();
}
Public List<SecondClass> SecondClassList { get; set; }
}
The linq query should look like this:
List<oneViewModel> workOrderInfoList = (from abc in db.ABC
join customer in db.Customers on abc.CustomerId equals customer.CustomerId into customers
select new oneViewModel()
{
CustomerId = abc.CustomerId,
OrderNumber = workOrderInfo.OrderNumber,
OrderDate = abc.OrderDate
}).ToList();
Now that you will have a list of oneViewModel objects.
You need to execute query first and then initialize list, e.g.:
List<oneViewModel> workOrderInfoList = (from abc in db.ABC
join customer in db.Customers on abc.CustomerId equals customer.CustomerId into customers).ToList()
Select(n => new oneViewModel()
{
CustomerId = n.CustomerId,
OrderNumber = workOrderInfo.OrderNumber,
OrderDate = n.OrderDate,
SecondClassList = new List<SecondClass>(),
}).ToList();

Group by a many-to-many relashionship

I have two entities:
public class Category
{
// Primary properties
public int Id { get; set; }
public string Name { get; set; }
// Navigation properties
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
// Primary properties
public int Id { get; set; }
public string Name { get; set; }
// Navigation properties
public virtual ICollection<Category> Categories { get; set; }
}
Based on a set of Address Id's, I need to Group By all the addresses by Category, including the Address Count for each Address.
Example:
Category: {1,"WORKSHOP",{1,2}},{2,"DEALER",{1,3}}
Address: {1,"Paul Workshop and Dealer",{1,2}}
{2,"Joe Workshop",{1}}
{3,"Peter Dealer",{2}}
If I have the Address Id's 1 and 3, I want to get:
Categories - "WORKSHOP - Count: 1"
"DEALER - Count: 2"
If I have the Address Id's 1 and 2, I want to get: Category -
Categories - "WORKSHOP - Count: 2"
"DEALER - Count: 1"
So far I get this, but the group by is not working:
var groupedAddresses = from add in addressQuery
where addressIds.Contains(add.Id)
group add by new { Category_Id = add.Categories, Address_Id = add.Id };
var result = from add in groupedAddresses
group add by add.Id into final
join c in categoryQuery on final.Key equals c.Id
select new CategoryGetAllBySearchDto
{
Id = final.Key,
Name = c.Name,
SearchCount = final.Count()
};
Any idea?
Thanks.
int[] addressIds = { 1, 3 };
var query = from c in categoryQuery
let searchCount = c.Addresses.Count(a => addressIds.Contains(a.Id))
where searchCount > 0
select new CategoryGetAllBySearchDto{
Id = c.Id,
Name = c.Name,
SearchCount = searchCount
};

Categories