Entity Framework 6: Resolving user permissions by roles (IdentityUserRole) - entity-framework

I have a process in which an entity changes its state, i want to get the States that current user is allowed to transfer to, i have the following expression which when evaluated i'm getting an NotSupportedException with the message:
Unable to create a constant value of type
'Microsoft.AspNet.Identity.EntityFramework.IdentityUserRole'. Only
primitive types or enumeration types are supported in this context.
var user = db.Users.Single(...); //Successfully Retrieve the user
var states = db.StateTransitions
.Join( db.RoleStateTransitions
.Join( user.Roles,
rst => rst.RoleID,
ur => ur.RoleId,
( rst, ur ) => rst ),
wst => wst.StateTransitionID,
rst => rst.StateTransitionID,
( wst, rst ) => wst.FinalState ) ;
As you can see joining with user.Roles (collection of IdentityUserRole) is the problem. What is wrong with the previous expression?

The problem is that user.Roles is a local collection of objects. EF has no way of translating local objects into SQL (even though they are mapped objects). But you can make it work by reducing the local collection to primitive types:
var userRoleIds = db.Users.Where(u => u.UserId == id)
.SelectMany(u => u.Roles.Select(r => r.RoleId))
.ToList();
var states = from wst in db.StateTransitions
join rst in db.RoleStateTransitions
.Where(x => userRoleIds.Contains(x.RoleId))
on wst.StateTransitionID equals rst.StateTransitionID
select wst.FinalState;
Or maybe even one query:
var states = from wst in db.StateTransitions
join rst in db.RoleStateTransitions
on wst.StateTransitionID equals rst.StateTransitionID
join role in db.Roles
on rst.ROleId equals role.RoleId
where role.Users.Any(u => u.UserId == id)
select wst.FinalState;
(Assuming there is a many to many between User and Role).

Related

EF Core Update The LINQ expression 'x' could not be translated

I updated my .net core 2.2 to 5
I have a error about ef that
System.InvalidOperationException: 'The LINQ expression 'x' could not
be translated. Either rewrite the query in a form that can be
translated, or switch to client evaluation explicitly by inserting a
call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or
'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for
more information.'
public List<CustomerPageModel> GetCustomers(int AccountID)
{
return (from p in context.Customers
join f in context.Patients on p.ID equals f.CustomerID into ps
from t in ps.DefaultIfEmpty()
where p.AccountID == AccountID
select new CustomerPageModel
{
ID = p.ID,
Name = p.Name,
IsActive = p.IsActive,
TC = p.TC,
Surname = p.Surname,
Email = p.Email,
Address = p.Address,
Phone = p.Phone,
Note = p.Note,
AccountID = p.AccountID,
Pats = string.Join(",", ps.Select(x => x.Name)),
PatCount = ps.Count()
})
.GroupBy(p => p.ID)
.Select(g => g.First())
.ToList();
}
How can I convert that code?
Your problem line is:
Pats = string.Join(",", ps.Select(x => x.Name)),
Specifically, the string.Join method doesn't translate to SQL, so in previous versions of EF, it had to retrieve the data from the database then in-memory preform the string.Join function. Now EF explicitly tells you it can't run that on the database server - this is a breaking change but a design decision that tells you (the developer) that it may not have been running as efficiently as you thought it was...
To "fix" this, and based on your particular code example I'd recommend the following:
Add a pet names array property to your CustomerPageModel:
public string[] PetNames {get;set;}
And turn the Pets property into a readonly calculated string:
public string Pets { get => string.Join(",", PetNames); }
And change the problem line in your LINQ expression to:
PetNames = ps.Select(x => x.Name).ToArray()
I changed (lamb to subquery)
string.Join(",", ps.Select(x => x.Name))
to
string.Join(",", (from y in PatientList where y.CustomerID == p.ID select y.Name).ToArray()),
I made the group later (after tolist)
var test = mylist.GroupBy(p => p.ID)
.Select(g => g.First())
.ToList();
problem solved

How do I do an Left Outer Join in EF Core v3

