LINQ Lambda: Getting Last item (where == ) of sub collection - c#

I have the following (which I've used in CodeFirst):
public class MyForm
{
public int MyFormId { get; set; }
//Many Form Properties
public virtual ICollection<AuditItem> AuditItems { get; set; }
}
public class AuditItem
{
public int AuditItemId { get; set; }
public int MyFormId { get; set; }
public IAppUser User { get; set; }
public string ActionByUserStr { get; set; }
public string ActionByUserDisplayName { get; set; }
public DateTime DateOfAction { get; set; }
public string TypeOfAction { get; set; }
public int Step { get; set; }
public virtual MyForm MyForm { get; set; }
}
The AuditItem tracks many actions carried out on MyForm
I'm presenting an index page showing a table where the AuditItem's last Step = 1.
I do an orderby DateOfAction (because the Step can get setback to 0, so I wouldn't want it to equal 1 unless it is also the last AuditItem Record for that MyForm), then Select the last record's step, then query on that. I want to query as early as I can so that I'm not pulling back unrequired MyForms records.
This is the query I have:
var myFormsAtStep1 =
context.MyForms.Include("AuditItems").Where(
mf => mf.AuditItems.OrderBy(ai => ai.DateOfAction).Last().Step == 1);
It gives this error:
LINQ to Entities does not recognize the method 'MyForms.Models.AuditItem Last[AuditItem](System.Collections.Generic.IEnumerable`1[MyForms.Models.AuditItem])' method, and this method cannot be translated into a store expression.

Have a look at the list of Supported and Unsupported LINQ Methods.
Last() is not supported, but First() is, so you can just go on and reverse your ordering and use First() instead of Last().
mf => mf.AuditItems.OrderByDescending(ai => ai.DateOfAction).First().Step == 1);

You simply sort in descending order and get the first item.
var myFormsAtStep1 =
context.MyForms.Include("AuditItems").Where(
mf => mf.AuditItems.OrderByDescending(
ai => ai.DateOfAction).First().Step == 1);
I would also use the query syntax, which would be more readable, as well as the strongly-typed Include (import System.Data.Entity):
from mf in context.MyForms.Include(x => x.AuditItems)
let items = from ai in mf.AuditItems
orderby ai.DateOfAction descending
select ai;
where items.First().Step == 1
select mf

Related

How to use function in where with ToListAsync()

I wrote a where function to check if product have some functionality and find a product with max price.
My simply models look like this:
public class Product
{
public long ProductID { get; set; }
[MaxLength(150)]
public string Name { get; set; }
public List<Functionality> Functionalities { get; set; }
public List<Price> Prices { get; set; }
}
public class Functionality
{
public long FunctionalityID { get; set; }
[MaxLength(150)]
public string Name { get; set; }
[Required]
public long ProductID { get; set; }
public Product Product { get; set; }
}
public class Price
{
public long PriceID { get; set; }
public decimal Value { get; set; }
[Required]
public long ProductID { get; set; }
public Product Product { get; set; }
}
then my sync function to find correct product look like this:
public List<Product> GetList(ProductFiltersDto filters)
{
return _context.Product
.Include(x => x.Functionality)
.Include(x => x.Price)
.Where(x =>
CheckCollectionFilter(x.Functionality.Select(f => f.FunctionalityID), filters.Functionalities) &&
CheckMaximumPrice(x.Prices , filters.MaxPrice)
)
.ToList();
}
below my where function:
private bool CheckCollectionFilter<T>(IEnumerable<T> collection, List<T> filterCollection)
{
if (filterCollection != null)
{
var result = true;
foreach (var filterValue in filterCollection)
{
if (!collection.Contains(filterValue))
{
result = false;
break;
}
}
return result;
}
else
{
return true;
}
}
private bool CheckMaximumPrice(List<Price> prices, decimal? avalibleMinPrice)
{
return avalibleMinPrice.HasValue && prices.Count > 0 ? prices.Min(x => x.Value) <= avalibleMinPrice : true;
}
For above code everything work fine. But when i change ToList() to ToListAsync() i got a error
Expression of type 'System.Collections.Generic.IAsyncEnumerable1[System.Int64]'
cannot be used for parameter of type 'System.Collections.Generic.IEnumerable1[System.Int64]'
of method 'Boolean CheckCollectionFilter[Int64](System.Collections.Generic.IEnumerable1[System.Int64],
System.Collections.Generic.List1[System.Int64])'
Parameter name: arg0
I try few things to change IEnumerable to IAsyncEnumerable and modify my function to work with asyn version but stil i get error (i dont find a way to change List<long> to IAsyncEnumerable<long> in where clause).
I read that EF Core Async functions have some limitations but maybe someone know is this can be achiev or for now i should leave this and stick with sync solution?
As soon as you move from IQueriable<T> to IEnumerable<T>, EntityFramework will execute the query so far on the database server pulling all that data into memory and will execute the rest of the query in memory.
If you want to keep it running in the database server, you must keep the query an IQueriable<T> and use expression trees instead of executable code.
You will need to come up with something like this:
private Expression<Func<T, bool>> CheckCollectionFilter<T>(IEnumerable<T> filterCollection);
private Expression<Func<T, bool>> CheckMaximumPrice<T>(decimal? avalibleMinPrice);
and change your query to:
_context.Product
.Include(x => x.Functionality)
.Include(x => x.Price)
.Where(CheckCollectionFilter(filters.Functionalities))
.Where(CheckMaximumPrice(filters.MaxPrice))

