EF. How to include only some sub results in a model? - entity-framework

I'm trying to select list of Users and for each User JobTitle in correct language depended of strLang selected by user.
Something like that:
IList<User> myData;
myData = Context.Users.Where(u => u.Location == strLocation)
.Include(u => u.JobTitles.Where(e => e.Language == strLang))
.ToList();
But it seems Include doesn't like Where clause

You can't conditionally include only a few entities of a related collection, so you should use projection to get the stuff you need:
IList<User> myData;
var temp = Context.Users.Where(u => u.Location == strLocation)
.Select(u => new
{
User = u;
Locations = u.JobTitles.Where(e => e.Language == strLang));
});
foreach(var t in temp)
{
User user = t.User;
user.Locations = t.Locations;
myData.Add(user);
}

You cannot do it by using the "Include" method since it only take naviation properties.
Disclaimer: I'm the owner of the project EF+ on github.
EF+ Query IncludeFilter allow you to easily filter related entities:
// using Z.EntityFramework.Plus; // Don't forget to include this.
IList<User> myData;
myData = Context.Users.Where(u => u.Location == strLocation)
.IncludeFilter(u => u.JobTitles.Where(e => e.Language == strLang))
.ToList();
You can find the project here
You can find the documentation here
Behind the code, IncludeFilter do exactly like Alexander answer by using a projection.

Related

EF Core 5.0 - The linq expression could not be translated. either rewrite the query

This question has been asked before and answered a few times, but not in a generalized way. Rather, the answers are specific to the asker (which makes sense)...but I use this pattern a lot, and I'm sure others do as well, so I'm looking for a more general answer. I know why this is happening, but I'm not sure what to do about it exactly. the offending line below is
RoleId = ur.First(xu => xu.UserId == x.Id).RoleId,
My understanding is that I have to convert this in some way before the projection, but that's where I'm stuck. Or use Contains() instead of First() (which is an implicit Where() filter), but I'm not sure how to rewrite it so the server will do the query, instead of the client. What is the best way forward?
var roles = await _roleManager.Roles.ToListAsync();
var rolesList = roles.Select(x => new {x.Id, x.Name}).ToList();
var rid = ur.First(xu => xu.UserId == 4).RoleId;
var ur = await _context.UserRoles.ToListAsync();
var appUsers = await _context.Users
.Select(x => new AppUserViewModel
{
Id = x.Id,
StaffId = x.StaffId,
Email = x.Email,
UserName = x.UserName,
PhoneNumber = x.PhoneNumber,
RoleId = ur.First(xu => xu.UserId == x.Id).RoleId,
RoleSelectListItems = rolesList.Select(yy => new SelectListItem
{
Value = yy.Id.ToString(),
Text = yy.Name
}).ToList()
})
.ToListAsync();
Actually you should work with IQueryable, not lists.
var roles = _roleManager.Roles.AsQueryable();
var ur = _context.UserRoles.AsQueryable();
var appUsers = await _context.Users
.Select(x => new AppUserViewModel
{
Id = x.Id,
StaffId = x.StaffId,
Email = x.Email,
UserName = x.UserName,
PhoneNumber = x.PhoneNumber,
RoleId = ur.FirstOrDefault(xu => xu.UserId == x.Id).RoleId,
RoleSelectListItems = roles.Select(yy => new SelectListItem
{
Value = yy.Id.ToString(),
Text = yy.Name
}).ToList()
})
.ToListAsync();
I got an answer to this. Linq sends itself down to the database, but certain expressions can't be interpreted by the translator which is why it throws this error. So if, for example, your Linq expression references a method to return a value, well, that method can't be passed down to your db, so you have to get that value before you use it in your Linq expression.
So in the example above, RoleId = ur.First(xu => xu.UserId == x.Id).RoleId, it's really two expressions, and the translator doesn't know what to do with that. The first part is "return to me the first object in the userRoles list where the id equals the id of the user" and the second part is "and now give me the RoleId". But you can't send that down - it can't be translated. So I'd have to get the RoleId from the userRoles list as a separate pair of transactions.
Get the role object, then separate out the RoleId value from that object.

