Making a LINQ query better - c#

I'm having a hard time getting the LINQ-syntax.. How can I do this command in a better way?
var user = (from u in context.users
where u.email.Equals(email)
select u).Single();
var pinToUser = (from ptu in context.pintousers
where ptu.user_id.Equals(user.id)
select ptu).Single();
var pin = (from p in context.pins
where p.idpin.Equals(pinToUser.pin_idpin)
select p).Single();
return pin;
As you can see, there's a table user, a table pintouser and a table pin. Pintouser references user and pin. Is it possible to write something short like "user.pintouser.pin"? I think I have the navigation properties all set up but I'm not sure how to use them properly or if I could make them better by modifying them.
Thanks for reading

Use joins to rewrite everything as a single clean query. If I read your queries properly, this should give you the correct result:
var pin = (from u in context.users
join ptu in context.pintousers on u.id equals ptu.user_id
join p in context.pins on ptu.pin_idpin equals p.idpin
where u.email == email
select p).Single();
Keep in mind, though, that if this query returns anything other than a single result your code will throw an Exception.
If you want to handle the possibility of getting one or no rows then you should use SingleOrDefault().
If you want to handle the possiblity of getting any number of rows then you should really use FirstOrDefault().

Note that if you have your foreign-key relationship set righ in your database, Linq-to-Sql should have the joins for you automatically:
var pin = (from u in context.users
where u.email == email
select u.pintouser.pin).Single();
which means you can reduce this to:
var pin = context.users.Where(u=>u.email == email)
.Select(u=>u.pintouser.pin)
.Single();
(UPDATE Note: I had originally suggested the following, which is much shorter, but I believe it will cause two round-trips to the database)
var pin = context.users.Single(u=>u.email == email).Single().pintouser.pin;
Now, the .pintouser.pin is safe, because the Single() will always return a user object (or throw an exception).

You should be using join, as #JustinNiessner points out, but this is another way to write your query.
var user = context.users.Single(u => u.email == email);
var pinToUser = context.pintousers.Single(ptu => ptu.user_id == user.id);
var pin = context.pins.Single(p => p.idpin == pinToUser.pin_idpid);

Since you have navigation properties, might as well use them:
Pin pin =
(
from u in context.Users
where u.email == email
from ptu in u.pintousers
let p = ptu.pin
select p
).Single();

Related

Equivalent LINQ query for SQL query not resulting in expected results

I am trying to write a LINQ query equivalent to below SQL
SELECT DISTINCT m.*,rm.RoleId FROM dbo.Menu m
INNER JOIN dbo.RoleMenu rm on m.Id=rm.MenuId
INNER JOIN dbo.RoleUser ru on rm.RoleId=ru.RoleId
WHERE ru.UserName='dd#dd.com' and m.Url='/dashboard#/pm'
I came with the below query which is not returning the expected output
var auth = _context.RoleUsers.Where(
x => x.Role.MenuRoles.FirstOrDefault().Menu.Url == pagePermissions.Url
&& x.UserName == pagePermissions.UserName).Count()
May I know a better way to do this?
Your sql looks at all the menus related to a role user, but your Linq is only looking at the first one. I think you want x.Role.MenuRoles.Any(mr => mr.Menu.Url == pagePermissions.Url). But then you're also doing a Count on the matching users instead of selecting the menus that match that url. A closer translation would be.
var results = (from m in _context.Menus
from rm in m.RoleMenus
from ru in rm.RoleUsers
where m.Url == pagePermissions.Url
&& u.UserName == pagePermissions.UserName
select new { Menu = m, rm.RoleId }).Distinct();
You may have to adjust some of the navigation properties as I was just guessing at them. They usually are pluralizations of the tables, but I see in your Linq that you have MenuRoles instead of RoleMenus.

Linq to Sql - How to get data from second level table

