Better way to join tables with Entity Framework - c#

Good morning,
I have inherited a database with no foreign key relations and the project is such that i have to ignore this major issue and work around it. Obviously this eliminates some of the cooler features of Entity Framework providing me related entities automatically.
So i have been forced to do something like this:
using (var db = new MyEntities())
{
Entities.Info record = db.Infoes.Where(x => x.UserId == authInfo.User.Id).FirstOrDefault();
//Get all the accounts for the user
List<Entities.AcctSummary> accounts = db.AcctSummaries.Where(x => x.InfoId == record.Id).ToList();
//Loop through each account
foreach (Entities.AcctSummary account in accounts)
{
//pull records for account
List<Entities.Records> records= db.Records.Where(x => x.AcctSummaryId == account.Id).ToList();
}
}
If there a better way to join the "record" and "accounts" Entities, or perhaps a more efficient way for getting "records" in a single query?
TIA

Are you just looking for the .Join() extension method? As an example, joining Infoes and Accounts might look like this:
var accounts = db.Infoes.Join(db.Accounts,
i => i.Id,
a => a.InfoId,
(info, account) => new { info, account });
This would result in accounts being an enumeration of an anonymous type with two properties, one being the Info record and the other being the Account record. The collection would be the full superset of the joined records.
You can of course return something other than new { info, account }, it works just like anything you'd put into a .Select() clause. Whatever you select from these joined tables would be what you have an enumeration of in accounts. You can further join more tables by changing .Join() extensions, returning whatever you want from each.

Related

LINQ query list of objects with multiple list of objects in it

I have the following table relations
User
-List<Role> on User.RoleId = Id
-RoleDetail on Role.RoleDetailId = Id
-Practice on Role.PracticeId = Id
While I do have all these entities linked up in EF, I'm only able to access Roles when I query User with dbConext Include, RoleDetail & Practice are not accessible, which is understandable. Is there any way I can change how I query User to include those information?
If not, how should I join these tables efficiently? I'm not looking to foreach loop on every single user's multiple roles but a single query request. Any help/hint is appreciated.
Querying User with Role
users = _dbcontext.Users.Include(u => u.Roles)
Joining
var userQuery = _userRepository.GetUser(searchTerm);
var roleQuery = _roleRepository.GetRole();
var test = from user in userQuery join role in roleQuery on user.Roles....
You need to use two Include on roles for getting all data like this:
users = _dbcontext.Users.Include(u => u.Roles).ThenInclude(x=>x.RoleDetail)
.Include(u => u.Roles).ThenInclude(x=>x.Practice)
.AsSplitQuery(); //highly recommend if you are using .Net >=5

Improving performance of Linq to SQL list intersection

I've got some working code here but I'm really concerned that it's not efficient - but I can't think of a way to improve it. Any thoughts?
We have IQueryable<Users> users, which gets its data from a users table with entity framework mapping each user to a separate table of organizations, which is a one-to-many relationship. We also have a List<string> orgCriteria, which is a list of organizations we want to filter Users by. In essence we want to get a list of users who have a membership in any of the organizations in the criteria.
To compare the names of the user's orgs to the orgs in the filter criteria, we have to use some linq/EF mappings like this: var x = users.Select(x => x.Orgs.Name).ToList(); However, the display name is what we get from the criteria, which means we have to translate the partial name to the display name as well...
These are the tables that get pulled in to all this: User, Orgs, UserOrgs. User has a FK to the Id of Orgs, and UserOrgs has 3 columns: Id, UserId, OrgId where UserId and OrgId are FKs to their respective tables. A user can have 0 or as many as there are Orgs. Each org has a name, but the display name, which we map in the domain model normally, is composed of three columns: name, foo, and bar with bar being the nullable column.
I tried an intersect like this, but it doesn't work: users = users.Where(x => x.Select(y => string.Format("{0} - {1}{2}", y.Org.Name, y.Org.foo, y.Org.bar != null ? " - " + y.Org.bar : string.Empty)).Intersect(orgCriteria).Any()); because I get this error:
Local sequence cannot be used in LINQ to SQL implementations of query
operators except the Contains operator.
So I can make it work by combining a foreach and an intersect, but I'm concerned... If we have 500 users who each have 20 orgs, it seems like this could be a very expensive filter.
This way works, but it makes me nervous:
foreach(var user in users)
{
List<string> userOrgNames = user.Orgs.Select(x => string.Format("{0} - {1}{2}", y.Org.Name, y.Org.foo, y.Org.bar != null ? " - " + y.Org.bar : string.Empty)).ToList();
if (!userOrgNames.Intersect(orgCriteria).Any())
users = users.Where(x => x.Id != user.Id);
}
Any ideas?
Edit - Here is a rudimentary diagram!
You can try something below.
I did this LINQ based on this statement In essence we want to get a list of users who have a membership in any of the organizations in the criteria.
var filteredUsers = users.Where(user =>
user.Orgs.Any(org => orgCriteria.Contains($"{org.Name} - {org.foo}{org.bar}")));
In your case if $"{org.Name} - {org.foo}{org.bar}" this does not work, use string.Format("{0} - {1}{2}", org.Name, org.foo, org.bar)