How to sort on DB side, if entities are not connected via Navigation (since it's not possible)

I want to have my EfCore query translated into the following SQL query:
select
c.blablabla
from
codes c
left join lookups l on c.codeId = l.entityid and l.languageCode = <variable - language code of current thread> and l.lookuptype = 'CODE'
where
..something..
order by
l.displayname
Note: tables 'codes' and 'lookups' are not connected! 'lookups' contains a lot of different lookup data in different languages!
I am stuck into limitations of EfCore (like 'NavigationExpandingExpressionVisitor' failed). I don't want to make in-memory filtering, it looks silly to me... Am I missing something obvious?
In perspective, I'd like to make universal method to help sort by displayname (or other lookup name) for different kind of entities - not only codes.
Seems like I figured it out. If there's a better approach - please let me know:
protected override IQueryable<FixCode> SortByDisplayName(IQueryable<FixCode> queryable, string languageCode = null)
{
return queryable
.GroupJoin(
DbContext.FixCodeValues.Where(x =>
x.DomainId == CentralToolConsts.Domains.CENTRAL_TOOLS
&& x.CodeName == CentralToolsFieldTypes.CODE_ORIGIN
&& (x.LanguageCode == languageCode || x.LanguageCode == CentralToolsDbLanguageCodes.English)),
//TODO: this will be a 'selector' parameter
code => code.CodeOriginId,
codeOrigin => codeOrigin.StringValue,
(c, co) => new
{
Code = c,
CodeOrigin = co
}
)
.SelectMany(
x => x.CodeOrigin.DefaultIfEmpty(),
(x, codeOrigin) => new { Code = x.Code, CodeOrigin = codeOrigin }
)
.OrderBy(x => x.CodeOrigin.ShortName)
.Select(x => x.Code);
}

EF Core entity refresh

I would like if there is a way to trigger an object hierachy's lazy load after it has been added in the cache following an insert.
If I insert an object only providing it the foreign keys ids and try to request it right after, none of these properties / collections are retrieved.
Example:
_dbContext.Set<MYTYPE>().Add(myObject);
await _dbContext.SaveChangesAsync(ct);
_dbContext.Entry(myObject).Collection(c => c.SomeCollection).Load();
_dbContext.Entry(myObject).Reference(c => c.Property1).Load();
_dbContext.Entry(myObject.Property1).Reference(c => c.NestedProperty).Load();
_dbContext.Entry(myObject).Reference(c => c.Property2).Load();
_dbContext.Entry(myObject).Collection(c => c.SomeCollection2).Load();
_dbContext.Entry(myObject).Reference(c => c.Property3).Load();
_dbContext.Entry(myObject).Reference(c => c.Property4).Load();
_dbContext.Entry(myObject.Property4).Reference(c => c.SomeOtherNestedProperty).Load();
_dbContext.Entry(myObject).Reference(c => c.Property5).Load();
_dbContext.Entry(myObject).Reference(c => c.Property6).Load();
return _dbContext.Set<MYTYPE>().Where(x => x.Id == myObject.Id);
Unless I do all the Load() (having to nest into several layers sometimes ...), I can't have any of the sub properties properly lazy loaded.
Note: doing
_dbContext.Entry(myObject).Reload()
or
_dbContext.Entry(myObject).State = EntityState.Detached;
doesn't work.
Any idea? Because I have several cases like this, with HEAVY object nesting, and it feels really bad to have to do all the loads manually.
Thanks.
The thing is, if you try to add some line using only the integer as a foreign key like below :
db.tablename.Add(new tablename{
name = "hello",
FK_IdofanotherTable = 5
});
db.SaveChanges();
Then entity does not care about linking the object immediately, you won't be able to access the linked object as a property.
This is how it's actually done in modern Entity Framework :
var TheActualCompleteObject = db.AnotherTable.First(t => t.id == 5);
db.tablename.Add(new tablename{
name = "hello",
AnotherTable = TheActualCompleteObject
});
db.SaveChanges();
So... basically, you will have loaded the object anyway, it's not gonna make anything faster, but it will be more consistant. You will be able to navigate through foreign keys properties.
If this is not possible, then you will have to query back AFTER SaveChanges to retrieve your object AND have access to foreign properties (eagerly loading them, of course, or else it's the same disaster)
Exemple :
_dbContext.Configuration.LazyLoadingEnabled = false;
_dbContext.Set<MYTYPE>().Add(myObject);
await _dbContext.SaveChangesAsync(ct);
var v = _dbContext.First(t => t.id == myObject.id);
var fk_prop = v.Property1;
I also found no easy way around this.
The following might make it simpler...
public static void Reset(this DbContext context)
{
var entries = context.ChangeTracker
.Entries()
.Where(e => e.State != EntityState.Unchanged)
.ToArray();
foreach (var entry in entries)
{
switch (entry.State)
{
case EntityState.Modified:
entry.State = EntityState.Unchanged;
break;
case EntityState.Added:
entry.State = EntityState.Detached;
break;
case EntityState.Deleted:
entry.Reload();
break;
}
}
}
Hope this helps!
JS

EF Append to an IQueryable a Where that generates an OR

I'm trying to achieve dynamic filtering on a table. My UI has filters that can be enabled or disabled on demand, and as you can imagine, my query should be able to know when to add filters to the query.
What I have so far is that I check if the filter object has a value, and if it does it adds a where clause to it. Example:
var q1 = DBContext.Table1
if (!string.IsNullOrEmpty(filterModel.SubjectContains))
q1 = q1.Where(i => i.Subject.Contains(filterModel.SubjectContains));
if (filterModel.EnvironmentId != null)
q1 = q1.Where(i => i.EnvironmentId == filterModel.EnvironmentId);
if (filterModel.CreatedBy != null)
q1 = q1.Where(i => i.CreatedByUserId == filterModel.CreatedBy);
var final = q1.Select(i => new
{
IssuesId = i.IssuesId,
Subject = i.Subject,
EnvironmentId = i.EnvironmentId,
CreatedBy = i.CreatedByUser.FullName,
});
return final.ToList();
The code above generates T-SQL that contains a WHERE clause for each field that uses AND to combine the conditions. This is fine, and will work for most cases.
Something like:
Select
IssueId, Subject, EnvironmentId, CreatedById
From
Table1
Where
(Subject like '%stackoverflow%')
and (EnvironmentId = 1)
and (CreatedById = 123)
But then I have a filter that explicitly needs an IssueId. I'm trying to figure out how the EF Where clause can generate an OR for me. I'm looking something that should generate a Tsql that looks like this:
Select
IssueId, Subject, EnvironmentId, CreatedById
From
Table1
Where
(Subject like '%stackoverflow%')
and (EnvironmentId = 1)
and (CreatedById = 123)
or (IssueId = 10001)
Found a solution for this that doesn't have to do multiple database call and works for me.
//filterModel.StaticIssueIds is of type List<Int32>
if (filterModel.StaticIssueIds != null)
{
//Get all ids declared in filterModel.StaticIssueIds
var qStaticIssues = DBContext.Table1.Where(i => filterModel.StaticIssueIds.Contains(i.IssuesId));
//Let's get all Issues that isn't declared in filterModel.StaticIssueIds from the original IQueryable
//we have to do this to ensure that there isn't any duplicate records.
q1 = q1.Where(i => !filterModel.StaticIssueIds.Contains(i.IssuesId));
//We then concatenate q1 and the qStaticIssues.
q1 = q1.Concat(qStaticIssues);
}
var final = q1.Select(i => new
{
IssuesId = i.IssuesId,
Subject = i.Subject,
EnvironmentId = i.EnvironmentId,
CreatedBy = i.CreatedByUser.FullName,
});
return final.ToList();

Entity Framework - ElementAt

I have a Table User(ID, Name....), Projects(ID, Name, Timestamps, IsFavorite...), Projects_Favoite(UserID, ProjectID)
I try to check for each projekt if there is a row for the current user. If there is a row I want set the "IsFavorite" in my Project true, otherwise false.
I tied:
for(int i = 0; i <= erg.Count();i++)
{
if (erg.ElementAt(i).User11.Any(u => u.Guid == ID) == true)
erg.ElementAt(i).SetFavorite = true;
}
but there is no way to use ElementAt, because it can't translated to SQL.
So I tried:
for(int i = 0; i <= erg.Count();i++)
{
if (erg.Take(i).Last().User11.Any(u => u.Guid == ID) == true)
erg.Take(i).Last().SetFavorite = true;
}
same problem here, so I tried:
foreach (Project project in erg)
{
if (project.User11.Any(u => u.Guid == ID))
project.SetFavorite = true;
}
There is the same problem. Is there a way to realise a ElementAt?
You should be able to do:
foreach (Project project in erg.Where(p => p.User11.Any(u => u.Guid == ID))
.ToList())
project.SetFavorite = true;
}
Assuming that erg is an IQuerable than you cannot use ElementAt as it is not supported (http://msdn.microsoft.com/en-us/library/bb399342.aspx check the section Operators With no translation), you can use it on a list though, but of course you will lose the benefits of the deferred query.
Try to use Skip instead
var whatyouwant = erg.Skip(index).First();
By looking at the stacktrace (can't read german though) looks there's something wrong with the SetFavorite column. Try first to remove this bit first:
project.SetFavorite = true;
then check also that your model columns match with what you got on the db.