Possible to add a condition for linked table fields in LINQ

Can someone suggest me a solution to add condition for reference table items in linq.
I have a master table called TourPackage, which include
public class TourPackage{
public int TourID { get; set; }
public string TourName { get; set; }
public List<IncludedItems> IncludedItems { get; set; }
}
Every tour package contain some selected items reference like
public class IncludedItems {
public int TourID { get; set; }
public int IncludedID { get; set; }
public Included Included { get; set; }
}
All included item should have a reference to Included table for lookup reference
public class Included {
public int IncludedID { get; set; }
public string IncludedValue { get; set; }
}
now i have set of IncludedID like [1,2,3], Is it possible to filter TourPackage based on IncludedID.
Thanks in advance
You can use following code
I have sample array(i.e example) which contains ID's we check if current Id(i.e ele.Included.IncludedID) is present in the array of id's.
listex.Where(x => x.IncludedItems.Any(ele => example.Contains(ele.Included.IncludedID))).ToList();
sample:-
int[] example = new int[3];
example[0] = 123;
example[1] = 456;
example[2] = 789;
List<TourPackage> listex = new List<TourPackage>();
List<TourPackage> filterList = listex.Where(x => x.IncludedItems.Any(ele => example.Contains(ele.Included.IncludedID))).ToList();
Have you tried using something like:
var myIds = new List<int> {123,456};
var result = context.TourPackages
.Where(x => x.IncludedItems.Any(a => a.Included !=null && myIds.Contains(a.Included.IncludedId)))
.ToList();
You might have to include some relations manually depending if you're lazy loading is setup or not.
More info at https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx

Using Lambda to insert derived attribute into IQueryable dataset

