Lets say that I have following tables:
Student(id(pk), name)
Class(id(pk), name)
StudentClass(id(pk), studentId(fk), classId(fk))
Imagine as follows:
Student table contains:
(1,"John"), (2, "Mike"), (3,"Josh")
Class table contains:
(1,"Geography"), (2, "Math")
StudentClass table contains:
(`1, 1, 1),(2,2,2),(3,3,2)
Lets now assume that I have a StudentClassDTO class which contains
List<string> StudentNames
string ClassName
How can I by using using LINQ query get data into StudentClassDTO? Any help appreciated.
var data = from sc in context.GetStudentClasses
join s in context.GetStudents on sc.StudentId equals s.Id
join c in context.GetClass on sc.ClassId equals c.Id
select new StudentClassDTO
{
}
so it gets name and classname 3 seperate ones but I need if their classes are same it should have to combine them where it will be just one classname and 2 different students. So it should be like {john, Geography} and {[Mike, Josh], Math}
Solution
from c in classes
join sc in studentClasses on c.Id equals sc.ClassId
join s in student on sc.StudentId equals s.StudentId
group s by new {c.Name} into g
select new StudentClassDTO
{
ClassName = g.Key.Name,
StudentNames = g.Select(a=>a.Name).ToList()
};
I use code like this all the time to accomplish what you're trying to do (untested and using the C# 7.3 syntax).
var xs =
from s in ctx.students
join cs in ctx.student_classes on cs.student_id equals s.student_id
join c in ctx.classes on c.class_id equals cs.class_id
select new
{
s, c
}
var memo = new Dictionary<int, StudentClassDTO>(); //the key is class_id
foreach(var x in xs)
{
if(!memo.Contains(x.c.class_id, out var #class))
memo.Add(x.c.class_id, #class = new StudentClassDTO(x.c.class_name));
#class.Accept(s.student_name);
}
sealed class StudentClassDTO
{
readonly List<string> student_names;
public string ClassName { get; }
public IEnumerable<string> StudentNames => student_names;
public(string class_name)
{
ClassName = class_name;
}
public void Accept(string name) => student_names.Add(name);
}
Using the LINQ group join operator, you can get a collection of matching student classes, however your query needs to start from the Class table since you want one StudentClassDTO per class. Unfortunately you have to nest the join from student classes to students (an EF navigation property may do better) so this may generate multiple queries.
var data = from c in context.GetClass
join sc in context.GetStudentClasses on c.Id equals sc.ClassId into scj
select new StudentClassDTO {
ClassName = c.Name,
StudentNames = (from sc in scj
join s in context.GetStudents on sc.StudentId equals s.Id
select s.Name).ToList()
};
i have 5 table from which i want to get some information using linq.i am using following query for reading data from data .
var query = (from GRD in _tblStudents.GetQueryable()
join apt in _tblApplicants.GetQueryable()
on GRD.ApplicantID equals apt.ApplicantID
join cls in _tblClasses.GetQueryable()
on GRD.CityID equals cls.ClassID
join prg in _tblPrograms.GetQueryable()
on cls.ProgramID equals prg.ProgramID
join city in _tblCities.GetQueryable()
on GRD.CityID equals city.CityID
where GRD.AcademicYearID == yearId && cls.ProgramID == programId
group apt by new{apt.Gender} into grouped
select new CityWiseStudentModel
{
CityName=city.CityName,
//'city' does not exist in the current context
Gender = grouped.Count(),
programName=prg.Program,
//'prg' does not exist in the current context
}
);
How i can get City name from city table and program name from prg table
group <--> by <--> into <--> is changed your scope to IGrouping<a,b>
My opinion is not only apt.Gender is your key but city.CityName and prg.Program
try this (or some similar):
group apt by new{apt.Gender, city, prg} into grouped
select new CityWiseStudentModel
{
CityName = grouped.Key.city.CityName,
Gender = grouped.Count(), //rename GenderCount
programName = grouped.Key.prg.Program,
// Gender = grouped.Key.Gender,
}
Remember that grouped will only hold the things you've grouped. If you only group adt, then city and prg will not be available in your select.
So you'll need to:
Include city and program into grouped (otherwise they're not available)
Access CityName and Program inside the grouped collection
Something like this should do the trick:
...
group new { apt, cls, prg, city } by new{apt.Gender} into grouped
select new CityWiseStudentModel
{
CityNames = grouped.Select(g => g.city.CityName),
...
programNames = grouped.Select(g => g.prg.Program)
}
Let'say there's a basic SQL db with the following structure:
Customers table
Customer ID
Name
PostCode
Email
Orders table
Order ID
Customer ID
Description
Items table
Item ID
Order ID
Name
Cost
So a customer can have many orders and an order can have many items.
What's the most appropriate LINQ query to run in order to achieve the following result where the Order Item Names result is a comma separated string:
Customer Name | Customer Email | Order Item Names
So effectively the Orders table is acting like a link table between the Customer and Order Items tables. I then want to concatenate the names of all items which are associated with all orders into a single string and return it in the same result as the customer details.
I've got the following working as expected which will return a result for each order item:
IQueryable<CustomerSearchResult> customerSearchResult = from customer in db.Customers
join order in db.Orders on customers.CustomerId equals order.CustomerId
join item in db.OrderItems on order.OrderId equals item.OrderId
where customerId.Equals(userId)
select new CustomerSearchResult {
customerName = customer.Name,
customerEmail = customer.Email,
itemName = item.Name
};
EDIT 21st March 2014
There are some cases when there will be no associated OrderItems in which case the CustomerSearchResult should still be returned but with the ItemNames property empty.
After you have results
var query = from c in db.Customers
join o in db.Orders on c.CustomerId equals o.CustomerId
join i in db.OrderItems on o.OrderId equals i.OrderId
where c.CustomerId == userId
select new {
CustomerName = c.Name,
CustomerEmail = c.Email,
ItemName = i.Name
};
Group them and concatenate item names:
var result = from r in query.AsEnumerable()
group r by new { r.CustomerName, r.CustomerEmail } into g
select new CustomerSearchResult {
CustomerName = g.Key.CustomerName,
CustomerEmail = g.Key.CustomerEmail,
ItemNames = String.Join(",", g.Select(r => r.ItemName))
};
You should do names concatenation in-memory, because EF will not be able to translate it into SQL.
I have two result sets from database into object and when I inner join them, it returns cross join. Following is my code:
var nw = new NorthwindEntities();
var employee1 = (from emp in nw.Employees
join ord in nw.Orders on emp.EmployeeID equals ord.EmployeeID
where emp.EmployeeID == 5
select new
{
empID = emp.EmployeeID,
empName = emp.FirstName,
ordDate = ord.OrderDate
}).ToList();
var employee2 = (from emp in nw.Employees
join ord in nw.Orders on emp.EmployeeID equals ord.EmployeeID
where emp.EmployeeID == 5
select new
{
empID = emp.EmployeeID,
empName = emp.FirstName,
shpAddress = ord.ShipAddress
}).ToList();
var employee = from e1 in employee1
join e2 in employee2 on new { e1.empID } equals new { e2.empID }
select new
{
empID = e1.empID,
empName = e1.empName,
ordDate = e1.ordDate,
orAdd = e2.shpAddress
};
gvAll.DataSource = employee.ToList();
Now when I join employee1 and employee2 into employee, I get a cross join of those data set. Any help would be highly appreciated.
My main target behind this is to have data returned from database stored in some place temporarily so I can work it with later with other data retrieved from database.
employee1 and employee2 uses same query for employee id (5) except employee1 has empID, empName and OrderDate where as employee2 has empID, empName and shipAddress. Now I want empID, empName, OrderDate and ShipAddress out of employee1 and employee2 in employee. empID is common in both but I am getting a cross join. Thank you.
In employee1 and employee2 you will aslo need the OrderId column to and then join them by both cos you need to get an equal unique key on both tables.
and from what you wrote I think that empID is not unique in either tables cos you can have more orders for same empID
So first you need a unique key on both employee1 and employee2 then you can join on that.
The result returned from function is logically correct, it is not a cross join. empID has some duplicate row in some table so, data is looking like a cross join.
Use distinct then it will return your desired output.
Question:
I'm trying to use PetaPoco to join more than four tables to populate an object of type A with five members of type B (very similiar to this question: https://stackoverflow.com/a/11275334/296296). From what I've read it would be possible to use this syntax:
var result = db.Query<Investment>(new System.Type[] { typeof(Investment), typeof(Person), typeof(Person), typeof(Person), typeof(Person), typeof(Person) }, null, sql, null).FirstOrDefault();
Where the sql is:
SELECT Investment.*, p1.*, p2.*, p3.*, p4.*, p5.* FROM Investment
INNER JOIN People p1 ON Investment.OwnerID = p1.Id
INNER JOIN People p2 ON Investment.ITOwnerID = p2.Id
INNER JOIN People p3 ON Investment.InformationOwnerId = p3.Id
INNER JOIN People p4 ON Investment.MaintenanceLeaderId = p4.Id
INNER JOIN People p5 ON Investment.MaintenanceLeaderITId = p5.Id
WHERE (Investment.Id = #0)
But it just gives me the following error:
Can't auto join Person as Investment has more than one property of type Person
Anyone who had the same problem or could help in any way?
Background:
I have the following (simplified) database tables:
Investment
--------------
Id
Name
OwnerId
ITOwnerId
InformationOwnerId
MaintenanceLeaderId
MaintenanceLeaderITId
People
--------------
Id
Name
The classes I would like to map these database tables to are:
Public class Investment {
public int Id
public string Name
public Person Owner
public Person ITOwner
public Person InformationOwner
public Person MaintenanceLeader
public Person MaintenanceLeaderIT
}
Public class Person {
public int Id
public string Name
}
In order to do so, I need to join the People table for every Person-type in the Investment class and map them to the corresponding property as instances of the type Person.
The only way you could possibly do this is create your own callback (instead of letting it fall back to the self discovering callback thats currently being invoked) where you wire up each Person object on the Investment object.
Take a look at http://www.toptensoftware.com/Articles/115/PetaPoco-Mapping-One-to-Many-and-Many-to-One-Relationships
why can't you do this :
/////SQL CODE for testing the selects
DECLARE #People TABLE (Id INT, Name VARCHAR(50))
INSERT INTO #People (Id,Name)
SELECT 1,' John Smith' UNION ALL
SELECT 2,'Albert Lee' UNION ALL
SELECT 3,'Christina Wetherbe' UNION ALL
SELECT 4,'Alice Cany' UNION ALL
SELECT 5,'Jim Blabery' UNION ALL
SELECT 6,'Octaviose Mayflower' UNION ALL
SELECT 7,'Sandra Lee M' UNION ALL
SELECT 8,'Some test user' UNION ALL
SELECT 9,'Some test user 2' UNION ALL
SELECT 10,'Some test user 3' UNION ALL
SELECT 11,'Some test user 4'
DECLARE #Investment TABLE (
Id INT,
Name VARCHAR(50),
OwnerId INT,
ITOwnerId INT,
InformationOwnerId INT,
MaintenanceLeaderId INT,
MaintenanceLeaderITId INT
)
INSERT INTO #Investment(Id,Name,OwnerId,ITOwnerId,InformationOwnerId,MaintenanceLeaderId,MaintenanceLeaderITId)
SELECT 1,'INVESTMENT 1',1,2,1,3,4 UNION ALL
SELECT 2,'INVESTMENT 2',1,3,2,3,2 UNION ALL
SELECT 3,'INVESTMENT 3',3,1,3,3,4 UNION ALL
SELECT 4,'INVESTMENT 4',5,4,4,2,3 UNION ALL
SELECT 5,'INVESTMENT 5',6,5,5,7,6 UNION ALL
SELECT 6,'INVESTMENT 6',8,6,6,7,8 UNION ALL
SELECT 7,'INVESTMENT 7',9,8,7,4,5 UNION ALL
SELECT 8,'INVESTMENT 8',11,8,8,6,11 UNION ALL
SELECT 9,'INVESTMENT 9',10,9,9,10,9
--SELECT * FROM #People
--SELECT * FROM #Investment
-- THIS IS YOUR SELECT STATEMENT to be uses in PetaPoco call
SELECT unpv.Id,unpv.Name, unpv.INVTYPE,unpv.PersonId,p.Name FROM #Investment
UNPIVOT(PersonId for INVTYPE in (OwnerId,ITOwnerId,InformationOwnerId,MaintenanceLeaderId,MaintenanceLeaderITId))unpv
join #People p on unpv.PersonId = p.Id
order by INVTYPE, PersonId
Then C# code as
1 - Extend your Investment POCO to have 2 more columns, INVTYPE and PersonId. Just create new object in the same name-space. Like so:
public partial class Investment
{
[ResultColumn]
public string INVTYPE { set; get; }
[ResultColumn]
public int PersonId { set; get; }
}
2 - create viewclass InvestmentView (you can use your Investment class for that, just rename it)
var myInvestmentView = new InvestmentView
{
Id = result.Id,
Name = result.Name,
Owner = new Person{ Id = result.firstOrDefault(o => o.INVTYPE.Equals("OwnerId")).PersonId, Name = result.firstOrDefault(o=> o.INVTYPE.Equals("OwnerId")).PersonName},
ITOwner = new Person{ Id = result.firstOrDefault(o => o.INVTYPE.Equals("ITOwnerId")).PersonId, Name = result.firstOrDefault(o=> o.INVTYPE.Equals("ITOwnerId")).PersonName},
InformationOwner = new Person{ Id = result.firstOrDefault(o => o.INVTYPE.Equals("InformationOwnerId")).PersonId, Name = result.firstOrDefault(o=> o.INVTYPE.Equals("InformationOwnerId")).PersonName},
MaintenanceLeader = new Person{ Id = result.firstOrDefault(o => o.INVTYPE.Equals("MaintenanceLeaderId")).PersonId, Name = result.firstOrDefault(o=> o.INVTYPE.Equals("MaintenanceLeaderId")).PersonName},
MaintenanceLeaderIT = new Person{ Id = result.firstOrDefault(o => o.INVTYPE.Equals("MaintenanceLeaderITId")).PersonId, Name = result.firstOrDefault(o=> o.INVTYPE.Equals("MaintenanceLeaderITId")).PersonName}
}
3 - populate the class with data from the list returned
using (var data = new Database(Config.MainDbConnectionName))
{
var result = data.Fetch<Investment,People>(
Sql.Builder
.Append("SELECT unpv.Id,unpv.Name, unpv.INVTYPE,unpv.PersonId,p.name as PersonName FROM Investment")
.Append(" UNPIVOT(PersonId for INVTYPE in (OwnerId,ITOwnerId,InformationOwnerId,MaintenanceLeaderId,MaintenanceLeaderITId)) unpv")
.Append(" JOIN People p on unpv.PersonId = p.Id")
.Append(" WHERE (Investment.Id = #0)",InvId)
.Append(" ORDER BY INVTYPE, PersonId")
);
}
this way the only class that will need the special treatment will be Investment class as you would need to process the data backwards from InvestmentView into flat structure of POCO.