I have been trying to use the GroupJoin statement to do a left outer join on two tables so that i can get a list of rows in table A which does not have any items in table B. However I keep getting an exception
Exception found: Processing of the LINQ expression 'DbSet<People>
.GroupJoin(
outer: DbSet<RiskGroup>,
inner: person => (Nullable<int>)person.Id,
outerKeySelector: risk => risk.AssessorId,
innerKeySelector: (person, risks) => new {
person = person,
risks = risks
.DefaultIfEmpty()
})' by 'NavigationExpandingExpressionVisitor' failed.
which seems to imply that it is not possible in EF Core v3. Does anyone know how to get around this problem or is my LINQ incorrect below:
var Ids = destContext.People.GroupJoin(destContext.RiskGroup,
person => person.Id,
risk => risk.AssessorId,
(person, risks) => new { person, risks = risks.DefaultIfEmpty() }).ToList();
I have trying to find all rows in the person table who doesn't have rows in the RiskGroup table.
I have struggled with this myself and I would like to see if this help you
OK for starters, I am going to keep this simple. Just find things are easier if we do
this is something that every developer comes across Company and address
the first table (Company) is the table that we could have multple records
the second table (Address) we have a single record for
company => company.AddressId,
address => address.AddressId,
So we are querying using the addressId of the address, I always used tableNameId, it makes things easier to read
So for the GroupJoin you now need output to be able to be used later in the query.
(company, address) = new (company, address)
And now you need a SelectMany - this is what screws everyone up
you need a record for when there is a null in Company
s.address.DefaultIfEmpty(),
and the output from the SelectMany
(s, address) => new { company = s.company, address });
Notice, very important - company must be s.company - not just company or the LEFT JOIN will not appear.
var a = context.Companys.GroupJoin(context.Addresses,
company => company.AddressId,
address => address.AddressId,
(company, address) => new { company, address })
.SelectMany(s => s.address.DefaultIfEmpty(),
(s, address) => new { company = s.company, address });
var outA = a.ToList();
for your specific issue
var a = destContext.People.GroupJoin(destContext.RiskGroup,
person => person.Id,
risk => risk.AssessorId,
(person, risk) => new { person, risk })
.SelectMany(s => s.risk.DefaultIfEmpty(),
(s, risk) => new { person = s.person, risk })
.Where(x => x.AssesorId == null);
var outA = a.ToList();
Try that out and see if you get what you need

EF CORE Select distinct grandchildren with many-to-may relationship

I'm trying to learn EF Core and hit this wall since I'm also fairly new to LINQ
Consider the model:
I'm trying to get all the distinct users from a single company;
The SQL statement would be something like this:
SELECT DISTINCT gau.AppUserId, au.Name, au.Id FROM Companies c
INNER JOIN Groups g ON g.CompanyId = c.Id
INNER JOIN GroupAppUsers gau ON gau.GroupId = g.Id
INNER JOIN AppUsers au ON gau.AppUserId = au.Id
Where c.Id = 40
Result:
How would I build this query like this? (Without the includes)
return await context.Companies
.Include(g => g.Groups)
.ThenInclude(au => au.AppUsers)
.ThenInclude(u => u.AppUser)
.SingleOrDefaultAsync(x => x.Id == id);
*Also, I'm not sure about the DB Model, I'm trying to avoid circular references but I think I should put Users linked with Companies instead of Groups, what do you think??
I'm trying to get all the distinct users from a single company
Rather than starting from companies and navigating to users, thus multiplying the users due to many-to-many relationship and then applying Disctinct operator, you could simply start from users and apply Any based criteria, thus eliminating the need of Disctinct at all.
Something like this (the DbSet / navigation property names could be different):
var companyUsers = await context.Users
.Where(u => u.UserGroups.Any(ug => ug.Group.Company.Id == id))
.ToListAsync();
Assuming your linking table (GroupAppUser) isn't modeled as an entity, something like:
var q = from c in db.Companies
from g in c.Groups
from u in g.AppUsers
select u;
or in Lambda form:
var q = db.Companies
.SelectMany(c => c.Groups)
.SelectMany(g => g.AppUsers);
Once you have the single Companies object, you can use the Navigation properties to get the AppUser objects:
return await context.Companies
.Include(g => g.Groups)
.ThenInclude(au => au.AppUsers)
.ThenInclude(u => u.AppUser)
.SingleOrDefaultAsync(x => x.Id == id)
.Groups.AppUsers.Distinct();

Table value parameter to joinable IQueryable

So for various reasons we need to send a large list of Ids to a EF6 query.
queryable.Where(x => list.Contains(x.Id));
is not ideal since it will create a huge were list.
So I was thinking, would it be possible some homehow to pass a table value parameter with the ids and get a IQueryable back that I can join against?
something like (Pseudo code)
var queryable = TableValueToIQueryable<MyTableValueType>(ids);
context.Set<MyEntity>().Join(queryable, x => x.Id, x.Value, (entity, id) => entity);
Is this possible somehow?
update: I have been able to use EntityFramework.CodeFirstStoreFunctions to execute a sql function and map the data to IQueryable<MyEntity>. it uses CreateQuery and ObjectParameters, can I use table value params somehow with ObjectParamters?
update2: Set().SqlQuery(...) will work with Table value parameters, but the resulting DbSqlQuery is not Joinable in SQL with a IQueryably so the result will be two connections and the join is done in memory
var idResult = Set<IdFilter>().SqlQuery("select * from GetIdFilter(#ids)", parameter);
var companies = idResult.Join(Set<tblCompany>(), x => x.Id, y => y.CompanyID, (filter, company) => company).ToList();
update3: ExecuteStoreQuery
((IObjectContextAdapter)ctx).ObjectContext.ExecuteStoreQuery<InvoicePoolingContext.IdFilter>("select * from dbo.GetIdFilter(#ids)", parameter)
.Join(ctx.Set<tblCompany>(), x => x.Id, y => y.CompanyID, (filter, company) => company).ToList();
Gives error:
There is already an open DataReader associated with this Command which
must be closed first.

Entity Framework Include with condition

