select TeamName, [Description], COUNT(u.UserId)
from Team t
left outer join [User] u on u.TeamId=t.TeamId
group by TeamName, Description, UserId
and here i have so far but cant able to do that.please help
var countUser = (from t in db.Teams
join u in db.Users on u.TeamId equals t.TeamId
group TeamName, Description, UserId by select
new
{
u.UserId
}).Count();
This should do it:
Teams.Join(Users.DefaultIfEmpty().
t => t.TeamId,
u => u.TeamId,
(t, u) => new { t.TeamName, t.Description, UserId = u == null ? null:(int?)u.UserId })
.GroupBy(x => x)
.Select(g => new { g.Key.TeamName, g.Key.Description, Count = g.Count() });
RePierre I'm going to steal part of your answer (+1) because I think I understand what OP is talking about, though the question text does not convey it.
You could do something like this:
// Model class for View
public class UsersPerTeamCount
{
public string TeamName { get; set; }
public string Description { get; set; }
public int Count { get; set; }
}
// ...
public ActionResult PlayersPerTeam()
{
var model = from t in db.Teams
join u in db.Users on t.TeamId equals u.TeamId into joinedRecords
select new UsersPerTeamCount()
{
Name = t.TeamName,
Description = t.Description,
PlayerCount = joinedRecords.Count()
};
return View(model);
}
As far as in OPs comment "please try to write as like..." that's just a difference in syntax, it doesn't really matter which way you write it - either fluent vs query syntax (at least i think it's called query syntax)
Related
I'm using Dapper to map my database content to EmployeeModel objects.
The mapping of properties is working, but the grouping at the end is still giving me trouble:
EmployeeModel takes List<PhoneModel> and List<EmployeeModel> as properties.
Items are grouped according to EmployeeID, however multiple email and phone results are returned as I haven't been able to find the syntax for doing so.
I've tried looping through the EmployeeIDs in the employeeList after it's been grouped by ID, before it's been grouped by ID, and while it's being grouped by ID.
var sql = #"
SELECT
e.id,
e.FirstName, e.LastName, e.Nickname,
em.id as ID, em.Address, em.Type,
jt.id as ID, jt.Name,
e.id as ID, p.Number, p.Type,
d.id as ID, d.Name,
es.id as ID, es.Name
FROM
dbo.Employees e
LEFT JOIN dbo.Emails em ON em.EmployeeID = e.id
LEFT JOIN dbo.JobTitles jt ON e.JobTitleID = jt.id
LEFT JOIN Phones p ON p.EmployeeID = e.id
LEFT JOIN dbo.Departments d ON e.DepartmentID = d.id
LEFT JOIN dbo.EmployeeStatus es ON e.StatusID = es.id
";
IEnumerable<EmailModel> emailsGrouped = new List<EmailModel>();
var employees = await connection
.QueryAsync<
EmployeeModel,EmailModel,TitleModel,
PhoneModel,DepartmentModel,StatusModel,
EmployeeModel>
(
sql,
( e, em, t, p, d, s ) =>
{
e.EmailList.Add(em);
e.JobTitle = t;
e.PhoneList.Add(p);
e.Department = d;
e.Status = s;
return e;
},
splitOn: "ID, ID, ID, ID, ID"
);
foreach (EmployeeModel emod in employees)
{
emod.EmailList.GroupBy(em => em.ID);
}
var result = employees
.GroupBy(e => e.ID)
.Select(g =>
{
var groupedEmployee = g.First();
groupedEmployee.EmailList = g.Select(e => e.EmailList.Single()).ToList();
groupedEmployee.PhoneList = g.Select(e => e.PhoneList.Single()).ToList();
return groupedEmployee;
});
return result.ToList();
Here is my Email definition, as requested. It's inside my EmployeeClass, so I've posted the whole thing.
public class EmployeeModel
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Nickname { get; set; }
public DepartmentModel Department { get; set; }
public TitleModel JobTitle { get; set; }
public DateTime HireDate { get; set; }
public StatusModel Status { get; set; }
public List<EmailModel> EmailList { get; set; } = new List<EmailModel>();
public List<PhoneModel> PhoneList { get; set; } = new List<PhoneModel>();
public List<RestrictionModel> RestrictionsList { get; set; } = new List<RestrictionModel>();
public List<CitationModel> CitationsList { get; set; } = new List<CitationModel>();
public List<CertificationModel> CertificationList { get; set; } = new List<CertificationModel>();
public string ListView
{
get
{
return $"{LastName}, {FirstName}";
}
}
public string ToEmailString()
{
IEnumerable<string> employeeEmailStrings = EmailList.Select(emmod => emmod.ToString());
string employeeEmailString = string.Join($"{Environment.NewLine}", employeeEmailStrings);
return $"{FirstName}, {LastName}: {Environment.NewLine} -{JobTitle.Name}- {Environment.NewLine}";
}
//IEnumerable<string> phoneStrings = PhoneList.Select(plistmod => plistmod.ToString());
//string phoneString = string.Join($"{Environment.NewLine}", phoneStrings);
public string ToCertificationString()
{
IEnumerable<string> certificationStrings = CertificationList.Select(clistmod => clistmod.ToString());
string certificationString = string.Join($"{Environment.NewLine}", certificationStrings);
return certificationString;
}
public class EmailModel
{
public int ID { get; set; }
public string Address { get; set; }
public string Type { get; set; }
public override string ToString()
{
return $"{Address} ({Type})";
}
}
public class PhoneModel
{
public int ID { get; set; }
public string Number { get; set; }
public string Type { get; set; }
public override string ToString()
{
return $"{Number} ({Type})";
}
}
}
}
What I'm trying now is to loop through the Emails in the EmployeeModel to create a new list of emails, and then set that new list as the EmployeeModel.List<EmailModel>.
So it looks like you're actually trying to load an object-graph (containing nodes of distinct types) from a database using SQL - and you're trying to do that using a single query.
That won't work. (Naïve, single-query) SQL is not suitable for querying object-graphs. This is why ORMs exist. However with some RDBMS-specific SQL extensions (e.g. T-SQL, PL/SQL, etc) to execute a query batch you can return an object-graph from a database.
The good news is that Dapper supports this scenario with QueryMultiple - however as far as I know it won't map collection properties, so you need to do that manually (so read on!)
(I note that Entity Framework, specifically, will generate single-SELECT queries that return redundant data in columns that represent lower-multiplicity data - this has its trade-offs but generally speaking separate queries can work faster overall with the right tweaks (such as using a table-valued variable to hold KEY values instead of re-evaluating the same WHERE criteria for every query in the batch - as always, check your indexes, STATISTICS objects, and execution plans!).
When querying for an object-graph, you'll write a SELECT query batch where each query returns all objects of the same type that has a JOIN with any other entities with a 1:1 or 1:0..1 multiplicity (if it isn't more efficient to load them in a separate query in the same batch).
In your case, I see you have:
[Employees]---(1:m)---[Phones]
[Employees]---(1:m)---[Emails]
[JobTitles]---(1:m)---[Employees]
[Departments]---(1:m)---[Employees]
[EmployeeStatus]---(1:m)---[Employees] // is this an enum table? if so, you can probably ditch it
So try this:
For the sake of simplicity, JobTitles, Departments, and EmployeeStatus can be done in a single query.
I assume the foreign-key columns are NOT NULL so an INNER JOIN should be used instead of LEFT OUTER JOIN.
const String EMPLOYEES_PHONES_EMAILS_SQL = #"
-- Query 1: Employees, Departments, EmployeeStatuses
SELECT
e.id,
e.FirstName,
e.LastName,
e.Nickname,
t.Name AS JobTitleName, -- This is to disambiguate column names. Never rely on column ordinals!
d.Name AS DepartmentName,
s.Name AS StatusName
FROM
dbo.Employees AS e
INNER JOIN dbo.JobTitles AS t ON e.JobTitleID = t.id
INNER JOIN dbo.Departments AS d ON e.DepartmentId = d.id
INNER JOIN dbo.EmployeeStatus AS s ON e.StatusID = s.id;
-- Query 2: Phones
SELECT
p.EmployeeId,
p.Number,
p.Type
FROM
dbo.Phones AS p;
-- Query 3: Emails
SELECT
m.id,
m.EmployeeId,
m.Address,
m.Type
FROM
dbo.Emails AS m;
";
using( SqlMapper.GridReader rdr = connection.QueryMultiple( EMPLOYEES_PHONES_EMAILS_SQL ) )
{
List<EmployeeModel> employees = ( await rdr.ReadAsync<EmployeeModel>() ).ToList();
var phonesByEmployeeId = ( await rdr.ReadAsync<PhoneModel> () ).GroupBy( p => p.EmployeeId ).Dictionary( grp => grp.Key grp => grp.ToList() );
var emailsByEmployeeId = ( await rdr.ReadAsync<EmailModel> () ).GroupBy( m => m.EmployeeId ).Dictionary( grp => grp.Key, grp => grp.ToList() );
foreach( EmployeeModel emp in employees )
{
if( phonesByEmployeeId.TryGetValue( emp.EmployeeId, out var phones ) )
{
emp.Phones.AddRange( phones );
}
if( emailsByEmployeeId.TryGetValue( emp.EmployeeId, out var emails ) )
{
emp.Emails.AddRange( emails );
}
}
}
I'll admit that I'm not intimately familiar with Dapper - and there is a problem with the code above: it doesn't instruct Dapper how to read the included Department, JobTitleModel, and EmployeeStatus data in the first query. I assume there's some overload of ReadAsync to specify other included data.
If you find yourself doing this kind of logic repetitively you can define your own extension-methods to handle the worst parts (such as GroupBy().ToDictionary(), and populating a collection property from a dictionary of loaded entities).
If you had a filter criteria, then you'd need to either store the resultant EmployeeId key values in a TVV, or repeat the criteria on Employees as the right-hand-side of an INNER JOIN in the queries for Phones and Emails.
For example, if you wanted to add an ability to find all Employees (and their phone-numbers and e-mail addresses) by name, you'd do this:
const String EMPLOYEES_PHONES_EMAILS_SQL = #"
-- Query 0: Get EmployeeIds:
DECLARE #empIds TABLE ( EmpId int NOT NULL PRIMARY KEY );
INSERT INTO #empIds ( EmpId )
SELECT
EmployeeId
FROM
dbo.Employees
WHERE
FirstName LIKE #likeFirst
OR
LastName LIKE #likeLast;
-- Query 1: Employees, Departments, EmployeeStatuses
SELECT
e.id,
e.FirstName,
e.LastName,
e.Nickname,
t.Name AS JobTitleName, -- This is to disambiguate column names. Never rely on column ordinals!
d.Name AS DepartmentName,
s.Name AS StatusName
FROM
dbo.Employees AS e
INNER JOIN dbo.JobTitles AS t ON e.JobTitleID = t.id
INNER JOIN dbo.Departments AS d ON e.DepartmentId = d.id
INNER JOIN dbo.EmployeeStatus AS s ON e.StatusID = s.id
INNER JOIN #empIds AS i ON i.EmpId = e.EmployeeId;
-- Query 2: Phones
SELECT
p.EmployeeId,
p.Number,
p.Type
FROM
dbo.Phones AS p
INNER JOIN #empIds AS i ON i.EmpId = p.EmployeeId;
-- Query 3: Emails
SELECT
m.id,
m.EmployeeId,
m.Address,
m.Type
FROM
dbo.Emails AS m
INNER JOIN #empIds AS i ON i.EmpId = m.EmployeeId;
";
using( SqlMapper.GridReader rdr = connection.QueryMultiple( EMPLOYEES_PHONES_EMAILS_SQL, new { likeFirst = "%john%", likeLast = "%smith%" } ) )
{
// same as before
}
I'm trying to do a left join that is 1 user record has 1 (if it's there) contact Record associated with it. When this runs, it still pull multiple contacts ignoring my condition of 'where c.RecordType == "USR"'.
public class Users
{
public int Id { get; set; }
... other properties
public Contact Contact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public int RecordID { get; set; }
public string RecordType { get; set; }
[ForeignKey("RecordID")]
public Users User { get; set; }
}
Linq Query
var query3 = (from c in db1.Contacts
join u in db1.Users
on c.RecordID equals u.Id
into a
from b in a.DefaultIfEmpty(new Models.Users())
where c.RecordType == "USR"
&& b.Lastname.Contains(name)
&& b.Active == 1
select new
{
b.Id,
b.FirstName,
b.Lastname,
b.FullName,
b.Contact
}).ToList();
Any help with where I'm going wrong would be much appreciated.
Thanks,
Adam
You can do a join in linq without using the join keyword. I use the following construct for left joins and do not have any issues.
var query3 = (from c in db1.Contacts
from u in db1.Users.Where(x => c.RecordID == x.Id).DefaultIfEmpty()
where (c.RecordType == "USR")
&& u.Lastname.Contains(name) && (u.Active == 1)
select new
{
u.Id,
u.FirstName,
u.Lastname,
u.FullName,
u.Contact
}).ToList();
As per information in question
1 user record has 1 (if it's there) contact Record associated with it
that means, a user can have zero or more contacts, so you suppose to use User as main entity and then left join on Contacts. Also you should apply Contact type filter during JOIN itself, like below
var query3 = ( join u in db1.Users
from c in db1.Contacts
on new { Record = c.RecordID, Type = c.RecordType } equals new { Record = u.Id, Type = "USR"} into b
from cont in b.DefaultIfEmpty(new Models.Contacts())
where u.Lastname.Contains(name)
&& u.Active == 1
select new
{
u.Id,
u.FirstName,
u.Lastname,
u.FullName,
cont.Contact
}).ToList();
What I'm trying to do is to is join tables to fill a viewModel that looks like this:
public class UserViewModel
{
public String Id { get; set; }
public String UserName { get; set; }
public String Email { get; set; }
public String Role { get; set; }
}
My query atm looks like this, but it doesn't work obviously, but it might help with descriping the problem.
public IActionResult AddAdmin()
{
var allUsers = (from u in _dbContext.Users
join r in _dbContext.UserRoles on u.Id equals r.UserId
join i in _dbContext.Roles on r.RoleId equals i.Id
select new UserViewModel
{
UserName = u.UserName,
Email = u.Email,
Id = u.Id,
Role = i.Name
}).ToList();
return View(allUsers);
}
As you see the thing I find hard is to apply the role to the viewModel, since they are connected to eachother with a manyTomany Table
Problem: The query does not work, and does not give anything in result
Before I joined the role into the viewModel, i got the data to the view, now i get nothing at all.
My Question: What's the correct way to do this? To easily navigate through the data
When I did it like this, it worked...
public IActionResult AddAdmin()
{
var allUsers = (from u in _dbContext.Users
select new UserViewModel
{
UserName = u.UserName,
Email = u.Email,
Id = u.Id
}).ToList();
foreach (var item in allUsers)
{
var roleId = _dbContext.UserRoles.Where(x => x.UserId == item.Id).FirstOrDefault();
item.Role = _dbContext.Roles.Where(x => x.Id == roleId.RoleId).FirstOrDefault().Name;
}
return View(allUsers);
}
I know it looks like s***..
You need a third entity to tie the two together. In Database terms, it is called a junction table.
I am new to Entity Framework, can anybody please tell how to extract data from following query and pass the result to the view.
public ActionResult Index()
{
var query = (from c in db.Customers
join b in db.Banks on c.Id equals b.CustomerId
join bt in db.BankTransactions on b.Id equals bt.BankId
where c.Id == 1
orderby bt.Id descending
select new
{
Name = c.Name,
Balance = bt.Balance
}).Take(1);
//I want to pass Customer Name and Customer Balance to the view
return View();
}
Create a view model
public class CustomerVM
{
public string Name { get; set; }
public decimal Balance { get; set; }
}
and modify your query to
var query = (from c in db.Customers ...
....
select new CustomerVM
{
Name = c.Name,
Balance = bt.Balance
}).FirstOrDefault();
then
return View(query);
View
#model YourAssembly.CustomerVM
...
#Html.DisplayFor(m => m.Name)
...
I didn't compile this snipet to check, but to solve your problem you could do something like this:
NewObject balanceInfo = query.AsEnumerable().Select(p => new NewObject
{
CostumerName = p.Name,
CostumerBalance = p.Balance
});
I do it a lot when my methods return lists. As I told, I didn't make a query and compiled to test, but I believe that this should solve your problem.
I'm newer using C#, linq. I'm trying to add the UserName into a query to show it as part of a DataSource of a ListView, I have tested several way to joined, but always I m'receiving next error:
"Unable to create a constant value of type 'Web.Admin.system.User'. Only primitive types or enumeration types are supported in this context."
My code is:
//Entities
public class Category
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class Order
{
public Guid Id { get; set; }
public string Description { get; set; }
public Guid CategoryId { get; set; }
public Guid UserId { get; set; }
}
//class added just for getting the user list (possibly, I do not need)
public class User
{
public Guid Id { get; set; }
public String Name { get; set; }
}
Here is my code preparing the filter
//retrieve the data from Order and Category
IQueryable<Order> orders = orderService.GetAllOrders();
IQueryable<Category> category = categoryService.GetAllCategories();
//obtain the users
MembershipUserCollection members = Membership.GetAllUsers();
// 1st option for managing users directly with memberShip variable
var memberShip = members.Cast<MembershipUser>().ToDictionary(m => m.ProviderUserKey, m => m.UserName).AsQueryable();
// 2nd option, I have added this code to see if I could manage the users as a list
List<User> users = new List<User>();
foreach (var _member in memberShip)
{
users.Add(new User { Id = (Guid)_member.Key, Name = _member.Value });
}
//Getting information to fill a listview
var DDLsource = from i in orders
join c in category on i.CategoryId equals c.Id
join u in users on i.UserId equals u.Id // 1st I tried to use memberShip directly but gave me error of types
select new
{
i.Id,
i.Description,
CategoryName = c.Name,
UserName = u.Name
};
ListViewOrders.DataSource = DDLsource.ToList();
Here is where the Error is triggered, I'm trying to understand the error and do other solution, I tested the query like:
Example 2
var DDLsource = from i in orders
join c in category on i.CategoryId equals c.Id
select new
{
i.Id,
i.Description,
CategoryName = c.Name,
UserName = (from u in users where u.Id == i.UserId select u.Name)
};
Example 3
var DDLsource = from i in orders
join c in category on i.CategoryId equals c.Id
join u in Membership.GetAllUsers().Cast<MembershipUser>() on i.UserId equals ((Guid)u.ProviderUserKey)
select new
{
i.Id,
i.Description,
CategoryName = c.Name,
UserName = u.UserName
};
all with the same results, could someone give me a hand with my mistake will surely be very obvious. Thanks in advance
I would do something like this (sorry, untested code...):
var DDLsource =
from i in orders
join c in category on i.CategoryId equals c.Id
select new
{
i.Id,
i.Description,
CategoryName = c.Name,
i.UserId,
UserName = ""
};
foreach(var ddl1 in DDLsource)
ddl1.UserName = Membership.GetUser(ddl1.UserId).Name;