EF Complex Query Join - c#

The query shown below is very straight forward, it'll simply pull up tasks for a specified customer. What I'd now like to be able to do is take a UserId that is passed into this function and validate that the user has permission to view the task.
var dbData = await db.Tasks
.Where(a => a.CustomerId == customerId)
.OrderBy(a => a.CreatedDateTime).ToListAsync();
There is a property in the Tasks table for OrganizationId. A User can belong to n+1 Organizations via a UserOrganizations table. What is the best way to take the known UserId and validate the the Task.OrganizationId is one of the User's?

If the relations are not already properties on the Tasks class, you can write your join in query-syntax. Something along these lines:
var dbData = await (from t in db.Tasks
join uo in UserOrganizations on t.OrganizationId equals uo.OrganizationId
join u in Users on uo.UserId equals u.UserId
where t.CustomerId == customerId && u.UserId == theUserId
order by t.CreatedDateTime
select t).ToListAsync();
Depending on how your data classes where generated, you might already have navigation properties on the Tasks class, allowing you to do:
var dbData = await db.Tasks
.Where(a => a.CustomerId == customerId && a.Organization.UserOrganizations.Any(uo => uo.UserId == theUserId)
.OrderBy(a => a.CreatedDateTime).ToListAsync();

var dbData = await db.Tasks
.Where(a => a.CustomerId == customerId
&& a.Organization.Users
.Any(u=>u.UserId == customerId)))
.OrderBy(a => a.CreatedDateTime).ToListAsync();
This is given the foreign keys are setup and relationships are navigable through the entities.

Related

C# LINQ expression cannot be translated when summing group joins

I have two tables: users and transactions. The transactions table stores user wallet transactions (+ balance, - balance) etc. I'm trying to obtain a list of users based on the current balance of their wallet ordered by largest first. (I can get the total balance by summing the Amount column in the Transactions table).
var query = from u in _context.users
join t in _context.Transactions on u.id equals t.UserId into gj
from txn in gj.DefaultIfEmpty()
where txn.TransactionStatus == Transaction.Status.Success && !u.Deleted.HasValue
select new
{
balance = gj.Sum(a => a.Amount),
user = u,
};
var result = await query.OrderByDescending(t => t.balance).Skip(offset).Take(range).ToListAsync();
I am getting the error:
The LINQ expression 'gj' could not be translated.
This is the equivalent SQL I'm trying to achieve:
SELECT balance, u.* FROM
(SELECT COALESCE(SUM(Amount), 0) as balance, u.id
FROM dbo.users u
LEFT JOIN dbo.Transactions t ON(u.id = t.UserId AND t.TransactionStatus = 0)
WHERE u.Deleted IS NULL
GROUP BY u.id) as tbl
JOIN dbo.users u ON(u.id = tbl.id)
ORDER BY balance DESC
Starting from users and using the navigation props to go through to Trans should make this trivial. Give something like this a go:
users.Where(u => u.Deleted == null)
.Select(u => new {
User = u,
Balance = u.Transactions.Where(t => t.Status == 0).Sum(t => t.Amount)
})
.OrderByDescending(at => at.Balance);
If the logic truly is "list all users but only add up transactions for users that are non deleted, and show deleted users as 0 balance" then:
users.Where(u => u.Deleted == null)
.Select(u => new {
User = u,
Balance = u.Deleted != null ? 0 : u.Transactions.Where(t => t.Status == 0).Sum(t => t.Amount)
})
.OrderByDescending(at => at.Balance);
Try not to write EF queries like "right, in SQL I would have this table and join that table, and it'd be a left join, and that would be summed... so I'll tell EF to do this context set join that context set, and it's a left join, and sum..." - EF will write joins for you; express your requirements in terms of how you want the C# object graph to be manipulated and let EF do the conversion to SQL how it can; use the navigation props between entities so it can work out how you want to bring your data together and arrange the necessary joins. It seldom needs micromanaging in a SQL-ey flavored approach

How to write Entity Framework query to be able to join to the ICollection?