I'm newbie to linq to sql, just trying to understand what type of queries I can handle with linq.
Here is my database scheme,
I want to get all customers of a specific user and this is what I've done,
var userId = 4;
var companies = from c in db.Company
where c.UserId == userId
select c.Id;
var costumers = from c in db.Customers
where companies.Contains(c.CompanyId)
select c;
I'm just wondering whether it's a nice approach and is there any better method to handle this type of queries?
Use can also get customers by this way also:
var result = db.Customers.Join(
db.Company, customer => customer.CompanyId, comp => comp.Id, (customer, comp)
=> new { customer, comp }).Where(#t => #t.comp.UserId == 4)
.Select(#t => #t.customer);
You can also keep it simple like this.
select * from db.Customers Cus
inner join db.company Com on Com.Id = Cus.CompanyId
where Com.UserId= userId
Contains is the equivalent of IN in SQL and your Linq statement will be translated to a SQL statement. So I can't really see another way that will give you better performance with Linq. If you want to use less code you can maybe try the following instead:
var companies = db.Companies.Where(x=> x.UserId == userid).Select(x=>x.Id);
var customers = db.Customers.Where(x=> companies.Contains(x.CompanyId));

LINQ to Sql is empty and throws Object reference not set to an instance of an object. error

I use the following query to get a result set
var overlaps = from s in db.signups
join u in db.users on new { userid = s.userid } equals new { userid = u.studentid }
join a in db.activities on new { activityid = s.activityid } equals new { activityid = a.id }
where
s.userid != Convert.ToInt32(Request.Cookies["studentid"].Value) &&
(from signups in db.signups
where
signups.userid == Convert.ToInt32(Request.Cookies["studentid"].Value)
select new
{
signups.activityid
}).Contains(new { s.activityid })
orderby
u.studentid
select new
{
a.name,
u.firstname,
u.lastname,
u.studentid,
u.email
};
I'm pretty new to LINQ so I actually wrote the Sql and then used Linqer to generate the LINQ, so if this can be done more efficiently then please let me know. Having said that, this is not the problem.
The problem is that when I do
foreach(var overlap in overlaps)
{
//do something
}
it throws the object reference not set error. This is being run in an MVC 3 application.
However, when this is run in a Console application, it runs without issue; it just returns no results. I've tried using DefaultIfEmpty but just can't find anything that addresses how to use this with anonymous types.
So
... is my approach correct?
If not, what should I do differently?
Thanks, in advance.
I don't know if this is your problem, but your join syntax is really weird.
You don't have to build anonymous types here, just compare directly.
join u in db.users on s.userid equals u.studentid
join a in db.activities on s.activityid equals a.id
Same with this:
select new
{
signups.activityid
}).Contains(new { s.activityid })
Can be just:
select signups.activityid).Contains(s.activityid)
And why in the world do you want to redo all the work to convert the cookie parameter to an int over and over?
var studentId = Convert.ToInt32(Request.Cookies["studentid"].Value);
//use this instead now in the query, dont repeat yourself
To your first question, you are appropriately worried about how messy the linq is... we often will take messy linq and just do a dataContext.ExecuteQuery or .ExecuteCommand because one of linq's major short falls is their ability to optimize complex queries as well as you could.
To get an idea of how badly linq has botched your query there, run it through the query analyzer and compare it to what you started with... My guess is that it will be comical!
ICBW, but I would try casting overlaps, something like:
foreach(OverlapType overlap in overlaps as IEnumerable<OverlapType>)
{
//stuff
}
This of course means you will need to make a model of the object you are getting from the database. But really, you should have one anyway, that is the whole premise behind MVC (Model View Controller)
Well, first off, I'm pretty sure you can simplify the first few lines down to :
from s in db.signups
join u in db.users on s.userid equals u.studentid
join a in db.activities on s.activityid equals a.id
in fact, if you've defined foreign keys on those properties, you don't need the joins at all -- LINQ will handle them for you automatically: Write s.User.firstname instead of u.firstname etc.
As for your main problem, check all the component of that query, mainly "db" and "Request" (and how exactly does Request.Cookies work in a console application?)

is there a better way to write this frankenstein LINQ query that searches for values in a child table and orders them by relevance?