How to return IQueryable LINQ result from two joined tables into a List<string>?

This is an add-on question to one asked here: Entity Framework Core 5.0 How to convert LINQ for many-to-many join to use Intersection table for ASP.NET Membership
How can I return the results of an the following LINQ IQueryable result, which is from two join tables, for the RoleName column to a List<string>?
var queryResult = (this.DbContext.aspnet_UsersInRoles
.Where(x => x.UserId == dpass.UserId)
.Join(
this.DbContext.aspnet_Roles,
ur => ur.RoleId,
r => r.RoleId,
(ur, role) => new
{
ur,
role
}
)
.Select(x => new { x.ur.UserId, x.role.RoleName })
);
UPDATE 1
I need the List in the form of an array of values so that I can use the Contains() method. I need to search for specific RoleNames assigned to a UserId. If I use ToList() on the IQueryable, then the array result is in the form of:
{ RoleName = "admin"}
{ Rolename = "user"}
I am unable to use the .Contains() method because I get the following error:
cannot convert from 'string' to <anonymous type: string RoleName>.
It seems be to expecting a class that the query result can be assigned to. But, one doesn't exist because I am doing this on-the-fly.
UPDATE 2
I need the queryResult in a List that is in the form of:
{ "admin"}
{ "user"}
With this output, I can use the .Contains() method to perform multiple checks. This is used for determining Windows Forms field properties. So, if the UserId belongs to the admin role then the form enables certain check boxes and radio buttons whereas if the UserId belongs to the user role then the form enables different check boxes. This is not an exhaustive list of roles available along with the checks that are performed by the form. But, what is important is that there are multiple checks on the List that need to be performed in separate IF statements.
Currently, I am able to use the queryResult to do the following:
Get a list of the RoleNames
Perform separate LINQ queries on the queryResult by checking for the specific RoleName
Perform a .Count() > 0 check to see if the UserId is in a specific role.
This seems like an ugly hack because I have the intermediate step of creating 1 + N variables to retrieve, by LINQ, and store each RoleName and then check to see if the .Count() is greater than zero. I think that the List method would be cleaner and more efficient. If that is possible.
var varUser = from d in queryResult
where d.RoleName == "user"
select new { d.RoleName };
var varAdmin = from u in queryResult
where u.RoleName == "admin"
select new { u.RoleName };
//... more declarations and LINQs ...
Short answer:
Select only the RoleName, and use SelectMany instead of Select
Better answer
So you have a table of Roles, and a table of Users (I'm simplifying your long identifiers, not part of the problem and way too much typing).
There seems to be a many to many relation between Roles and Users: Every Role is a role for zero or more Users, every User has zero or more Roles.
This many-to-many relation is implemented using a standard junction table: UsersInRoles. This junction table has two foreign keys: one to the User and one to the Roles.
You have a UserId, and it seems that you want all names of all Roles of the user that has this Id.
How about this:
int userId = ...
// Get the names of all Roles of the User with this Id
var namesOfRolesOfThisUser = dbContext.UsersInRoles
// only the user with this Id:
.Where(userInRole => userInRole.UserId == userId)
// get the names of all Roles for this userInRole
.SelectMany(userInRole => dbContext.Roles.Where(role => role.RoleId == userInRole.RoleId)
.Select(role => role.RoleName));
In words: from the table of UsersInRoles, keep only those UsersInRoles that have a value for property UserId that equals userId.
From every one of the remaining UsersInRoles, select all Roles that have a RoleId that equeals the UserInRole.RoleId. From these Roles take the RoleName.
I use SelectMany to make sure that I get one sequence of strings, instead of a sequence of sequences of strings.
If you suspect double RoleNames, consider to append Distinct() at the end.
But I want to Join!
Some people really like to do the joins themselves.
int userId = ...
var namesOfRolesOfThisUser = dbContext.UsersInRoles
.Where(userInRole => userInRole.UserId == userId)
.Join(dbContext.Roles,
userInRole => userInRole.RoleId, // from every UserInRole take the foreign key
role => role.RoleId, // from every Role take the primary key
// when they match, take only the name of the Role
(userInRole, role) => role.RoleName);
Try to use GroupBy(). Be careful, this method is not supported by direct IQueryable to SQL conversion. If you will try to call GroupBy() before .ToList(), it will throw an error.
In your example you could this: select a list in memory and then work with it:
var queryResult = (this.DbContext.aspnet_UsersInRoles
.Where(x => x.UserId == dpass.UserId)
.Join(this.DbContext.aspnet_Roles,
ur => ur.RoleId,
r => r.RoleId,
(ur, role) => new { ur, role }
)
.Select(x => new { x.ur.UserId, x.role.RoleName })
.ToList() // MATERIALIZE FIRST
.GroupBy(x => x.UserId) //ADD THIS
);
queryResult.Contains(roleName=> roleName == "ROLE_TO_SEARCH")
var userId = queryResult.Key;

Access Object through foreign key c#

I have the following code and would like to know if there is a way to refactor in order to remove duplicated logic.
This results current user with eager loading.
var currentEmployee = RosterContext.Employees
.Where(e => e.User.Id == id)
.Include(e => e.Job.Department).FirstOrDefault();
.
var job = RosterContext.Employees.Where(e=>e.Job.Department.Id == currentEmployee.Job.DepartmentId).ToList();
I created another same context which compares the first line of code to result all employee names who work in same department. My question is, as I am using two linq expression that uses the same context (Employees) am i able to combine both linq queries into one?
It may become a long linq expression but it should serve on getting the current user object followed by comparing user object to get all employees that share the same department id?
It makes sense to try an ORM framework, such as Entity Framework or NHibernate.
ORM framewok will model database FK relationship as a scalar property on one side and vector property (collection) on the other side of the relationship.
For instance Department would have a collection property Jobs, and a Job entity would have a scalar Department property.
DB queries with joins on FK become just dependency property navigation, for example - to access the list of employees in current department you would just return something like employee.Department.Employees - that is, assuming your entities are all loaded (which is rather simple to achieve in EF, using include statement)
In Entity Framework you have the using clause to attach children. So for example in pure EF you could do:
var department = context.Department.Include(d => d.Jobs).First(d => d.DepartmentId == departmentId);
https://msdn.microsoft.com/en-us/library/gg671236%28v=vs.103%29.aspx#Anchor_1
With a repository, you may need to do something like this:
EF Including Other Entities (Generic Repository pattern)
EF Code First supports relationships out of the box. You can either use the conventions or explicitly specify the relationship (for example, if the foreign key property is named something weird). See here for example: https://msdn.microsoft.com/en-us/data/hh134698.aspx
When you've configured your models right, you should be able to access department like so:
var currentUser = _unitOfWork.Employee.GetEmployeeByID(loggedInUser.GetUser(user).Id);
var job = currentUser.Job;
var department = job.Department;
// or
var department = _unitOfWork.Employee.GetEmployeeByID(loggedInUser.GetUser(user).Id).Job.Department;
To show all employees that work in the same department:
var coworkers = department.Jobs.SelectMany(j => j.Employees);
Update
To use eager loading with a single repository class (you shouldn't need multiple repository classes in this instance, and therefore don't need to use Unit of Work):
public class EmployeeRepository {
private readonly MyContext _context = new MyContext(); // Or whatever...
public IList<Employee> GetCoworkers(int userId) {
var currentEmployee = _context.Employees
.Where(e => e.UserId == userId)
.Include(e => e.Job.Department) // Use eager loading; this will fetch Job and Department rows for this user
.FirstOrDefault();
var department = currentEmployee.Job.Department;
var coworkers = department.Jobs.SelectMany(j => j.Employees);
return coworkers;
}
}
And call it like so...
var repo = new EmployeeRepository();
var coworkers = repo.GetCoworkers(loggedInUser.GetUser(user).Id);
You probably would be able to make the repository query more efficient by selecting the job and department of the current user (like I've done) and then the related jobs and employees when coming back the other way. I'll leave that up to you.

LINQ: Is there a way to combine these queries into one?

I have a database that contains 3 tables:
Phones
PhoneListings
PhoneConditions
PhoneListings has a FK from the Phones table(PhoneID), and a FK from the Phone Conditions table(conditionID)
I am working on a function that adds a Phone Listing to the user's cart, and returns all of the necessary information for the user. The phone make and model are contained in the PHONES table, and the details about the Condition are contained in the PhoneConditions table.
Currently I am using 3 queries to obtain all the neccesary information. Is there a way to combine all of this into one query?
public ActionResult phoneAdd(int listingID, int qty)
{
ShoppingBasket myBasket = new ShoppingBasket();
string BasketID = myBasket.GetBasketID(this.HttpContext);
var PhoneListingQuery = (from x in myDB.phoneListings
where x.phonelistingID == listingID
select x).Single();
var PhoneCondition = myDB.phoneConditions
.Where(x => x.conditionID == PhoneListingQuery.phonelistingID).Single();
var PhoneDataQuery = (from ph in myDB.Phones
where ph.PhoneID == PhoneListingQuery.phonePageID
select ph).SingleOrDefault();
}
You could project the result into an anonymous class, or a Tuple, or even a custom shaped entity in a single line, however the overall database performance might not be any better:
var phoneObjects = myDB.phoneListings
.Where(pl => pl.phonelistingID == listingID)
.Select(pl => new
{
PhoneListingQuery = pl,
PhoneCondition = myDB.phoneConditions
.Single(pc => pc.conditionID == pl.phonelistingID),
PhoneDataQuery = myDB.Phones
.SingleOrDefault(ph => ph.PhoneID == pl.phonePageID)
})
.Single();
// Access phoneObjects.PhoneListingQuery / PhoneCondition / PhoneDataQuery as needed
There are also slightly more compact overloads of the LINQ Single and SingleOrDefault extensions which take a predicate as a parameter, which will help reduce the code slightly.
Edit
As an alternative to multiple retrievals from the ORM DbContext, or doing explicit manual Joins, if you set up navigation relationships between entities in your model via the navigable join keys (usually the Foreign Keys in the underlying tables), you can specify the depth of fetch with an eager load, using Include:
var phoneListingWithAssociations = myDB.phoneListings
.Include(pl => pl.PhoneConditions)
.Include(pl => pl.Phones)
.Single(pl => pl.phonelistingID == listingID);
Which will return the entity graph in phoneListingWithAssociations
(Assuming foreign keys PhoneListing.phonePageID => Phones.phoneId and
PhoneCondition.conditionID => PhoneListing.phonelistingID)
You should be able to pull it all in one query with join, I think.
But as pointed out you might not achieve alot of speed from this, as you are just picking the first match and then moving on, not really doing any inner comparisons.
If you know there exist atleast one data point in each table then you might aswell pull all at the same time. if not then waiting with the "sub queries" is nice as done by StuartLC.
var Phone = (from a in myDB.phoneListings
join b in myDB.phoneConditions on a.phonelistingID equals b.conditionID
join c in ph in myDB.Phones on a.phonePageID equals c.PhoneID
where
a.phonelistingID == listingID
select new {
Listing = a,
Condition = b,
Data = c
}).FirstOrDefault();
FirstOrDefault because single throws error if there exists more than one element.

Categories

Resources