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();
Related
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
I need to include records of the child table with filtering on a condition Status='Completed'. I tried all the possible ways like Any(), IncludeFilter(), but I can't achieve what I'm looking for. I went through all the posts related to this query but no solution.
return await db.Jobs
.Where(x => x.Account == id &&
x.Status == "Completed")
.Include(x => x.Account1)
.Include(x => x.Bids.Select(s => s.Account1))
.ToListAsync();
I can filter on the main table Jobs, but I also need to filter the child table Bids. I short - I need jobs that are completed with bids whose status is Completed.
Include does not filter, it merely includes related data to be loaded eagerly along with the main entity being selected.
To get jobs where the account status is completed and has at least one bid with a status of completed:
db.Jobs.Where(x => x.Account == id
&& x.Status == "Completed"
&& x.Bids.Any(b => b.Status == "Completed"))
.Include(x => x.Account1)
.ThenInclude(b => b.Account1)
.ToListAsync();
The typical thing that trips people up when they return entities is that they'd say "but I only want back the Bids that are completed." This will return back all bids for jobs that are completed and have at least 1 completed bid.
To return back a filtered set of related data to those jobs, use ViewModels/DTOs to return to the views/API consumers rather than returning entities. Entities jobs are to reflect the data state. A job may include many bids, so the entity should reflect the complete data state for that job.
To return completed jobs with their completed bids, define POCO view model classes for the job, bid, etc. then use .Select() to project the entities into the view models:
var viewModels = db.Jobs
.Where(x => x.Account == id
&& x.Status == "Completed"
&& x.Bids.Any(b => b.Status == "Completed"))
.Select(x => new JobViewModel
{
AccountId = x.Account,
Account = x.Account1.Select(a => new AccountViewModel
{
AccountId = a.Id,
// ...
},
Bids = x.Bids.Where(b => b.Status == "Completed)
.Select(b => new BidViewModel
{
BidId = b.Id,
// ...
}).ToList()
}).ToListAsync();
I am trying to query a collection and child collection using EF 7. Here's the code:
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID )
.Include(g => g.Address.Where(p => p.AddressTypeID == 1))
.ThenInclude(p=> p.City)
.ToListAsync();
> Error CS1061 'IEnumerable<Address>' does not contain a definition for
> 'City' and no extension method 'City' accepting a first argument of
> type 'IEnumerable<Address>' could be found (are you missing a using
> directive or an assembly reference?) Contacts.DNX 4.5.1, Contacts.DNX
> Core 5.0
It works fine when I just use
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID )
.Include(g => g.Address)
.ThenInclude(p=> p.City)
.ToListAsync();
But this will load all the addresses for the customer where I only want the recent address for which the AddressTypeID is 1.
Any idea how to do this?
You can try anonymous projection, that will fetch translate your query into SQL.
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID)
.Select(cntct=> new
{
contact = cntct,
address = cntct.Address.Where(p => p.AddressTypeID == 1),
city = cntct.Address.Where(p => p.AddressTypeID == 1)
.Select(h=>h.City),
}.ToList();
You can't filter in Include. In any version of entity framework.
If you need to load a subset of the collection then you need to Join instead of using navigation property and filter whenever you need using Where clause
Like this (simplified, extra steps for readability):
var filteredAddresses = Addresses.Where(x=>x.AddressTypeId==1);
var customersWithAddress = Customers.Join(filteredAddresses, x=>x.Id,x=>x.CustomerId,(c,a)=> new {
Customer=c,
Address=a
});
Or if you need a single customer, assuming you have Customer navigation property in Address:
var addressWithCustomer = Addresses
.Where(x=>x.AddressTypeId==1 && x.CustomerId == customerId)
.Include(x=>x.Customer)
.Include(x=>x.City)
.Single();
A lot of times, it is better to approach queries which involve conditional nested entities, to start with the nested entity, apply the conditions to this nested fellow and then project out the parent entity, since it is always easier to reach to the parent entities from the nested enumerable ones. (many to one)
in our case, we can apply the filter out on the Address entity and then group it on the Contact entity.
var customerID = 86795;
var query = await _context.Addresses
.Where(a => a.Contact.CustomerID == customerID
&& a.Contact.RegistrationDate.Year == 2016
&& a.AddressTypeID == 1)
.Include(a => a.Contact)
.Include(a => a.City)
.GroupBy(a => a.Contact)
.Take(20) // by the way, you should apply some orderby for a predicatble Take
.ToListAsync();
and if you absolutely want a list of Contacts as the output of the above query, you can do this.
var contacts = query.Select(g =>
{
g.Key.Addresses = g.ToList();
return g.Key;
}).ToList();
// now you can work off the Contacts list, which has only specific addresses
This will basically give you a grouped list of all Contacts with CustomerID, and with those address types and registration years only. The important thing here is to iterate through the group to get the addresses, and not use the grouping.Key.Addresses navigation. (grouping.Key will be the Contact entity)
Also, I don't know if CustomerID is a primary key on the Contact entity, but if it is, it looks like you would just need a list of matching addresses for one Contact. In that case, the query would be:
var query = await _context.Addresses
.Where(a => a.Contact.CustomerID == customerID && a.AddressTypeID == 1)
.Include(a => a.Contact)
.Include(a => a.City)
.ToListAsync();
Include The Collection for Eager Load then use Any instead of Where ... to Select specific items in the child of the wanted entity.
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID )
.Include(g => g.Address.Any(p => p.AddressTypeID == 1))
.ThenInclude(p=> p.City)
.ToListAsync();
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.
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);