I need to filter a dealer based on id and the uncomplete checkins
Initially, it returned the dealer based only on id:
// TODO: limit checkins to those that are not complete
return this.ObjectContext.Dealers
.Include("Groups")
.Include("Groups.Items")
.Include("Groups.Items.Observations")
.Include("Groups.Items.Recommendations")
.Include("Checkins")
.Include("Checkins.Inspections")
.Include("Checkins.Inspections.InspectionItems")
.Where(d => d.DealerId == id)
.FirstOrDefault();
As you can see the requirement is to limit the checkins.
Here's what I did:
var query = from d in this.ObjectContext.Dealers
.Include("Groups")
.Include("Groups.Items")
.Include("Groups.Items.Observations")
.Include("Groups.Items.Recommendations")
.Include("Checkins.Inspections")
.Include("Checkins.Inspections.InspectionItems")
.Where(d => d.DealerId == id)
select new
{
Dealer = d,
Groups = from g in d.Groups
select new
{
Items = from i in g.Items
select new
{
Group = i.Group,
Observations = i.Observations,
Recommendations = i.Recommendations
}
},
Checkins = from c in d.Checkins
where c.Complete == true
select new
{
Inspections = from i in c.Inspections
select new
{
InspectionItems = i.InspectionItems
}
}
};
var dealer = query.ToArray().Select(o => o.Dealer).First();
return dealer;
It works.
However, I am not convinced I am doing the right thing.
What is the best way to accomplish what I did? A stored procedure maybe?
I am not sure I even have to use Include clause anymore
Thank you.
If you want to load filtered relations with single query you indeed have to execute such projection but you don't need those calls to Include. Once you are building projections includes are not use - you have returned data under your control.
Stored procedure will help you only if you fall back to plain ADO.NET because stored procedures executed through Entity framework are not able to fill related entities (only flattened structures).
Automatic fixupu mentioned by #Andreas requires multiple database queries and as I know it works only if lazy loading is disabled because proxied object somehow doesn't have information about fixup and it still has its internal flags for each relation as not loaded so when you access them for the first time they still execute additional query.
Maybe you can make use of the relation fixup mechanism in the EF ObjectContexts. When you do multiple queries in the same context for entities, that are related by associations, these are resolved.
Assuming your association between Dealers and Checkins is 1:n with navigation properties on each side, you could do like:
var dealer = yourContext.Dealers
.Where(p => p.DealerId == id)
.FirstOrDefault();
if(dealer != null)
{
yourContext.Checkins
.Where(c => c.Complete && c.DealerId == dealer.DealerId)
.ToList();
I have not tested this by now, but since EF recognises that the Checkins, it inserts into the context by the second query belong to the dealer from the first query, corresponding references are created.
#Andreas H:
Awesome, thank you a lot.
I had to adjust your suggestion like this and it worked:
var dealer = this.ObjectContext.Dealers
.Include("Groups")
.Include("Groups.Items")
.Include("Groups.Items.Observations")
.Include("Groups.Items.Recommendations")
.Where(p => p.DealerId == id).
FirstOrDefault();
if (dealer != null)
{
this.ObjectContext.Checkins
.Include("Inspections")
.Include("Inspections.InspectionItems")
.Where(c => !c.Complete && c.Dealer.DealerId == dealer.DealerId)
.ToList();
}
return dealer;
I still have to use the Include otherwise it won't return the referenced entities.
Note also that Dealer.Groups are unrelated to the Dealer.Checkins.
So if there's no checkins satisfying the condition, Groups still need to be returned.
It's interesting to note that at first, I put the two include for checkins to the dealer
var dealer = this.ObjectContext.Dealers
.Include("Groups")
.Include("Groups.Items")
.Include("Groups.Items.Observations")
.Include("Groups.Items.Recommendations")
.Include("Checkins.Inspections")
.Include("Checkins.Inspections.InspectionItems")
.Where(p => p.DealerId == id).
FirstOrDefault();
if (dealer != null)
{
this.ObjectContext.Checkins
.Where(c => c.Complete && c.DealerId == id)
.ToList();
}
return dealer;
but it returned all the Checkins including those which are not complete.
I don't understand exactly why the latter doesn't work but the former does, how are the entities are resolved. I somehow can intuit that the former returns all data.
Your accepted solution will generate multiple database queries. As Ladislav Mrnka said a projection is the only way to pull your result with one query. The maintance of your code indeed hard. Maybe you could use an IQueryable-Extension that builds the projection dynamically and keep your code clean:
var query = this.ObjectContext.Dealers.SelectIncluding( new List<Expression<Func<T,object>>>>(){
x => x.Groups,
x => x.Groups.Select(y => y.Items),
x => x.Groups.Select(y => y.Items.Select(z => z.Observations)),
x => x.Groups.Select(y => y.Items.Select(z => z.Recommendations)),
x => x.Checkins.Where(y => y.Complete==true),
x => x.Checkins.Select(y => y.Inspections),
x => x.Checkins.Select(y => y.Inspections.Select(z => z.InspectionItems))
});
var dealer = query.First();
return dealer;
You can find the extension at thiscode/DynamicSelectExtensions on github