I have a question regarding the LINQ and foreach.
Lets say I have a simple DB with three tables (ingredient, recipe and many-to many relation ingredientinrecipe). They all have the simple structure of id and name.
I am running a console application where user could enter two ingredients for example
egg, flour
How would I structure the LINQ clause to go through all recipes where these both would be inside.
I am using UOW to structure everything together, so I could do uow.Ingridients.All ... etc etc
I figured I need to approach it from ingridientinrecipe table.
It would be something following:
var recipes = uow.ingridientinrecipe.All.Where(a => a.Ingridient.IngridientName.Contains( ...
But this works if and only if user enters one ingridient. How would I expand it to use two to infinite inputs.
I add what I have done so far.
var line = Console.ReadLine();
while (line.ToLower().Trim() != "exit")
{
var ingridients= line.Split(',');
Console.WriteLine("\nAre you looking for following receipes:");
foreach (var ingridient in ingridients)
{
var recipes= _uow.IngridientInRecipe.All.Where(a => a.Ingridient.IngridientName.Contains(ingridient)).ToList();
foreach (var recipe in recipes)
{
Console.WriteLine(recipe.Recipes.RecipeName);
}
Console.WriteLine();
line = Console.ReadLine();
}
TABLE STRUCTURE AS ASKED
public class Recipe
{
public int RecipeId{ get; set; }
public string RecipeName{ get; set; }
public virtual List<IngridientInRecipe> IngridientInRecipe{ get; set; }
}
public class Symptom
{
public int IngridientId{ get; set; }
public string IngridientName{ get; set; }
public virtual List<IngridientInRecipe> IngridientInRecipe{ get; set; }
}
public class IngridientInRecipe
{
public int IngridientInRecipeId{ get; set; }
public int RecipeId{ get; set; }
public virtual Recipe Recipe{ get; set; }
public int IngridientId{ get; set; }
public virtual Ingridient Ingridient{ get; set; }
}
You can use Intersect
var line = Console.ReadLine();
while (line.ToLower().Trim() != "exit")
{
var ingridients = line.Split(',');
Console.WriteLine("\nAre you looking for following receipes:");
var recipes = _uow.IngridientInRecipe.All;
foreach (var ingridient in ingridients)
{
recipes = recipes.Intersect(_uow.IngridientInRecipe.All.Where(a => a.Ingridient.IngridientName.Contains(ingridient)));
}
foreach (var recipe in recipes.ToList())
{
Console.WriteLine(recipe.Recipes.RecipeName);
}
Console.WriteLine();
line = Console.ReadLine();
}
You could use a PredicateBuilder from LinqKit (I have no affiliation with this):
var ingredients = ingredientList.ToLower().Split(new [] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);
var predicate = PredicateBuilder.False<Recipe>();
foreach (var ingredient in ingredients)
{
string temp = ingredient; // To avoid passing the loop variable into a closure
predicate = predicate.Or(r => r.Ingredients.Contains(temp);
}
reportQuery = reportQuery.Where(predicate);
Related
*I have written a recursive query to get unlimited menu layer. The query works fine providing the exact results but it takes too much time to load. It takes probably 10 to 15 seconds. Please help me if I need to do anything to improve the performance. I have provided all the code to find out the problem. for mapping from entity to view model I have used automapper. *
Entity:
public class Menus
{
public int Id { get; set; }
public string Icon { get; set; }
public string Label { get; set; }
public string To { get; set; }
[ForeignKey("Parents")]
public int? ParentsId { get; set; }
public string Key { get; set; }
public bool? Permitted { get; set; }
public Menus Parents { get; set; }
public ICollection<Menus> Subs { get; set; }
public ICollection<MenusRole> MenusRoles { get; set; }
}
Query:
public async Task<IEnumerable<Menus>> GetAllMenusAsync()
{
List<Menus> temp = await ApplicationDbContext
.Menus
.Include(x => x.Subs)
.Where(x => x.Parents == null)
.Select(f => new Menus
{
Id = f.Id,
Key = f.Key,
Label = f.Label,
To = f.To,
Icon = f.Icon,
ParentsId = f.ParentsId,
Subs = f.Subs
}).ToListAsync();
return Get_all_menus(temp);
}
public List<Menus> Get_all_menus(List<Menus> menus)
{
int z = 0;
List<Menus> menuList = new List<Menus>();
if (menus.Count > 0)
{
menuList.AddRange(menus);
}
foreach (Menus item in menus)
{
Menus menu = ApplicationDbContext
.Menus
.Include(y => y.Subs)
.Where(y => y.Id == item.Id)
.Select(y => new Menus
{
Id = y.Id,
Key = y.Key,
Label = y.Label,
To = y.To,
Icon = y.Icon,
ParentsId = y.ParentsId,
Subs = y.Subs,
Permitted = true
}).First();
if (menu.Subs == null)
{
z++;
continue;
}
List<Menus> subMenu = menu.Subs.ToList();
menu.Subs = Get_all_menus(subMenu);
menuList[z] = menu;
z++;
}
return menuList;
}
In Controller
[HttpGet("get-all-menus")]
public async Task<ActionResult> GetAllMenus()
{
var menus = await _menusService.GetAllMenus();
var menusResources = _mapper.Map<IEnumerable<Menus>, IEnumerable<MenusResourceForSidebar>>(menus);
return Ok(menusResources);
}
View Model
public string Id { get; set; }
public string Icon { get; set; }
public string Label { get; set; }
public string To { get; set; }
public bool? Permitted { get; set; }
public ICollection<MenusResourceForSidebar> Subs { get; set; }
Instead of loading the root menus, then loading the children in separate queries, just load the whole collection in one query, and then populate the navigation links afterwards.
public async Task<IEnumerable<Menus>> GetAllMenusAsync()
{
List<Menus> temp = await ApplicationDbContext.Menus.ToList();
List<Menus> topLevel = new List<Menu>();
foreach (var menu in temp)
{
if (menu.ParentsId == null)
{
topLevel.Add(menu);
continue;
}
var parent = temp.Find(x => x.Id == temp.ParentsId.Value);
if (parent.Subs == null)
parent.Subs = new List<Menus>();
parent.Subs.Add(menu);
}
return topLevel;
}
You should just be able to do:
context.Menus.Include(m => m.Subs).ToList();
The relationship fixup in EFCore will link all the menus together in a tree for you. In later EFs you don't even need the Include..
context.Menus.ToList();
Here is a table in SSMS:
Here is the data:
Here it is chopped up in a paint program and rearranged into a tree:
Here's the scaffolded entity:
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System.Collections.Generic;
namespace ConsoleApp7net5.Models
{
public partial class Menu
{
public Menu()
{
InverseParent = new HashSet<Menu>();
}
public int Id { get; set; }
public int? ParentId { get; set; }
public string Label { get; set; }
public virtual Menu Parent { get; set; }
public virtual ICollection<Menu> InverseParent { get; set; }
}
}
Here's what we see after asking EFC (5, in my case) to download it all with just a ToList:
Of course it might make sense to start with a root (or multiple roots but my data only has one)
Don't give classes plural names (Menus), btw, and don't give properties plural names if they aren't collections/enumerables (Parents) - it makes for very confusing code
So I've got a nested data structure like this:
public class ContractTerm
{
public int ContractId { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public int TermOrder { get; set; }
public TermItem TermNavigation { get; set; }
}
public class TermItem
{
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public string Text { get; set; }
public ICollection<ContractTerm> ContractNavigation { get; set; }
}
I've also got a class to map the section/subsection pairings in a more EF-friendly way (IRL this is an enum with attribute values and a helper, but this class abstracts away some work not necessary to reproduce the issue):
public class Section
{
public string Name { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
}
Both ContractTerm and TermItem have their own collections in a DbContext, and I'm trying to get a collection of all text entries assigned to specific Sections for a given ContractId. I have the following class to contain it:
public class TextsBySection
{
public string SectionName { get; set; }
public IEnumerable<string> Texts { get; set; }
}
I want to select a collection of TextsBySection, and have something like this:
public class ContractManager
{
//insert constructor initializing MyContext here
private MyContext Context { get; }
public IEnumerable<MyOutputClass> GetTerms(int contractId, IEnumerable<Section> sections)
{
Func<string, string, IEnumerable<string>> getBySection =
(section, subsection) => context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId
&& x.SectionId == section
&& x.SubsectionId == subsection)
.Select(x => x.TermNavigation.Text);
var result = sections.Select(x => new MyOutputClass
{
SectionName = x.Name,
Texts = getBySection(x.SectionId, x.SubsectionId)
}).ToList();
return result;
}
}
This works fine and dandy, but it hits the database for every Section. I feel like there's got to be a way to use Join and/or GroupBy to make it only query once, but I can't quite see it. Something like this, perhaps:
var result = context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId)
.Join(sections,
term => //something
section => //something
(term, section) => /*something*/)
If all this were in SQL, selecting the necessary data would be easy:
SELECT sections.name,
term_items.text
FROM contract_terms
JOIN term_items
ON term_items.section_id = contract_terms.section_id
AND term_items.subsection_id = contract_terms.subsection_id
AND term_items.term_id = contract_terms.term_id
JOIN sections --not a real table; just corresponds to sections argument in method
ON sections.section_id = contract_terms.section_id
AND sections.subsection_id = contract_terms.subsection_id
...and then I could group the results in .NET. But I don't understand how to make a single LINQ query that would do the same thing.
I changed my answer, well I would do something like this... maybe this may help you.
public static void Main(string[] args)
{
List<Section> sections = new List<Section>();
List<ContractTerm> contractTerms = new List<ContractTerm>();
List<TermItem> termItens = new List<TermItem>();
//considering lists have records
List<TextsBySection> result = (from contractTerm in contractTerms
join termItem in termItens
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId,
contractTerm.TermId
}
equals new
{
termItem.SectionId,
termItem.SubsectionId,
termItem.TermId
}
join section in sections
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId
} equals new
{
section.SectionId,
section.SubsectionId
}
select
new
{
sectionName = section.Name,
termItemText = termItem.Text
}).GroupBy(x => x.sectionName).Select(x => new TextsBySection()
{
SectionName = x.Key,
Texts = x.Select(i=> i.termItemText)
}).ToList();
}
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
public class GRNMaster
{
public string ID { get; set; }
public string GRNNo { get; set; }
public List<GRNDetails> GRNDetails { get; set; }
}
public class GRNDetails
{
public string GRNID { get; set; }
public string ItemID { get; set; }
public string ItemType { get; set; }
public int RecevedQty { get; set; }
}
above classes contains some of the properties of GRN header class and Detail class. i Grn can consist of many items so that "List GRNDetails" is there to keep them.
I take a GRN List from a method which will store in the variable GrnList
public List<GRNMaster> GrnList
I have a list of Items IDs
public List<string> ItemIDList
In the controller I want to loop the ItemIDList (List ItemIDList) and get sum for that particular item based on the List
int ItemQty = 0;
foreach (var item in ItemIDList)
{
ItemQty = 0;
var ItemQty = //some code using GrnList
// rest of the programming code based on the
// item qty
}
Using LINQ
var totalQty = 0;
foreach (var item in ItemIDList)
{
var sumOfCurrentItem = GrnList.SelectMany(s => s.GRNDetails
.Where(f => f.ItemID == item)).Select(f => f.RecevedQty).Sum();
totalQty += sumOfCurrentItem ;
}
Or even a one liner replacement of the foreach loop (Credit goes to ReSharper :) )
int totalQty = ItemIDList.Sum(item => GrnList
.SelectMany(s => s.GRNDetails.Where(f => f.ItemID == item))
.Select(f => f.RecevedQty).Sum());
If I've understood your requirements correctly then this is what you need:
IEnumerable<int> query =
from grn in GrnList
from grnDetail in grn.GRNDetails
join itemID in ItemIDList on grnDetail.ItemID equals itemID
select grnDetail.RecevedQty;
int ItemQty = query.Sum();
I have a class
public class AmenityShowtime
{
public String AmenityKey { get; set; }
public String AmenityIcon { get; set; }
public String shTimes { get; set; }
}
Ultimately, I want to have a structure that is comprised of these nested classes:
public class AmenityShowtime
{
public String AmenityKey { get; set; }
public String AmenityIcon { get; set; }
public String shTimes { get; set; }
}
// Movie Class
public class theMovie
{
public String Movie_title { get; set; }
public String Rating { get; set; }
public String RunTime { get; set; }
public List<theAmenities> amens { get; set; }
}
public class theAmenities
{
public String AmenityName { get; set; }
public String AmenityIcon { get; set; }
public List<theTimes> times { get; set; }
}
public class theTimes
{
public String timepref { get; set; }
}
I needed to group by AmenityKey and shtimes ... I used the following code:
IEnumerable<IGrouping<string, string>> query = amShow.GroupBy(ams => ams.AmenityKey, ams => ams.shTimes);
List<theAmenities> thisMoviesList = new List<theAmenities>();
foreach (IGrouping<string, string> grp in query)
{
theAmenities thisMovieAmenities = new theAmenities();
thisMovieAmenities.AmenityName = grp.Key;
List<theTimes> thisMovieTimes = new List<theTimes>();
foreach (string stimes in grp)
{
theTimes thisShowtime = new theTimes();
thisShowtime.timepref = stimes;
thisMovieTimes.Add(thisShowtime);
}
thisMovieAmenities.times = thisMovieTimes;
thisMoviesList.Add(thisMovieAmenities);
}
works great, with one exception ... how do I get access to the field: AmenityIcon in the
foreach (IGrouping<string, string> grp in query)
{
theAmenities thisMovieAmenities = new theAmenities();
thisMovieAmenities.AmenityName = grp.Key;
I want to be able to do the following:
thisMovieAmenites.AmenityIcon = AmenityIcon
I must be missing something, thank you in advance
If you want to include the amenityIcon in the grouping, then one way is this:
var query = amShow.GroupBy(ams => new {ams.AmenityKey, ams.AmenityIcon},
ams => ams.shTimes);
Note that you do need the var keyword now, since this is an IGrouping<Anonymous-type,String>
(This will group based on key and icon, I am assuming that the icon is the same if the key is the same, so this is essentially the same as grouping only by key)
Now you can do
foreach (var grp in query)
{
theAmenities thisMovieAmenities = new theAmenities();
thisMovieAmenities.AmenityName = grp.Key.AmenityKey;
thisMovieAmenities.AmenityIcon = grp.Key.AmenityIcon;
...
If you want to avoid anonymous types, you could also create your class theAmenities right in the grouping:
IEnumerable<IGrouping<theAmenities,string>> query = amShow.GroupBy(
ams => new theAmenities(){AmenityKey = ams.AmenityKey, AmenityIcon = ams.AmenityIcon},
ams => ams.shTimes);
but that requires that your class theAmenities implements the IEquatable<T> interface, to allow the GroupBy operator to recognize all theAmenities objects with the same key as equal.
If you use an anonymous type instead, this will work automatically. But this approach has the advantage that you could let your IEquatable ignore the AmenityIcon, if indeed it is possible that there are multiple items with the same key but different icons