I've got this horrible EF code in a code base and I am scratching my head trying to figure out what it's trying to do. If I were looking to put this in a stored procedure instead how would this query look in SQL?
public void LoadNotifyListItems(UserProfileModel user, DbContext pc)
{
var allowedEvents = (from r in user.Roles
join near in pc.NotifyEventAllowedRoles on r.RoleId equals near.RoleId
join ne in pc.NotifyEvents on near.NotifyEventId equals ne.Id
select ne).Distinct();
var NotifyListItems = from ne in allowedEvents
join pune in pc.UserNotifyEvents
on new { NotifyEventId = ne.Id, UserId = user.Id }
equals new { pune.NotifyEventId, pune.UserId }
into loj
from pune in loj.DefaultIfEmpty()
select new NotifyListItem
{
Event = ne,
Value = pune ?? new UserNotifyEvent
{
NotifyEventId = ne.Id
}
};
}
The issue I am having is the entirety of pc.UserNotifyEvents table is being queried. Monitoring the DB, EF is making this query when the LoadNotifyListItems method is being run:
[Extent1].[UserId] AS [UserId],
[Extent1].[NotifyEventId] AS [NotifyEventId],
[Extent1].[NotifyPrimaryEmail] AS [NotifyPrimaryEmail],
[Extent1].[NotifyAlternateEmail] AS [NotifyAlternateEmail],
[Extent1].[NotifySmsNumber] AS [NotifySmsNumber],
[Extent1].[Threshold1] AS [Threshold1],
[Extent1].[Threshold2] AS [Threshold2]
FROM [UserNotifyEvents] AS [Extent1]
This isn't needed and its ingesting 200,000 rows everytime. I am thinking of moving the query to a Stored Proc and pass in userId as a parameter instead
Please let me know if there is not enough information to go off here.
The first part of the query is joining a list of roles in memory (user.Roles) with an IQueryable from the DbContext. This is probably the cause of your issue. You should use the pc.Roles property instead, and introduce a where clause to filter by the id of the provided user.
If user info is in same database as other tables
Pass the user id to the stored proc. Inside the sproc, join user-roles to the other tables WHERE userid = passed-in parameter.
If user info is NOT in same database as other tables
First, get a list of role ids for the user.
var roleIds = user.Roles.Select(i=>i.RoleId).Distinct().ToList();
Then you're going to pass that list of role IDs to your stored proc. Try something like this Stack Overflow article, which tells you how to pass those role IDs as distinct strings to a stored proc.
The rest of this appears to be regular table joins within the database. The most critical part is getting those roles IDs for the user.
Related
That title is not very good, so consider the following. I have five tables:
User {
Id,
ProfileId // -> Profiles.Id
}
Profile {
Id
}
ProfilePermissionSets {
ProfileId // -> Profiles.Id
PermissionSetId // -> PermissionSets.Id
}
UserPermissionSets {
UserId // -> Users.Id
PermissionSetId // -> PermissionSets.Id
}
PermissionSets {
Id
}
Permissions {
Id,
PermissionSetId // -> PermissionSets.Id
}
And I want get all of the permissions for a user that are directly linked to it or indirectly through the profile. The not-quite-there SQL I've come up with so far is this:
SELECT [Pe].[Controller],
[Pe].[Action]
FROM [PermissionSets] AS [PS]
JOIN [UserPermissionSets] AS [UPS]
ON ([UPS].[PermissionSetId] = [PS].[Id])
JOIN [Users] AS [U]
ON ([U].[Id] = [UPS].[UserId])
JOIN [Profiles] AS [P]
ON ([P].[Id] = [U].[ProfileId])
JOIN [ProfilePermissionSets] AS [PPS]
ON ([PPS].[ProfileId] = [P].[Id])
JOIN [Permissions] AS [Pe]
ON ([Pe].[PermissionSetId] = [PS].[Id])
WHERE [U].[Id] = 4;
It returns back a correct count of rows, but it's repeating the controller or action over and over, so it's wrong. I'm hoping someone can help me correct it to show all of the distinct permission sets for the user. Ideally, I'd like to also change it so that it's all discovered starting at the user because that is what I have access to in the method I need to do this (the object is an Entity Framework class named User and will be browsed using LINQ).
UPDATED because I forgot that I really wanted the permissions not the permission sets.
Try this SQL
SELECT [Pe].[Controller],
[Pe].[Action]
FROM [Users] AS [U]
LEFT OUTER JOIN [UserPermissionSets] AS [UPS]
ON ([UPS].[UserId] = [U].[Id])
LEFT OUTER JOIN [ProfilePermissionSets] AS [PPS]
ON ([PPS].[ProfileId] = [U].[ProfileId])
LEFT OUTER JOIN [Permissions] AS [Pe]
ON ([Pe].[PermissionSetId] = [UPS].[PermissionSetId])
OR ([Pe].[PermissionSetId] = [PPS].[PermissionSetId])
WHERE [U].[Id] = 4;
So, messing around on LINQPad, I came up with this as far as the LINQ query:
user.PermissionSets.Union(user.Profile.PermissionSets).SelectMany(
ps =>
ps.Permissions.Select(
p =>
p.Controller + "." + p.Action));
And it produces what I want, BUT it does it by composing the results of a bunch of SQL queries. The biggest impact comes from profiles that have multiple permission sets, like say the Administrator. I don't think there's a way around it, and I only have a User object to work with, so I'm ok with the excess SQL queries, at least for now.
I've found plenty of info on how to select multiple result sets with stored procedures but nothing substantial on how to do so with a linq query.
For example, I can do sub-queries that return mulitple sets of results with something like
var query = (from school in context.Schools
where school.id == someId
select new
{
subSetA = (from student in context.Students
select student).ToList(),
subSetB = (from building in context.Buildings
select building).ToList(),
}).First();
query.subSetA; //Access subSetA
query.subSetB; //Access subSetB
Which works fine, but what if I just want to select both subSetA and subSetB without querying against the school table? I want to select two separate sets of data that gets sent to the server in one query.
Any information as to how to do this with EF 6 would be great.
Well, I'm sure there are many ways to do this, but if you want to avoid introducing a third DbSet into the mix...
var query = (from s in context.Students.Take(1)
select new
{
subSetA = context.Students.ToList(),
subSetB = context.Buildings.ToList(),
})
Then, you can use query.ToList() or maybe using query.Load() and working with context.Students.Local, etc. would work.
I have a stored procedure which returns me some dates, as well as an Id which related to a specific row in a table.
Basically, I am getting a list of all scheduled transactions for all accounts within an account portfolio.
The stored procedure returns a row with an Id (for the scheduled transaction), and some dates which I have minded within the proc.
If my query began with:
from p in Context.scheduled_transactions
then this plan would have worked. But I don't want to get the items like that, because in the proc, I am doing a lot of work to create business dates etc. So, instead of bring back the EF model - my proc just brings back the ID. I was HOPING to do something like this:
var trans = (from p in Context.get_scheduled_payments_by_portfolio(portfolioId)
.Include("account")
.Include("cost_centre")
.Include("z_account_transaction_type")
.Include("z_payment_frequency_type")
.Include("transaction_sub_category")
.Include("transaction_sub_category.transaction_category")
.Include("third_party")
select p).ToList();
But, the EF can't use 'Include' as it doesn't know what I am bring back. Although the id is called 'scheduled_transaction_id' in the proc - EF doesn't know that (understandably).
Is there a way I can tell EF that the ID is for a scheduled_transaction_model - and then use the 'Include'?
Maybe I need to just call the proc, which returns me a list of my objects, which has the scheduled_transaction_id, and all the dates I calculated in the proc, and then somehow, use that List<> in another linq query that can join the other tables?
EDIT:
I might be onto something! This doesn't show a syntax error. Just need to create a new Type... Playing with this:
var trans = (from p in Context.get_scheduled_payments_by_portfolio(portfolioId)
join st in Context.scheduled_transaction
.Include("account")
.Include("cost_centre")
.Include("z_account_transaction_type")
.Include("z_payment_frequency_type")
.Include("transaction_sub_category")
.Include("transaction_sub_category.transaction_category")
.Include("third_party")
on p.scheduled_transaction_id equals st.id
select p).ToList();
var ids = Context.get_scheduled_payments_by_portfolio(portfolioId).ToList();
var trans = (from p in Context.scheduled_transaction
.Include("account")
.Include("cost_centre")
.Include("z_account_transaction_type")
.Include("z_payment_frequency_type")
.Include("transaction_sub_category")
.Include("transaction_sub_category.transaction_category")
.Include("third_party")
where ids.Contains(p.id)
select p).ToList();
Try Contains() method which will translated into SQL's IN(,,) statement.
The answer was, join the proc to the table I was using, and then I can use the .Include()
var trans = (from p in Context.get_scheduled_payments_by_portfolio(portfolioId)
join st in Context.scheduled_transaction
.Include("account")
.Include("cost_centre")
.Include("z_account_transaction_type")
.Include("z_payment_frequency_type")
.Include("transaction_sub_category")
.Include("transaction_sub_category.transaction_category")
.Include("third_party")
on p.scheduled_transaction_id equals st.id
select new {st, p}).ToList();
And then with the new type, I can itterate through the list, and build my objects.
I have 2 tables:
Users data with PK UserId
UsersOrders with FK UserID
I want to do LINQ query to give me a list of users with member that is the list of the user orders.
I've tried:
var myNestedData = (from ub in db.Users
join ah in db.UsersOrders on ub.UserId equals ah.UserId
into joined
from j in joined.DefaultIfEmpty()
group j by new { ub.UserId, ub.UserName, ub.UserPhone, ub.Approved } into grouped
where grouped.Key.Approved == true
select new
{
UserId= grouped.Key.UserId,
UserName = grouped.Key.UserName,
UserPhone = grouped.Key.UserPhone,
Orders = grouped
}).ToList();
The problem is that I'm getting inside Orders an Object<a,UsersOrders>, which I don't expect.
Is this the right way to approach a solution in terms of performance?
It seems to me you should be able to keep this simple:
var myNestedData = (from u in db.Users
where u.Approved == true
select new
{
User = u,
Orders = u.UserOrders
}).ToList();
You are trying to get a list of all the Approved users and that users orders. Am I missing something?
ALso the name of u.UserOrders will depend on how you have configured your mapping.
Change it to
Orders = grouped.ToList()
To see if it's a potential performance issue, look at the SQL that is generated to see if it is an N+1 scenario (meaning it runs one query for the parent record and N queries for the child records.
I say potential because while may not be the fasted method, it may not be an issue. If there is no noticeable performance problems I wouldn't worry about it and instead focus on making the app better by improving the experience, which may include improving the perfornamce of other areas of the map.
I have two identical queries (but looking at different tables), using Entity-Framework, calling the Oracle database, one will be able to find the table, but the other does not.
using(CarContainer Cars = new CarContainer()) {
var carModel = from c in temp.Cars.OfType<BMW>() orderby c.ID select c.MODEL;
}
using(CarContainer Cars = new CarContainer()) {
var carModel = from c in temp.Cars.OfType<BENTLEY>() orderby c.ID select c.MODEL;
}
When I run the second query, it gives me "Oracle.DataAccess.Client.OracleException: ORA-00942: table or view does not exist"
I opened up SQL Plus, using the same credentials, and did a select * from BENTLEY and it gave me the table.
When I used my grants.sql file, one of the grant commands did not get successfully passed to the Oracle Database.
I added in GRANT ALL ON CHOWNER.BENTLEY TO ADMINROLE; and it worked again!