Filter child entity LINQ methods - c#

Consider the following query :
IQueryable<Employee> ret = this.ObjectContext.Employees
.Include("EmployeeInformation")
.Include("LatestInformation")
.Where(emp => emp.idJobTitle == 1 && emp.idCrtLoc == 1);
The Employees entity doesn't have a navigation property to LatestInformation's entity(So I can't directly access the other entity) but the LatestInformation does have a navigation property to the Employees entity.
How can I filter the LatestInformation entity of this query?
The expected query should look like this :
ret = ret.Where(r=> LatestInformation.Where(li => li.year == 2015)); // Ofcourse this piece of code is wrong.
So , the question was how to filter the LatestInformation entity ?

If there is no navigation property from Employee to LatestInformation, query it the other way around. Something like:
var ret = this.ObjectContext.LatestInfomration
.Where(i => i.Employee != null && i.year == 2015)
.Select(i => i.Employee)
.Where(emp => emp.idJobTitle == 1 && emp.idCrtLoc == 1);
Edited to match OP comment below:
// Extract needed data:
var employeeIdListWithInfo = this.ObjectContext.LatestInfomration
.Where(i => i.Employee != null)
.Select(i => i.Employee.Id)
.ToList();
// Build the query (not executed yet)
var employeeWithInformation = this.ObjectContext.LatestInfomration
.Where(i => i.Employee != null && i.year == 2015)
.Select(i => i.Employee)
.Where(emp => emp.idJobTitle == 1 && emp.idCrtLoc == 1);
var lonelyEmployees = this.ObjectContext.Employee
.Where(emp => !employeeIdListWithInfo.Contains(emp.Id));
var ret = employeeWithInformation.Union(lonelyEmployees);

What do you mean by navigation property?
If its some unique id then you could use
List<int> latest = this.ObjectContext.LatestInformation.Select(lat=> lat.someid).ToList();
ret = ret.Where(r=> latest.Contains(ret.id)).Where(emp => emp.idJobTitle == 1 && emp.idCrtLoc == 1);

Related

Append WHERE to query based on a condition

I have a query like so:
if (catId == null || catId == 0)
{
productVM = db.Products
.Include(x => x.Category)
.ToArray()
.Select(x => new ProductVM(x))
.ToList();
}
else
{
productVM = db.Products
.Include(x => x.Category)
.ToArray()
.Where(x => x.CategoryId == catId)
.Select(x => new ProductVM(x))
.ToList();
}
That works, but as you can see the only difference between the 2 queries is .Where(x => x.CategoryId == catId).
Is there a more elegant way to write this?
Entity Framework doesn't actually query the database until you materialise the data, for example with ToList() or iterating over the results. So you can build up your query as you go without hitting the database:
var query = db.Products.Include(x => x.Category);
if(catId != null && catId != 0)
{
//Add a where clause
query = query.Where(x => x.CategoryId == catId);
}
productVM = query
.ToList()
.Select(x => new ProductVM(x));
Note that I removed the ToArray call as that also materialises the data which means each subsequent method on the data is acting on the entire table from the database.
You could expand the Where statement to include the test for null or 0 on catId. This might not work if the field catId in the database is nullable or can have a value of 0.
productVM = db.Products
.Include(x => x.Category)
.Where(x => catId == null || catId == 0 || x.CategoryId == catId)
.Select(x => new ProductVM(x))
.ToList();
Also you should remove the ToArray() as it queries the entire Products table from the database then perform the filtering and projecting in memory on the client.
Another way.
var products = db.Products.Include(x => x.Category).ToArray()
if (catId == null || catId == 0)
{
productVM = products.Select(x => new ProductVM(x)).ToList();
}
else
{
productVM = products.Where(x => x.CategoryId == catId)
.Select(x => new ProductVM(x)).ToList();
}
Simply re-assign query:
var query = db.Products;
if (condition)
query = query.Where(criteria);
var list = query.ToList();

Check property for null LINQ

I have the following code that give me data from database:
var t = (from ula in proxy.eUserLoginAttempts
where ula.Date >= DateTime.Now && ula.Email.ToLower().Contains("")
&& ula.User != null
&& ula.User.Client != null
&& ula.User.Client.prStatus == 1
select ula).ToList();
In this case, I would get prStatus from Client entity and I check User and Client object if they are not null. Should I do it or ula.User.Client.prStatus will translate in Inner Join and this check is needless?
Answering your direct question: NO, you should test the nullable first...
About your code, I really do suggest a readable way:
var t = proxy.eUserLoginAttempts
.Where(ula => ula.Date >= DateTime.Now)
.Where(ula => !string.IsNullOrEmpty(ula.Email))
.Where(ula => ula.User != null)
.Where(ula => ula.User.Client != null)
.Where(ula => ula.User.Client.prStatus == 1)
.ToList();
Or even better with C# 6
var t = proxy.eUserLoginAttempts
.Where(ula => ula.Date >= DateTime.Now)
.Where(ula => !string.IsNullOrEmpty(ula.Email))
.Where(ula => ula.User?.Client?.prStatus == 1)
.ToList();

Union on empty Enumerable

I have function
public async Task<IQueryable<Document>> GetDocuments(...)
in which I search for documents under some given conditions. Some conditions can be skipped. At the end I perform union of these queries.
var documents = await documentService.GetDocuments(this, userId,
roleShowFullNumber, param.OrderColName(), param.SearchValue, filter);
var usersGroupsId = filter.UsersGroupsId;
if (usersGroupsId != null)
{
if (!usersGroupsId.Contains("All"))
{
IQueryable<Document> myDocs = Enumerable.Empty<Document>().AsQueryable();
if (usersGroupsId.Contains("myOrders"))
{
myDocs = documents.Where(x => x.OwnerId == userId || x.UserId == userId);
usersGroupsId = usersGroupsId.Where(x => x != "myOrders").ToArray();
}
IQueryable<Document> wards = Enumerable.Empty<Document>().AsQueryable();
if (usersGroupsId.Contains("wards"))
{
var relatedUserId = _db.Users.Where(x => x.Id == userId).Select(x => x.RelatedUserId).FirstOrDefault();
if (relatedUserId != null)
{
var myWards = _db.kh__Kontrahent.Where(x => x.kh_IdOpiekun == relatedUserId);
var myWardsUsers = _db.Users.Where(x => myWards.Any(w => w.kh_Id == (x.RelatedCustomerId == null ? -1 : x.RelatedCustomerId)));
wards = documents.Where(x => (myWardsUsers.Any(w => x.UserId == w.Id) || myWardsUsers.Any(w => x.OwnerId == w.Id)));
usersGroupsId = usersGroupsId.Where(x => x != "wards").ToArray();
}
}
IQueryable<Document> groups = Enumerable.Empty<Document>().AsQueryable();
if (usersGroupsId.Length > 0)
{
var usersGroups = _db.Groups.Where(x => usersGroupsId.Contains(x.Id.ToString()));
var usersList = usersGroups.Select(x => x.Users);
var users = usersList.SelectMany(x => x);
var usersId = users.Select(x => x.Id);
groups = _db.Documents.Where(x => (usersId.Any(u => u == x.OwnerId) || usersId.Any(u => u == x.UserId)));
}
documents = myDocs.Union(wards).Union(groups);
}
}
But if one of these partial queries is empty (was skipped) when I try obtain these documents in way shown below I got error.
var documentsPaginated = await documents.Skip(param.Start)
.Take(param.Length)
.ToListAsync();
Error: The source IQueryable doesn't implement IDbAsyncEnumerable.
How can I make this function to be able to skip some sub queries and then union all. I would prefer not to change function return value.
Try this, althought your code seems to have a massive amount of code smell...
public async Task<IQueryable<Document>> GetDocuments(...)
var documents = await documentService.GetDocuments(this, userId,
roleShowFullNumber, param.OrderColName(), param.SearchValue, filter);
var usersGroupsId = filter.UsersGroupsId;
if (usersGroupsId != null)
{
if (!usersGroupsId.Contains("All"))
{
IQueryable<Document> myDocs = null;
if (usersGroupsId.Contains("myOrders"))
{
myDocs = documents.Where(x => x.OwnerId == userId || x.UserId == userId);
usersGroupsId = usersGroupsId.Where(x => x != "myOrders").ToArray();
}
IQueryable<Document> wards = null;
if (usersGroupsId.Contains("wards"))
{
var relatedUserId = _db.Users.Where(x => x.Id == userId).Select(x => x.RelatedUserId).FirstOrDefault();
if (relatedUserId != null)
{
var myWards = _db.kh__Kontrahent.Where(x => x.kh_IdOpiekun == relatedUserId);
var myWardsUsers = _db.Users.Where(x => myWards.Any(w => w.kh_Id == (x.RelatedCustomerId == null ? -1 : x.RelatedCustomerId)));
wards = documents.Where(x => (myWardsUsers.Any(w => x.UserId == w.Id) || myWardsUsers.Any(w => x.OwnerId == w.Id)));
usersGroupsId = usersGroupsId.Where(x => x != "wards").ToArray();
}
}
IQueryable<Document> groups = null;
if (usersGroupsId.Length > 0)
{
var usersGroups = _db.Groups.Where(x => usersGroupsId.Contains(x.Id.ToString()));
var usersList = usersGroups.Select(x => x.Users);
var users = usersList.SelectMany(x => x);
var usersId = users.Select(x => x.Id);
groups = _db.Documents.Where(x => (usersId.Any(u => u == x.OwnerId) || usersId.Any(u => u == x.UserId)));
}
if(myDocs != null)
documents = documents.Union(myDocs);
if(wards != null)
documents = documents.Union(wards);
if(groups != null)
documents = documents.Union(groups);
}
}
It appears that ToListAsync can't be used interchangeably with linq, but only in an EF query.
Quote from MSDN
Entity Framework 6 introduced a set of extension methods that can be used to asynchronously execute a query. Examples of these methods include ToListAsync, FirstAsync, ForEachAsync, etc.
Because Entity Framework queries make use of LINQ, the extension methods are defined on IQueryable and IEnumerable. However, because they are only designed to be used with Entity Framework you may receive the following error if you try to use them on a LINQ query that isn’t an Entity Framework query.
The source IQueryable doesn't implement IDbAsyncEnumerable{0}. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.

Select two columns from the table via linq

I use the query below to get all columns(20 more) in Entity Framework Linq. Because of out of memory exception, I only want to get two of them. One is "FileName", the other one is "FilePath". How to modify my code?
var query = DBContext.Table1
.Where(c => c.FacilityID == facilityID && c.FilePath != null && c.TimeStationOffHook < oldDate)
.OrderBy(c => c.FilePath)
.Skip(1000)
.Take(1000)
.ToList();
foreach(var t in query)
{
Console.WriteLine(t.FilePath +"\\"+t.FileName);
}
var query = DBContext.Table1.Where(c => c.FacilityID == facilityID && c.FilePath != null && c.TimeStationOffHook < oldDate)
.OrderBy(c => c.FilePath)
.Skip(1000)
.Take(1000)
.Select(c => new { c.FilePath, c.FileName })
.ToList();
foreach(var t in query)
{
Console.WriteLine(t.FilePath +"\\"+t.FileName);
}
You need to use Select.
Just select out two of the columns:
DBContext.Table1.Select(c => new { c.FileName, c.FilePath });
How about something like
using (var entity = new MyModel(ConnectionString))
{
var query = (from myTable in entity.theTable
where myTable.FacilityID == facilityID &&
myTable.FilePath != null &&
myTable.TimeStationOffHook < oldDate
orderby myTable.FilePath
select new
{
myTable,FileName,
myTable.FilePath
}).Skip(1000).Take(1000).ToList();
//do what you want with the query result here
}

Datetime check in Linq where statement

Please see the code below:
var pcPageList = db.PcPages
.Where(m =>
m.Quarter == exactQuarter &&
m.Url == pageUrl &&
m.UpdatedOn.ToDateTime().Date.ToString("dd/MMM").ToLower() == "02/nov")
.OrderBy(m => m.UpdatedOn)
.FirstOrDefault();
When I run this above, the application throws error says: "ToDateTime" is not implemented yet. Anyone please can advice ?
How about:
var updateStart = DateTime.ParseExact("02/nov", "dd/MMM", CultureInfo.InvariantCulture);
var updateEnd = updateStart.AddDays(1.0);
var pcPageList = db.PcPages
.Where(m =>
m.Quarter == exactQuarter &&
m.Url == pageUrl &&
m.UpdatedOn >= updateStart &&
m.UpdatedOn < updateEnd)
.OrderBy(m => m.UpdatedOn)
.FirstOrDefault();
I think, you should be calling ToDateTime using Convert class as:
Convert.ToDateTime(m.UpdatedOn).Date...
And remove the Date in between as:
Convert.ToDateTime(m.UpdatedOn).ToString("dd/MMM").ToLower() == "02/nov"
Rather than doing a string comparison, it would be more efficient to compare the date components directly. I haven't tested this, but something like the following may work:
var pcPageList = db.PcPages
.Where(m => m.Quarter == exactQuarter && m.Url == pageUrl)
// You may need to materialize the results of the query at this point
// or use Convert.ToDateTime(...) instead of ToDateTime()
.Select(m => new { Row = m, UpdatedOn = m.UpdatedOn.ToDateTime() })
.Where(a => a.UpdatedOn.Month == 11 && a.UpdatedOn.Day == 2)
.Select(a => a.Row)
.OrderBy(m => m.UpdatedOn)
.FirstOrDefault();

Categories