I have a table of Users and a one to many UserSkills table. I need to be able to search for users based on skills. This query takes a list of desired skills and searches for users who have those skills. I want to sort the users based on the number of desired skills they posses. So if a users only has 1 of 3 desired skills he will be further down the list than the user who has 3 of 3 desired skills.
I start with my comma separated list of skill IDs that are being searched for:
List<short> searchedSkillsRaw = skills.Value.Split(',').Select(i => short.Parse(i)).ToList();
I then filter out only the types of users that are searchable:
List<User> users = (from u in db.Users
where
u.Verified == true &&
u.Level > 0 &&
u.Type == 1 &&
(u.UserDetail.City == city.SelectedValue || u.UserDetail.City == null)
select u).ToList();
and then comes the crazy part:
var fUsers = from u in users
select new
{
u.Id,
u.FirstName,
u.LastName,
u.UserName,
UserPhone = u.UserDetail.Phone,
UserSkills = (from uskills in u.UserSkills
join skillsJoin in configSkills on uskills.SkillId equals skillsJoin.ValueIdInt into tempSkills
from skillsJoin in tempSkills.DefaultIfEmpty()
where uskills.UserId == u.Id
select new
{
SkillId = uskills.SkillId,
SkillName = skillsJoin.Name,
SkillNameFound = searchedSkillsRaw.Contains(uskills.SkillId)
}),
UserSkillsFound = (from uskills in u.UserSkills
where uskills.UserId == u.Id && searchedSkillsRaw.Contains(uskills.SkillId)
select uskills.UserId).Count()
} into userResults
where userResults.UserSkillsFound > 0
orderby userResults.UserSkillsFound descending
select userResults;
and this works! But it seems super bloated and inefficient to me. Especially the secondary part that counts the number of skills found.
Thanks for any advice you can give.
--r
I think that should do the trick:
(from u in users
where u.UserSkills.Any(skill => searchedSkillsRaw.Contains(skill.SkillId))
select new
{
u.Id,
u.FirstName,
u.LastName,
u.UserName,
UserPhone = u.UserDetail.Phone,
UserSkills = u.UserSkills,
UserSkillsFound = u.UserSkills.Where(skill => searchedSkillsRaw.Contains(skill.SkillId)).Count()
} into userResults
orderby userResults.UserSkillsFound descending
select userResult).ToList();
However, since this is a query that gets executed on SQL server I strongly recommend to remove the 'ToList()' call from the first query. Because that actually causes LINQ to run two separate queries on the SQL server. You should change it to IQueryable instead. The power of LINQ is to construct queries in several steps without having to actually execute it in between. So 'ToList' should be called only at the end when the entire query has been constructed. In fact what you currently do is running the second query in memory rather than on the database server.
In regards to your UserSkills one-to-many relation you do not need to do an explicity join in LINQ. You can just access the collection property instead.
Let me know if you need more explanation.
Michael
Why not just let people do, say, fUsers.UserSkills.Count()? It would reduce the amount of data retrieved from the server in the first place.
Alternatively, you could create a View that has a calculated field in it and then map that to a type. Would push the query for count down into the DB.

How to do a subquery in LINQ?

