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
Related
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))
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
I have the following hierarchy in my project :
Activity
Task
Step
Responses
This means an activity has many tasks, which in turn has many steps , a step has many responses.
Here are my POCO classes:
public class Activity
{
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
public virtual ICollection<Step> Steps{ get; set; }
}
public class Step
{
public virtual int DisplayOrder { get; set; }
public virtual ICollection<Response> Responses{ get; set; }
}
public class Response
{
public virtual int UserId { get; set; }
public virtual int ResponseText{ get; set; }
}
Now, I need to return a List<Activity> which is sorted by ActivityId and has Steps ordered by DisplayOrder and also a Responses which only belong to a given UserId.
Here's what I have tried:
Activities.ToList()
.ForEach((activity) => activity.Tasks.ToList()
.ForEach((task) => task.Steps = task.Steps.OrderBy(s => s.DisplayOrder).ToList()
.ForEach((step) => step.Responses = step.Responses.Where(r => r.UserId == RequestorUserId)))
));
Which is giving me an error:
Cannot implicitly convert type 'void' to ICollection<Step>
The ForEach extension method doesn't return anything and you are trying to set the result to task.Steps. You can achieve the desired result by using the Select method to alter the objects in line
Activities.ToList()
.ForEach((activity) => activity.Tasks.ToList()
.ForEach((task) => task.Steps = task.Steps.OrderBy(s => s.DisplayOrder).ToList()
.Select((step) =>
{
step.Responses = step.Responses.Where(r => r.UserId == 1).ToList();
return step;
}).ToList()));
Also It might be worth changing your types if possible to IEnumerable rather than ICollection as you then won't need all of the ToList() calls.
If you need to assign the result then you'll need to replace the first ForEach as well - try something like:
var a = Activities.ToList()
.Select((activity) =>
{
activity.Tasks.ToList()
.ForEach((task) => task.Steps = task.Steps.OrderBy(s => s.DisplayOrder).ToList()
.Select((step) =>
{
step.Responses = step.Responses.Where(r => r.UserId == 1).ToList();
return step;
}).ToList());
return activity;
});
I have the following model:
public class Stredisko
{
public string Name { get; set; }
public ObservableCollection<SubStredisko> Pracoviska { get; set; }
public ObservableCollection<SubStredisko> Dohodari { get; set; }
}
public class SubStredisko
{
public string Name { get; set; }
public string Code { get; set; }
public VyplatnePasky VyplatnaPaska { get; set; }
public MzdoveNaklady MzdoveNaklady { get; set; }
public Poistne Poistne { get; set; }
}
I am now trying to run a super simple LINQ query which should return first element which matches the following condition:
var sStredisko = Strediska.FirstOrDefault(
n => n.Pracoviska.FirstOrDefault(x=>x.Name == "somename"));
I Run this condition against: ObservableCollection<Stredisko> Strediska
However for unknown reason (to me) it gives me the following error:
Cannot implicitly convert type 'SubStredisko' to 'bool'.
What am I doing wrong?
You're looking for Enumerable.Any:
var sStredisko = Strediska.FirstOrDefault(
n => n.Pracoviska.Any(x => x.Name == "somename"));
FirstOrDefault will yield the first element that matches the predicate. You want to match against the first element and yield a bool indicating the match has been found, which is what Any does.
Partially related to OP's question, this is what I was searching when I found this question:
I needed to actually get the nested object instead of the top one.
So re-using OP's model it would look like this:
var sSubStredisko = Strediska
.Select(n => n.Pracoviska.FirstOrDefault(x => x.Name == "somename"))
.FirstOrDefault(s => s != null);
Because of LINQ's deferred execution this won't loop twice but will just return the first nested element it will find (or none).
I'm a bit lost here and I've tried a few different ways to tackle it. So far I'm having a hard time writing out the LINQ to do what I want.
I want to take the user input string which can be multiple keywords split either by whitespace or ",".
This here works grabs the whole search term and compares it to the title in the Post or any tag I may have. I want the user to type in "HTML Preview" which would match a post called, "Preview the World" with the tags "HTML", "CSS", etc....
This query won't work...but I'm trying to modify it so that it does work.
public IPagedList<Post> SearchResultList(string searchTerm, int resultsPerPage, int page)
{
string[] terms = searchTerm.Split(null);
TNDbContext context = DataContext;
return context.Posts
.Include(a => a.Tags)
.Include(b => b.Comments)
.Where(c => (c.Title.Contains(searchTerm) || c.Tags.Any(d => d.Name.StartsWith(searchTerm))) || searchTerm == null)
.OrderByDescending(x => x.Views)
.ToPagedList(page, resultsPerPage);
}
I tried writing this instead of the other "Where" statement
.Where(x => (terms.All(y => x.Title.Contains(y))) || terms == null)
but it keeps throwing this error
Cannot compare elements of type 'System.String[]'. Only primitive types, enumeration types and entity types are supported.
FOR REFERENCE:
public class Post
{
public Post()
{
Tags = new HashSet<Tag>();
Comments = new HashSet<Comment>();
}
public int Id { get; set; }
public string Title { get; set; }
public string UrlTitle { get; set; }
public DateTime Date { get; set; }
public DateTime DateEdited { get; set; }
public string Body { get; set; }
public string Preview { get; set; }
public string PhotoPath { get; set; }
public int Views { get; set; }
//Navigational
public ICollection<Tag> Tags { get; set; }
public ICollection<Comment> Comments { get; set; }
}
public class Tag
{
public Tag()
{
Post = new HashSet<Post>();
}
public int Id { get; set; }
public string Name { get; set; }
public int TimesTagWasUsed { get; set; }
//Navigational
public ICollection<Post> Post { get; set; }
}
You need to start with a base query, and then keep adding where clauses to it for each search term. Try this:
TNDbContext context = DataContext;
//Create the base query:
var query = context.Posts
.Include(a => a.Tags)
.Include(b => b.Comments)
.OrderByDescending(x => x.Views);
//Refine this query by adding "where" filters for each search term:
if(!string.IsNullOrWhitespace(searchTerm))
{
string[] terms = searchTerm.Split(" ,".ToCharArray(),
StringSplitOptions.RemoveEmptyEntries);
foreach(var x in terms)
{
string term = x;
query = query.Where(post => (post.Title.Contains(term) ||
post.Tags.Any(tag => tag.Name.StartsWith(term))));
}
}
//Run the final query to get some results:
var result = query.ToPagedList(page, resultsPerPage);
return result;
You can nest queries with additional 'from' statements, so something like this should work:
var list = (from post in context.Posts.Include(a => a.Tags).Include(b => b.Comments)
from term in terms
where post.Title.Contains(term) || post.Tags.Any(d => d.Name.StartsWith(term))
select post).OrderByDescending(x => x.Views);