I have the following query:
IQueryable<BarcodeQuery> barcodes = db.Barcodes.Select(b => new BarcodeQuery
{
id = b.id,
category_id = b.category_id,
...
checkout = b.Checkouts.Select(c => new CheckoutChild
{
id = c.id,
loanee_id = c.loanee_id,
...
})
.Where(c => c.datein == null)
.FirstOrDefault()
});
And so on. It's based on this model:
public class BarcodeQuery
{
public int id { get; set; }
public int category_id { get; set; }
...
public CheckoutChild checkout { get; set; }
public CheckoutStatus checkoutStatus { get; set; }
}
My question is about CheckoutStatus down there at the bottom. It looks like this:
public class CheckoutStatus
{
public string status { get; set; }
public int daysUntilDue { get; set; }
public int daysOverdue { get; set; }
}
All of those values are derived from information I get from the query--none of them are in the database itself. What is the best way of inserting the CheckoutStatus values into each barcode record?
I have a function that creates the CheckoutStatus values themselves, I just don't know how to get them into the barcode records.
Thanks!
If b has just be created with new, how can b.Checkouts contain something? I do not really understadn what you are trying to do.
EF is converting the lambda expression into a SQL statement. Therefore you can only use expressions that can actually be translated to SQL. Just query the barcodes from the DB and then add the missing information to the barcodes returned in a loop.
var barcodes = db.Barcodes.Select(...).ToList();
foreach (Barcode b in barcodes) {
b.Checkouts = ...
}

Sequence contains more than one element when using Contains in LINQ query

The following code is throwing an InvalidOperationException: Sequence contains more than one element. The errors occurs whether I have the Include statement or not.
long[] customerIDs; //Method parameter. Has valid values
var results = from x in DB.CycleCounts
//.Include(y => y.CustomerInventoryItem)
select x;
if (customerIDs != null && customerIDs.Length > 0)
{
results = from x in results
where customerIDs.Contains(x.CustomerInventoryItem.CustomerID)
select x;
}
var cycleCounts = await results.ToListAsync(); //throws InvalidOperationException
I am using ASP5 RC1 (Core), and Entity Framework 7
I not sure how look your models, but I think that they looks like something below (relationship between CycleCounts and CustomerInventoryItems as many-to-one as I expect):
Models:
[Table("CustomerInventorys")]
public class CustomerInventory
{
public long ID { get; set; }
public long CustomerID { get; set; }
public virtual ICollection<CycleCount> CycleCounts { get; set; }
}
[Table("CycleCounts")]
public class CycleCount
{
public long ID { get; set; }
public long CustomerInventoryItemID { get; set; }
public virtual CustomerInventory CustomerInventoryItem { get; set; }
}
So if my suggestions are correct I recommend you to rewrite you code like this:
IQueryable<CycleCount> results = null;
if (customerIDs != null && customerIDs.Length > 0)
{
results = from invent in DB.CustomerInventorys
where customerIDs.Contains(invent.CustomerID)
join cycle in DB.CycleCounts on invent.ID equals cycle.CustomerInventoryItemID
select cycle;
}
else
results = DB.CycleCounts;
var cycleCounts = await results.ToListAsync();

Where condition inside lambda expression c#

I have a entity like
public class Program
{
public int ID { get; set; }
public bool IsActive { get; set; }
public string Title { get; set; }
}
and
public class EMetrics
{
public int ID { get; set; }
public bool IsActive { get; set; }
public string Title { get; set; }
public List<Program> Programs { get; set; }
}
I have repository method like,
IEnumerable<EMetrics> IEmetricsRepository.GetAllByProgram(params int[] programIds)
{
var metrics = EntitySet
.Where(x => programIds.Contains(x.Programs.Select(x => x.ID)))
.ToList();
return metrics;
}
[The above code throwing build error]
Here only where I am facing problem to get the EMetrics based on the program Ids array params.
I want list Emetrics which are associated with the program.
You're incorrectly accessing the same input parameter in your LINQ. It should be refactored by changing your inner Select to use a different parameter:
IEnumerable<EMetrics> IEmetricsRepository.GetAllByProgram(params int[] programIds)
{
var metrics = EntitySet
.Where(x => programIds.Contains(x.Programs.Select(y => y.ID)))
.ToList();
return metrics;
}
So you want to check if all elements of one collection are present in the other. In LINQ that can be done with combination of Except and Any:
var metrics = EntitySet
.Where(x => x.Programs.Select(p => p.ID).Except(programIds).Any())
.ToList();
Fyi - your current code is failing because Array.Contains expects a single item, an int in this case, while you are giving it a whole enumerable

Categories