LINQ to SQL many to many int ID array criteria query - linq

Ok this should be really simple, but I am doing my head in here and have read all the articles on this and tried a variety of things, but no luck.
I have 3 tables in a classic many-to-many setup.
ITEMS
ItemID
Description
ITEMFEATURES
ItemID
FeatureID
FEATURES
FeatureID
Description
Now I have a search interface where you can select any number of Features (checkboxes).
I get them all nicely as an int[] called SearchFeatures.
I simply want to find the Items which have the Features that are contained in the SearchFeatures.
E.g. something like:
return db.Items.Where(x => SearchFeatures.Contains(x.ItemFeatures.AllFeatures().FeatureID))
Inside my Items partial class I have added a custom method Features() which simply returns all Features for that Item, but I still can't seem to integrate that in any usable way into the main LINQ query.
Grr, it's gotta be simple, such a 1 second task in SQL. Many thanks.

The following query will return the list of items based on the list of searchFeatures:
from itemFeature in db.ItemFeatures
where searchFeatures.Contains(itemFeature.FeatureID)
select itemFeature.Item;
The trick here is to start with the ItemFeatures table.
It is possible to search items that have ALL features, as you asked in the comments. The trick here is to dynamically build up the query. See here:
var itemFeatures = db.ItemFeatures;
foreach (var temp in searchFeatures)
{
// You will need this extra variable. This is C# magic ;-).
var searchFeature = temp;
// Wrap the collection with a filter
itemFeatures =
from itemFeature in itemFeatures
where itemFeature.FeatureID == searchFeature
select itemFeature;
}
var items =
from itemFeature in itemFeatures
select itemFeature.Item;

Related

Tricky LINQ and nested collection