I'm trying to work out the Entity Framework syntax to return the User.Name, User.Email for the given Profile.email.
1 profile can have N categories. 1 category can have 1 User.
Profile - ID, email, Name, CreatedDate
Category - ID, ProfileId, Name, UserID
User - ID, Name, Email
In SQL I would write:
SELECT U.NAME, U.EMAIL
FROM PROFILE P
JOIN CATEGORY C ON P.ID = C.PROFILEID
JOIN USER U ON C.USERID = U.ID
WHERE P.EMAIL = 'SOME#EMAIL.COM'
Here is what I tried:
var data = await _context.Profiles
.AsNoTracking()
.Where(p => p.Categories.Users.email == 'some#email.com')
.Select(u => new
{
UName = u.Name,
UEmail = u.Email
}).ToListAsync();
The problem is that p.Categories is an ICollection, so I don't know how to proceed because p.Categories doesn't give me access to the .Users. I can write p.Categories.Where.... but I'm not sure how to proceed.
Instead of starting with _context.Profiles. should I be starting with _context.Users.?
Can someone help me on how to think about the approach when writing the Entity Framework query?
If I understood your model correctly, this should work:
var data = await _context.Categories.AsNoTracking()
.Where(c=>c.Profile.email == "some#email.com")
.Select(c=>new {
UName=c.User.Name,
UEmail=c.User.Email
}).ToListAsync();
Ofcourse this requires your model to have navigation properties set.
So just start your query the Categories in LINQ form:
from c in _context.Categories
where c.Profile.Email == someEmail
select new { c.User.Name, c.User.Email }
or in Lambda form:
_context.Categories
.Where( c => c.Profile.Email == someEmail )
.Select( c => new {c.User.Name, c.User.Email}
or start from Profiles and use SelectMany, whose LINQ form looks like
from p in _context.Profiles
from c in p.Categories
where p.Email == someEmail
select new {c.User.Name, c.User.Email}
or in Lambda form:
_context.Profiles
.Where(p => p.Email == someEmail)
.SelectMany(p => p.Categories)
.Select( c => new {c.User.Name, c.User.Email} )

SQL to Entity Framework - Not Exists Count & Group by in statement

Completely new to Entity Framework, but I am extending some work by another developer and struggling to convert part of my SQL into the correct syntax.
A simplified version of my SQL query: there are 2 tables, a list of UserDetails and the TaskInstance which tracks whether they have been invited to complete a task and the status of that task, whether its completed or pending.
I wish to return UserID's for users registered within last 28 days and then exclude from that list (not exists) anyone who has completed the task or has already been invited to complete the task 3 or more times.
SELECT
a.UserID
FROM
UserDetails
WHERE
(a.RegisteredDate >= DATEADD(DAY, -28, GETDATE()
AND IsAdminDisabled != true)
AND NOT EXISTS (
--exclude anyone who has completed this task
SELECT b.UserID
FROM TaskInstance b
WHERE b.UserID = a.UserID
AND (b.taskName= 'XXXXX' AND b.Status = 'Completed'))
AND NOT EXISTS (
--exclude anyone who has been invited to complete this task more then 3 times
SELECT c.UserID
FROM TaskInstance c
WHERE a.UserID = c.UserID
AND c.taskName= 'XXXXX'
GROUP BY c.UserID
HAVING COUNT(c.UserID) >= 3)
My code so far looks like this, I appreciate this may already have some errors, which I will work through and resolve but its the Count part which has me lost.
I want to exclude from my results so far, any UserIDs that appear in the TaskInstance table 3 or more times and eventually return a list of only UserID's.
var eligibleUsers = await context.UserDetails
.Where(a => (a.RegisteredDate >= DateTime.Now.AddDays(-28))
.Where(a => a.IsAdminDisabled != true && !context.TaskInstance.Any(si=>si.taskName== query.taskName && a.UserID== si.UserID && si.status = 'Completed'))
.Where(a => (!context.TaskInstance.Any(si => si.TaskInstance== query.AppName && si.UserID== a.UserID))) //should check for count >=3 grouped by UserID
.Select(a=>a.UserID)
.ToListAsync();
The answer to most questions like this is Navigation Properties.
var eligibleUsers = await context.UserDetails
.Where(a => (a.RegisteredDate >= DateTime.Now.AddDays(-28))
.Where(a => !a.IsAdminDisabled)
.Where(a => !a.Tasks.Any(si=>si.taskName== query.taskName && si.status = 'Completed'))
.Where(a => a.Tasks.Count(si => si.TaskInstance == query.AppName)<3))
.Select(a=>a.UserID)
.ToListAsync();

Linq Query with double sub queries

I am struggling converting the following SQL query I wrote into Linq. I think I'm on the right track, but I must be missing something.
The error I'm getting right now is:
System.Linq.IQueryable does not contain a definition for .Contains
Which is confusing to me because it should right?
SQL
select Users.*
from Users
where UserID in (select distinct(UserID)
from UserPermission
where SupplierID in (select SupplierID
from UserPermission
where UserID = 6))
LINQ
var Users = (from u in _db.Users
where (from up in _db.UserPermissions select up.UserID)
.Distinct()
.Contains((from up2 in _db.UserPermissions
where up2.UserID == 6
select up2.SupplierID))
select u);
EDIT: I ended up going back to SqlCommand objects as this was something I had to get done today and couldn't waste too much time trying to figure out how to do it the right way with Linq and EF. I hate code hacks :(
I think there is no need to do a distinct here (maybe I am wrong). But here is a simpler version (assuming you have all the navigational properties defined correctly)
var lstUsers = DBContext.Users.Where(
x => x.UserPermissions.Any(
y => y.Suppliers.Any(z => z.UserID == 6)
)
).ToList();
Above if you have UserID field in Supplier entity, if it is NOT you can again use the navigational property as,
var lstUsers = DBContext.Users.Where(
x => x.UserPermissions.Any(
y => y.Suppliers.Any(z => z.User.UserID == 6)
)
).ToList();
Contains() only expects a single element, so it won't work as you have it written. Try this as an alternate:
var Users = _db.Users
.Where(u => _db.UserPermissions
.Select(x => UserID)
.Distinct()
.Where(x => _db.UserPermissions
.Where(y => y.UserID == 6)
.Select(y => y.SupplierID)
.Contains(x))
);
I didn't try on my side but you can try using the let keyword:
var Users = (from u in _db.Users
let distinctUsers = (from up in _db.UserPermissions select up).Distinct()
let subQuery = (from up2 in _db.UserPermissions
where up2.UserID == 6
select up2)
where
distinctUsers.SupplierID== subQuery.SupplierID &&
u.UserID==distinctUsers.UserID
select u);

Lambda expression filtering included related data with Entity Framework

I need to filter only the visibles products from a category, but it's not working.
Category category = db.Categories
.Include(c => c.Products.Where(p => p.IsVisible))
.First(c => c.CategoryID == id);
Error:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
UPDATE
var result = (from c in db.Categories
where c.CategoryID == id
select new
{
CategoryID = c.CategoryID,
Description = c.Description,
Products = (from p in db.Products
where p.IsVisible
&& p.CategoryID == c.CategoryID
orderby p.DateSent descending
select p)
}).FirstOrDefault();
but now i need to cast the anonymousType to Category
Your query doesn't make sense if you want:
the visibles products from a category
If you genuinely want the visible products, try this:
var visibleProducts = db.Categories
.Where(c => c.CategoryID == id)
.Select(c => c.Products.Where(p => p.IsVisible));
Note: untested
Maybe something like:
var category = db.Products.Where(p=>p.IsVisible && p.CategoryID == id).Include("Category").ToList().Select( p=> p.Category).Distinct();
It may not be ideal because of the ToList... but I can see no other way right now.
Maybe you could change the Distinct into a FirstOrDefault()...
var category = db.Products.Where(p=>p.IsVisible && p.CategoryID == id).Include("Category").ToList().FirstOrDefault().Category;
Not tested either...

Categories

Resources