linq: aggregate into single line (flatten) - c#

I am currently using a linq and accessing some meta data using a for loop:
public SignUpMeta GetSignUpMeta(User user)
{
var users = (from u in User
where user.Email == u.Email || user.UserName == u.UserName
select u);
var result = new SignUpMeta();
foreach (var u in users)
{
if (user.Email == u.Email) result.IsDuplicateEmail = true;
if (user.UserName == u.UserName) result.IsDuplicateUserName = true;
}
return result;
}
Is there any way for the Linq to generate the SignUpMeta directly?
This function is inside the DBContext class and I am looking for a way to get the exact data directly from the db (without raw sql).
Update:
The User is DbSet and the whole code runs inside the db context class. I am trying to write something that will make EF fetch the value directly in a single query.
Update 2:
The SQL equivalent of what I am looking for would be:
SELECT MAX(username), MAX(email)
(SELECT CAST((UserName = #user) AS bit) username,
CAST((Email = #email) AS bit) email
FROM User WHERE UserName = #user OR Email = #email)
Update 3:
SignUpMeta object that needs to be fetched contains the metadata that provides information required for server side validation.
The above C# code runs a query that fetches up to two columns in this instance. When there are more such conditions, there would be more lines. I am trying to find a way that EF would give only the two booleans alone in a single record.

This'll be my try, if you truly must use LINQ:
from u in stuff.Users
group u by 0 into grp
select new
{
IsDuplicateEmail = grp.Any(x => x.Email == user.Email),
IsDuplicateUserName = grp.Any(x => x.UserName == user.UserName)
}
Entity Framework will translate that into sub-selects. If you're using SQL Server and have both columns indexed, this should result in the same amount of I/O as your sample SQL.
I don't believe there is any query that will generate your desired sample SQL.

I think this will be the fastest query:
public SignUpMeta GetSignUpMeta(User user)
{
return new SignUpMeta()
{
IsDuplicateEmail = User.Where(u => u.Email == user.Email).Take(1).Any(),
IsDuplicateUserName = User.Where(u => u.UserName == user.UserName).Take(1).Any(),
};
}
Caching on the DB server should make the two queries quite fast.

Related

Fetching only one column from a table via where and select LINQ statement

I have written two queries which look like:
var loggedUser2 = ctx.Users.Where(y => y.Email == User.Identity.Name).Select(usr => new Users { UserId = usr.UserId }).AsEnumerable();
And second query looks like this:
var loggedUser = ctx.Users.FirstOrDefault(y => y.Email == User.Identity.Name);
The second query noticabely takes much more time to pull a single record from a table from a remote server as I can tell.
For the first query I'm getting an error:
The entity or complex type 'Users' cannot be constructed in a LINQ to Entities query.
when I try to access the property UserId of the "Users" object.
Now I have a couple of questions:
Why do I get this error and how do I actually access a property of a collection which is IEnumerable? I get it that I can materialize the query by using .ToList() and then have it accessed at [0].UserId, but I don't think this is the right way to do it?
What is the most efficient way to select a single column for a single records from a table (UserId in my case)? How would that query looks like (besides using a stored procedure).
Can someone help me out ? :)
you can declare a DTO or you can use an anonymous type like this:
var user = ctx.Users
.Where(u => u.Email == email)
.Select(u => new { UserId = u.UserId })
.FirstOrDefault()

`from..where` or `FirstOrDefault` in LINQ

Traditionally, when I've tried to get data for a user from a database, and I've used the following method (to some degree):
DbUsers curUser = context.DbUsers.FirstOrDefault(x => x.u_LoginName == id);
string name = curUser.u_Name;
string email = curUser.u_Email;
You can see that all I want to do is get the Name and Email, but it seems to me that this LINQ query is getting everything stored in the database of that user, bringing it back, then allowing me to get what I want.
I have been doing some research and have found the following alternative:
var current = from s in context.DbUsers
where s.u_LoginName == id
select new {
name = s.u_Name,
email = s.u_Email
};
foreach (var user in current)
{
//Stuff Here
}
Which would be better, if any at all? Is there a lighter method to use when I only want to retrieve a few results / data?
If you want to get only two fields, then you should project your entity before query gets executed (and in this case query gets executed when you call FirstOrDefault). Use Select operator for projection to anonymous object with required fields:
var user = context.DbUsers
.Where(u => u.u_LoginName == id)
.Select(u => new { u.u_Name, u.u_Email })
.FirstOrDefault(); // query is executed here
string name = user.u_Name; // user is anonymous object
string email = user.u_Email;
That will generate SQL like:
SELECT TOP 1 u_Name, u_Email FROM DbUsers
WHERE u_LoginName = #id
In second case you are doing projection before query gets executed (i.e. enumeration started). That's why only required fields are loaded. But query will be slightly different (without TOP 1). Actually if you will convert second approach to lambda syntax, it will be almost same:
var query = context.DbUsers
.Where(u => u.u_LoginName == id)
.Select(u => new { u.u_Name, u.u_Email });
// query is defined but not executed yet
foreach (var user in query) // executed now
{
//Stuff Here
}
And just to show complete picture, without projection you get all fields of first found user:
DbUsers user = context.DbUsers
.Where(u => u.u_LoginName == id)
.FirstOrDefault(); // query is executed here
string name = user.u_Name; // user is DbUsers entity with all fields mapped
string email = user.u_Email;
In that case user entity is not projected before query is executed and you'll get all fields of user loaded from database and mapped to user entity:
SELECT TOP 1 u_LoginName, u_Name, u_Email /* etc */ FROM DbUsers
WHERE u_LoginName = #id
The second is better. You only get the needed data from database so the network traffic is lighter.
You can have the same result with extension methods:
var user = context.DbUsers
.Where(x => x.u_LoginName == id)
.Select(x => new {...})
.FirstOrDefault();
If you need not whole entity, but some values from it, then use new {name = s.u_Name, email = s.u_Email}. Because, this object is much "lighter" for cunstruction.
When you get entity with FirstOrDefault, it' saved in DBContext, but you don't do anything with it.
So, i advice you to get only data you need.

Building a custom|progressive query in LINQ?

I have a page with five text boxes, each one representing a field in my database table and a search button:
If I were using SQL I could build my SQL statement depending on which fields have data in them.
However, I want to use LINQ, and I'm at a loss as to how to accomplish this. For instance, take a look at the query below:
var db = new BookDBDataContext();
var q =
from a in db.Books
where a.Title.Contains(txtBookTitle) &&
a.Author.Contains(txtAuthor) &&
a.Publisher.Contains(txtPublisher)
select a.ID;
The query above will return data where all the fields match data in the table. But, what if the user didn't enter an Author in the txtAuthor field? If I were building this as a query string, I could check each field for data and add it to the query string. Since this is LINQ, I can't dynamically change the search criteria, it seems.
Any advice would be greatly appreciated!
var db = new BookDBDataContext();
var q = (from a in db.Books
where a.Title.Contains(txtBookTitle));
if(!String.IsNullOrEmpty(txtAuthor))
{
q = q.Where(a => a.Author.Contains(txtAuthor));
}
if(!String.IsNullOrEmpty(txtAuthor))
{
q = q.Where(a => a.Publisher.Contains(txtPublisher));
}
var id = q.Select(a => a.ID);
from a in db.Books
where (string.isNullorWhiteSpace(search) || a.Title.Contains(search)) &&
(string.isNullorWhiteSpace(txtAuthor) || a.Author.Contains(txtAuthor) ) &&
(string.isNullorWhiteSpace(txtPublisher) || a.Publisher.Contains(txtPublisher))
select a.ID;

Method x has no supported translation to SQL

I want to write a query which should get an user object and the amount of messages the user has posted already. I did this the following way:
var query = (from u in _db.Repository<User>()
where u.IsDeleted != true
select new UserWithMessagecount()
{
User = u
MessageCount = GetUserMessageCount(u.Documents).Count(),
});
I'm using a method because some messages should be filtered out (in a dynamic way).
To keep things simple I'll post the function without sorting logic (which still produces the same error).
private EntitySet<Document> GetUserMessageCount(EntitySet<Document> set)
{
return set;
}
The error returned is:
Method 'x' has no supported translation to SQL.
Any ideas on how to fix this issue?
use this syntax instead:
var query = (from u in _db.Repository<User>()
let MessageCount = GetUserMessageCount(u.Documents).Count()
where u.IsDeleted != true
select new UserWithMessagecount()
{
User = u,
MessageCount = MessageCount
});
Linq-to-SQL will be trying to convert your entire statment into SQL, an of course there is no GetUserMessageCount() available.
You will need to take the results of the SQL query by enumerating it -- then apply the C# side logic.
What you need to do is to use grouping in your projection.
var query = from u in _db.Repository<User>()
where !u.IsDeleted
group u by u.UserId into g
select new UserWithMessageCount {
User = g.First(x => x.UserId == g.Key),
MessageCount = g.Sum(x => x.Messages.Count())
}
This should work.

Making a LINQ query better

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();

Categories

Resources