Single database call when passing in a list to LINQ - c#

I get passed in an array of email addresses and for each email address, I need to return numTotal and numActive from members related to that email address.
Traditionally, I would query from the member table and filter where the member's email address exists in the passed in email array. However, this would only return me the list of email address with counts for the emails that exist in the member table. Instead, I need a record for each email address provided regardless if a member has the email address.
My solution is to loop over the email array and then do a subquery. This works, and I'm able to get what I need, but when I profile the calls, I see that EF is running a separate query for each email address/subquery.
var result = (from email in emailAddresses
select new EmailAddressStats {
Total = membersQueryable.Any(m => m.Email == email),
Active = membersQueryable.Any(m => m.Email == email && m.IsActive)
}).ToList();
So if I pass in 3 email addresses, I see 6 separate queries (3 email addresses * 2 subqueries).
I've tried making the array queryable using .AsQueryable(), but I'm still getting the same result.
Ideally I would like EF to generate something like:
SELECT ([totalSubquery]) AS Total,
([activeSubquery]) AS Active
FROM ????
WHERE EXISTS ('email1#domain.com', 'email2#domain.com', 'email3#domain.com' )
NOTE: I am using Entity Framework 4.

I don't have EF4 installed, but you should be able to something like this to only perform a single query.
List<EmailAddressStats> GetEmailAddressStats(string[] emailAddresses)
{
var returnList = emailAddresses.Select(e => new EmailAddressStats { Email = e })
.ToList();
// I don't know what your context is actually called so I'm just calling it MemberDatabaseContext)
using (var dbContext = new MemberDatabaseContext())
{
// Query the database once with a single query to get all of the relevant members
var membersWithEmailAddress = dbContext.Members.Where(m => emailAddresses.Contains(m.Email))
.Select(m => new { Email = m.Email, IsActive = m.IsActive })
.ToList();
// Update the email stats by analyzing the member info in memory
foreach (var emailStat in returnList)
{
emailStat.Total = membersWithEmailAddress.Count(m => m.Email == emailStat.Email);
emailStat.Active = membersWithEmailAddress.Count(m => m.Email == emailStat.Email && m.isActive);
}
}
return returnList;
}
(sorry that this is in Fluent syntax instead of the Query Expression syntax, but I only know how to do this in Fluent syntax)

Related

linq: aggregate into single line (flatten)

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.

Linq selecting sub results using select

I'm not sure if I'm looking to use a union here or if this will work.
var users =
db.AspNetUsers.Where(
x => x.AspNetRoles.Select(y => y.Id).Contains("4575754-799a-4bhe-8caf-00002344b7a6"))
.Select(d => d.PatientId);
var patients = from patient in db.Patients
where patient.LastName == lastName && clinicId == patient.ClinicId && users.Contains(patient.Id)
select new
{
patient.LastName,
patient.AspNetUsers.First(d=>d.Email),
patient.Sex
};
This works when I get everything from patients, but when I try to get the email address from the AspNetUsers object I get the message 'can not convert expression type string to return type bool'
When I am in quickwatch, I can see the entire AspNetUsers object returned inside the patient object, but why can't I easily select a single property out of that list?
This expression in LINQ
patient.AspNetUsers.First(d=>d.Email)
means "get me the first AspNetUser whose boolean attribute Email is true". Of course this makes no sense, because Email is a string, not a boolean.
What you need is
patient.AspNetUsers.First().Email

`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.

How to write some LINQ which excludes related records in a link table?

I have 2 tables
Client
ClientReport
I need to write some LINQ that lists all Clients that are not in the ClientReport table ie I need to list all Clients not associated with a particular report.
This is my starting point:
var ClientList = db.StdClient.ToList();
Many thanks.
EDIT:
Sorry forgot one important requirement and that is that the filter needs to be report specific. ReportId is fed in as a parameter into the Action
EDIT2:
var ClientList = db.StdClient
.Where(c => !db.StdClientReport
.Any(cr=>(
(cr.StdClientId == c.Id)
&& (cr.ReportId==ReportId)
)
)
).ToList();
This is assuming that there is a one-to-many relation between Client and ClientReport via either a navigation property on Client and/or a ClientId property on ClientReport.
If there is a navigation property from Client to ClientReport:
var clientList = db.Client.Where(c => !c.ClientReports.Any());
or
int id = 7; // report ID we're looking for
var clientList = db.Client.Where(c => !c.ClientReports
.Any(cr => cr.ReportId == id)
);
if it's specific to one report ID.
Otherwise
var clientList = db.Client.Where(c => !db.StdClientReport
.Any(cr=>cr.ClientId == c.Id)
);
You could possibly also use .Except but i've not tested it
Something like
var noReport = ClientList.Except(ClientReportList);

Linq to NHibernate: how to get a list of child objects without having the lists nested

I have a Linq to NHibernate query as follows:
var profile =
from UserProfile up in _Session.Query<UserProfile>()
.Fetch(x=>x.Messages)
where up.UserName == userName
select up.Messages;
this returns an IQueryable<IList<UserMessage>> which I then have to run a SelectMany() on. I'd prefer if I could just return an IQueryable<UserMessage> object instead, especially as the query will never return more than one user profile. Can this be done, or am I stuck with the extra step?
If you map the other side of the navigation e.g have a UserProfile property on the UserMessage class, your can start from UserMessage:
var messages =
from UserMessage um in _Session.Query<UserMessage>()
where um.UserProfile.UserName == userName
select um;
Otherwise you need to use SelectMany() to get a flattened out list.
Could you query the messages table directly and use the reverse association?
IQueryable<Message> messages = ...;
var filtered = from m in messages
where m.UserProfile.UserName == userName
select m;
Also, if you're willing to forgo query syntax you could make this shorter with:
var profile = _Session.Query<UserProfile>()
.Where(up => up.UserName == userName)
.SelectMany(up => up.Messages);

Categories

Resources