Here's an example of the query I'm trying to convert to LINQ:
SELECT *
FROM Users
WHERE Users.lastname LIKE '%fra%'
AND Users.Id IN (
SELECT UserId
FROM CompanyRolesToUsers
WHERE CompanyRoleId in (2,3,4) )
There is a FK relationship between CompanyRolesToUsers and Users, but it's a many to many relationship and CompanyRolesToUsers is the junction table.
We already have most of our site built, and we already have most of the filtering working by building Expressions using a PredicateExtensions class.
The code for the straightforward filters looks something like this:
if (!string.IsNullOrEmpty(TextBoxLastName.Text))
{
predicateAnd = predicateAnd.And(c => c.LastName.Contains(
TextBoxLastName.Text.Trim()));
}
e.Result = context.Users.Where(predicateAnd);
I'm trying to add a predicate for a subselect in another table. (CompanyRolesToUsers)
What I'd like to be able to add is something that does this:
int[] selectedRoles = GetSelectedRoles();
if( selectedRoles.Length > 0 )
{
//somehow only select the userid from here ???:
var subquery = from u in CompanyRolesToUsers
where u.RoleID in selectedRoles
select u.UserId;
//somehow transform this into an Expression ???:
var subExpression = Expression.Invoke(subquery);
//and add it on to the existing expressions ???:
predicateAnd = predicateAnd.And(subExpression);
}
Is there any way to do this? It's frustrating because I can write the stored procedure easily, but I'm new to this LINQ thing and I have a deadline. I haven't been able to find an example that matches up, but I'm sure it's there somewhere.
Here's a subquery for you!
List<int> IdsToFind = new List<int>() {2, 3, 4};
db.Users
.Where(u => SqlMethods.Like(u.LastName, "%fra%"))
.Where(u =>
db.CompanyRolesToUsers
.Where(crtu => IdsToFind.Contains(crtu.CompanyRoleId))
.Select(crtu => crtu.UserId)
.Contains(u.Id)
)
Regarding this portion of the question:
predicateAnd = predicateAnd.And(c => c.LastName.Contains(
TextBoxLastName.Text.Trim()));
I strongly recommend extracting the string from the textbox before authoring the query.
string searchString = TextBoxLastName.Text.Trim();
predicateAnd = predicateAnd.And(c => c.LastName.Contains( searchString));
You want to maintain good control over what gets sent to the database. In the original code, one possible reading is that an untrimmed string gets sent into the database for trimming - which is not good work for the database to be doing.
There is no subquery needed with this statement, which is better written as
select u.*
from Users u, CompanyRolesToUsers c
where u.Id = c.UserId --join just specified here, perfectly fine
and u.lastname like '%fra%'
and c.CompanyRoleId in (2,3,4)
or
select u.*
from Users u inner join CompanyRolesToUsers c
on u.Id = c.UserId --explicit "join" statement, no diff from above, just preference
where u.lastname like '%fra%'
and c.CompanyRoleId in (2,3,4)
That being said, in LINQ it would be
from u in Users
from c in CompanyRolesToUsers
where u.Id == c.UserId &&
u.LastName.Contains("fra") &&
selectedRoles.Contains(c.CompanyRoleId)
select u
or
from u in Users
join c in CompanyRolesToUsers
on u.Id equals c.UserId
where u.LastName.Contains("fra") &&
selectedRoles.Contains(c.CompanyRoleId)
select u
Which again, are both respectable ways to represent this. I prefer the explicit "join" syntax in both cases myself, but there it is...
This is how I've been doing subqueries in LINQ, I think this should get what you want. You can replace the explicit CompanyRoleId == 2... with another subquery for the different roles you want or join it as well.
from u in Users
join c in (
from crt in CompanyRolesToUsers
where CompanyRoleId == 2
|| CompanyRoleId == 3
|| CompanyRoleId == 4) on u.UserId equals c.UserId
where u.lastname.Contains("fra")
select u;
You could do something like this for your case - (syntax may be a bit off). Also look at this link
subQuery = (from crtu in CompanyRolesToUsers where crtu.RoleId==2 || crtu.RoleId==3 select crtu.UserId).ToArrayList();
finalQuery = from u in Users where u.LastName.Contains('fra') && subQuery.Contains(u.Id) select u;
Ok, here's a basic join query that gets the correct records:
int[] selectedRolesArr = GetSelectedRoles();
if( selectedRolesArr != null && selectedRolesArr.Length > 0 )
{
//this join version requires the use of distinct to prevent muliple records
//being returned for users with more than one company role.
IQueryable retVal = (from u in context.Users
join c in context.CompanyRolesToUsers
on u.Id equals c.UserId
where u.LastName.Contains( "fra" ) &&
selectedRolesArr.Contains( c.CompanyRoleId )
select u).Distinct();
}
But here's the code that most easily integrates with the algorithm that we already had in place:
int[] selectedRolesArr = GetSelectedRoles();
if ( useAnd )
{
predicateAnd = predicateAnd.And( u => (from c in context.CompanyRolesToUsers
where selectedRolesArr.Contains(c.CompanyRoleId)
select c.UserId).Contains(u.Id));
}
else
{
predicateOr = predicateOr.Or( u => (from c in context.CompanyRolesToUsers
where selectedRolesArr.Contains(c.CompanyRoleId)
select c.UserId).Contains(u.Id) );
}
which is thanks to a poster at the LINQtoSQL forum
Here's a version of the SQL that returns the correct records:
select distinct u.*
from Users u, CompanyRolesToUsers c
where u.Id = c.UserId --join just specified here, perfectly fine
and u.firstname like '%amy%'
and c.CompanyRoleId in (2,3,4)
Also, note that (2,3,4) is a list selected from a checkbox list by the web app user, and I forgot to mention that I just hardcoded that for simplicity. Really it's an array of CompanyRoleId values, so it could be (1) or (2,5) or (1,2,3,4,6,7,99).
Also the other thing that I should specify more clearly, is that the PredicateExtensions are used to dynamically add predicate clauses to the Where for the query, depending on which form fields the web app user has filled in. So the tricky part for me is how to transform the working query into a LINQ Expression that I can attach to the dynamic list of expressions.
I'll give some of the sample LINQ queries a shot and see if I can integrate them with our code, and then get post my results. Thanks!
marcel

Categories

Resources