I am trying to figure out an easier way I can take the following code and condense it into as few lines as possible. Ideally, I would like to get an IDictionary> out of this.
var report = db.Reports.Select(rid => rid.ID == reportId) as Report;
Dictionary<string, List<string>> notes = new Dictionary<string, List<string>>();
foreach (var subreport in report.SubReports)
{
foreach (var subreportitem in subreport.SubReportItems)
{
notes[subreportitem.Title] = new List<string>();
foreach (var note in subreportitem.SubReportItemNotes)
{
notes[subreportitem.Title].Add(note.NoteDetails);
}
}
}
Ideally, I had wanted to do something like so:
from report in db.Reports
where report.ID == reportId
from subreports in report.SubReports
from subreportitems in subreports.SubReportItems
from notes in subreportitems.SubReportItemNotes
//Unsure how to select into the desired dictionary...
This should be equivalent:
db.Reports
.Where(rpt => rpt.ID == reportId)
.Cast<Report>()
.SelectMany(rpt => rpt.SubReports)
.SelectMany(subRpt => subRpt.SubReportItems)
.ToDictionary(
sri => sri.Title,
sri => sri.SubReportItemNotes.SelectMany(note => note.NoteDetails);
Notes:
The first line of your code uses .Select(rid => rid.Id == reportId), but I assumed this should have been Where instead of Select, otherwise you would end up with a collection of null because the Select results would be of type bool and the as Report would output null for each.
This only works if the Titles of all of your SubReportItems are unique. It is conceivable that one Report could have 10 SubReports and that among those SubReports there are two or more SubReportItems with the same Title. If that is the case, then you may need to rethink this a bit, otherwise you will get a DuplicateKeyException when you try to add a Title that is already in the dictionary.
Explanation:
Basically, we are taking the set of reports, and applying the condition that we only want reports where the ID is the desired one. Personally, I would put this in a separate line, and use SingleOrDefault instead of Where because I expect only one result.
Next, we call .Cast<Report> just because you are using as Report, so I guess you had a need for it. This may be redundant and unnecessary in practice.
The first .SelectMany call will get all SubReports of all Reports. Again, we probably will only ever have one Report object at this point.
Now we have a bunch of SubReports, but we really want to get all the SubReportItems, so we use another SelectMany to get those.
Now that we have all the SubReportItems from all the SubReports from all the (1) Reports, we create the dictionary. For each SubReportItem we create a key from the Title property, and then for the value we use one final SelectMany to get all the NoteDetails objects associated with all the current SubReportItemNotes.

LINQ to EF - Find records where string property of a child collection at least partially matches all records in a list of strings

I am currently writing a web-based 'recipe' application using LINQ and Entity Framework 5.0. I've been struggling with this query for awhile, so any help is much appreciated!
There will be a search function where the users can enter a list of ingredients that they want the recipe results to match. I need to find all recipes where the associated ingredient collection (name property) contains the text of every record in a list of strings (the user search terms). For example, consider the following two recipes:
Tomato Sauce: Ingredients 'crushed tomatoes', 'basil', 'olive oil'
Tomato Soup: Ingredients 'tomato paste', 'milk', 'herbs
If the user used the search terms 'tomato' and 'oil' it would return the tomato sauce but not the tomato soup.
var allRecipes = context.Recipes
.Include(recipeCategory => recipeCategory.Category)
.Include(recipeUser => recipeUser.User);
IQueryable<Recipe> r =
from recipe in allRecipes
let ingredientNames =
(from ingredient in recipe.Ingredients
select ingredient.IngredientName)
from i in ingredientNames
let ingredientsToSearch = i where ingredientList.Contains(i)
where ingredientsToSearch.Count() == ingredientList.Count()
select recipe;
I've also tried:
var list = context.Ingredients.Include(ingredient => ingredient.Recipe)
.Where(il=>ingredientList.All(x=>il.IngredientName.Contains(x)))
.GroupBy(recipe=>recipe.Recipe).AsQueryable();
Thank you for your help!
Just off the top of my head i would go for something like this
public IEnumerable<Recipe> SearchByIngredients(params string[] ingredients)
{
var recipes = context.Recipes
.Include(recipeCategory => recipeCategory.Category)
.Include(recipeUser => recipeUser.User);
foreach(var ingredient in ingredients)
{
recipes = recipes.Where(r=>r.Ingredients.Any(i=>i.IngredientName.Contains(ingredient)));
}
//Finialise the queriable
return recipes.AsEnumerable();
}
You can then call it using:
SearchByIngredients("tomatoes", "oil");
or
var ingredients = new string[]{"tomatoes", "oil"};
SearchByIngredients(ingredients );
What this is going to do is attach where clauses to the queriable recipes for each of your search terms. Multiple where clauses are treated as ANDs in SQL (which is what you want here anyway). Linq is quite nice in the way that we can do this, at then end of the function we finalise the queriable essentially saying all that stuff that we just did can get turned into a single query back to the DB.
My only other note would be you really want to be indexing/full text indexing the Ingredient name column or this wont scale terribly well.

Entity Framework, Table Per Type and Linq - Getting the "Type"

I have an Abstract type called Product, and five "Types" that inherit from Product in a table per type hierarchy fashion as below:
I want to get all of the information for all of the Products, including a smattering of properties from the different objects that inherit from products to project them into a new class for use in an MVC web page. My linq query is below:
//Return the required products
var model = from p in Product.Products
where p.archive == false && ((Prod_ID == 0) || (p.ID == Prod_ID))
select new SearchViewModel
{
ID = p.ID,
lend_name = p.Lender.lend_name,
pDes_rate = p.pDes_rate,
pDes_details = p.pDes_details,
pDes_totTerm = p.pDes_totTerm,
pDes_APR = p.pDes_APR,
pDes_revDesc = p.pDes_revDesc,
pMax_desc = p.pMax_desc,
dDipNeeded = p.dDipNeeded,
dAppNeeded = p.dAppNeeded,
CalcFields = new DAL.SearchCalcFields
{
pDes_type = p.pDes_type,
pDes_rate = p.pDes_rate,
pTFi_fixedRate = p.pTFi_fixedRate
}
}
The problem I have is accessing the p.pTFi_fixedRate, this is not returned with the Products collection of entities as it is in the super type of Fixed. How do I return the "super" type of Products (Fixed) properties using Linq and the Entity Framework. I actually need to return some fields from all the different supertypes (Disc, Track, etc) for use in calculations. Should I return these as separate Linq queries checking the type of "Product" that is returned?
This is a really good question. I've had a look in the Julie Lerman book and scouted around the internet and I can't see an elegant answer.
If it were me I would create a data transfer object will all the properties of the types and then have a separate query for each type and then union them all up. I would insert blanks into the DTO properies where the properties aren't relevant to that type. Then I would hope that the EF engine makes a reasonable stab at creating decent SQL.
Example
var results = (from p in context.Products.OfType<Disc>
select new ProductDTO {basefield1 = p.val1, discField=p.val2, fixedField=""})
.Union(
from p in context.Products.OfType<Fixed>
select new ProductDTO {basefield1 = p.val1, discField="", fixedField=p.val2});
But that can't be the best answer can it. Is there any others?
So Fixed is inherited from Product? If so, you should probably be querying for Fixed instead, and the Product properties will be pulled into it.
If you are just doing calculations and getting some totals or something, you might want to look at using a stored procedure. It will amount to fewer database calls and allow for much faster execution.
Well it depends on your model, but usually you need to do something like:
var model = from p in Product.Products.Include("SomeNavProperty")
.... (rest of query)
Where SomeNavProperty is the entity type that loads pTFi_fixedRate.

Imroving/Modifying LINQ query

I already have a variable containing some groups. I generated that using the following LINQ query:
var historyGroups = from payee in list
group payee by payee.Payee.Name into groups
orderby groups.Key
select new {PayeeName = groups.Key, List = groups };
Now my historyGroups variable can contain many groups. Each of those groups has a key which is a string and Results View is sorted according to that. Now inside each of those groups there is a List corresponding to the key. Inside that List there are elements and each one those element is an object of a particular type. One of it's fields is of type System.DateTime. I want to sort this internal List by date.
Can anyone help with this? May be modify the above query or a new query on variable historyGroups.
Thanks
It is not clear to me what you want to sort on (the payee type definition is missing as well)
var historyGroups = from payee in list
group payee by payee.Payee.Name into groups
orderby groups.Key
select new {
PayeeName = groups.Key,
List = groups.OrderBy(payee2 => payee2.SomeDateTimeField)
};
Is most straightforward.
If you really want to sort only by date (and not time), use SomeDateTimeField.Date.
Inside that List there are elements and each one those element is an object of a particular type. One of it's fields is of type System.DateTime
This leads me to maybe(?) suspect
List = groups.OrderBy(payee2 => payee2.ParticularTypedElement.DateTimeField)
Or perhaps even
List = groups.OrderBy(payee2 => payee2.ObjectsOfParticularType
.OfType<DateTime>()
.FirstOrDefault()
)
I hope next time you can clarfy the question a bit better, so we don't have to guess that much (and come up with a confusing answer)

linq problem with distinct function

I am trying to bind distinct records to a dropdownlist. After I added distinct function of the linq query, it said "DataBinding: 'System.String' does not contain a property with the name 'Source'. " I can guarantee that that column name is 'Source'. Is that name lost when doing distinct search?
My backend code:
public IQueryable<string> GetAllSource()
{
PromotionDataContext dc = new PromotionDataContext(_connString);
var query = (from p in dc.Promotions
select p.Source).Distinct();
return query;
}
Frontend code:
PromotionDAL dal = new PromotionDAL();
ddl_Source.DataSource = dal.GetAllSource();
ddl_Source.DataTextField = "Source";
ddl_Source.DataValueField = "Source";
ddl_Source.DataBind();
Any one has a solution? Thank you in advance.
You're already selecting Source in the LINQ query, which is how the result is an IQueryable<string>. You're then also specifying Source as the property to find in each string in the databinding. Just take out the statements changing the DataTextField and DataValueField properties in databinding.
Alterantively you could remove the projection to p.Source from your query and return an IQueryable<Promotion> - but then you would get distinct promotions rather than distinct sources.
One other quick note - using query syntax isn't really helping you in your GetAllSources query. I'd just write this as:
public IQueryable<string> GetAllSource()
{
PromotionDataContext dc = new PromotionDataContext(_connString);
return dc.Promotions
.Select(p => p.Source)
.Distinct();
}
Query expressions are great for complicated queries, but when you've just got a single select or a where clause and a trivial projection, using the dot notation is simpler IMO.
You're trying to bind strings, not Promotion objects... and strings do not have Source property/field
Your method returns a set of strings, not a set of objects with properties.
If you really want to bind to a property name, you need a set of objects with properties (eg, by writing select new { Source = Source })

Resources