LINQ SubCollection SubSelect - linq

Hello – I’m trying to get a where condition to apply to a sub collection. I can get the criteria to return the proper parents. However, I want the sub collection to be limited to the criteria as well.
In my example code, I only want people with “LINK” skills; also, I only want the skills for each person to equal “LINK.” That is, each person should only have “LINK” for their skills.
Thanks in advance.
List<Skill> skills = new List<Skill>();
skills.Add(new Skill(){SkillName="ASP.NET"});
skills.Add(new Skill(){SkillName="C#"});
Person p1 = new Person(){ Name="Me", Skills=skills} ;
List<Skill> skills2 = new List<Skill>();
skills2.Add(new Skill(){SkillName="ASP.NET"});
skills2.Add(new Skill(){SkillName="C#"});
skills2.Add(new Skill(){SkillName="LINQ"});
Person p2 = new Person(){ Name="You", Skills=skills2} ;
List<Person> People = new List<Person>();
People.Add(p1);
People.Add(p2);
var oResult = (from item in People
from sk in item.Skills
where sk.SkillName == "LINQ"
select item
).ToList();
When I run this. I get p2 (correct), but I want the skills of P2 to only equal LINQ

Do this:
var oResult = (from item in People
where item.Skills.Count() == 1 &&
item.Skills.Any(s => s.SkillName == "LINQ")
select item
).ToList();
This query will return nothing because p2 (You) has other skills in addition to LINQ.
You can do what you want this way:
foreach (var person in oResult)
{
person.Skills.RemoveAll(s => !s.SkillName.Equals("LINQ"));
}
Note: while using LINQ you're only filtering your data. To post process it you use something like the foreach I show you above.

Try this:
var oResult = (from item in People
where item.Skills != null
where item.Skills.Count() > 0
where item.Skills.All(s => s.SkillName == "LINQ")
select item
).ToList();
Even though your example shows that the Skills collection has a value, you want to make sure that your code doesn't blow up if the Skills property is null.
Also, the All predicate returns true if your list is empty so you need to ensure that it is not empty. The above query reads better, but depending on the implementation of the Skills property calling Count() may cause the entire collection to be iterated. You could use Any() instead to ensure that the collection is not empty.
var oResult = (from item in People
where item.Skills != null
where item.Skills.Any()
where item.Skills.All(s => s.SkillName == "LINQ")
select item
).ToList();

Related

Distinct works on IQueryable but not List<T>?? Why?

First Table is the View and Second is the result I want
This below query works fine
List<BTWStudents> students = (from V in db.vwStudentCoursesSD
where classIds.Contains(V.Class.Value)
select new BTWStudents
{
StudentId = V.StudentId
Amount= V.PaymentMethod == "Cashier Check" ? V.Amount: "0.00"
}).Distinct().ToList();
But I changed it to List to add string formatting(see below)
List<BTWStudents> students = (from V in db.vwStudentCoursesSD
where classIds.Contains(V.Class.Value)
select new {V}).ToList().Select(x => new BTWStudents
{
StudentId = V.StudentId
Amount= V.PaymentMethod == "Cashier Check" ? String.Format("{0:c}",V.Amount): "0.00"
}).Distinct().ToList();
With this Second Query I get this
Why is distinct not working in the second query?
When working with objects (in your case a wrapped anonymous type because you are using Select new {V} rather than just Select V), Distinct calls the object.Equals when doing the comparison. Internally, this checks the object's hash code. You'll find in this case, the hash code of the two objects is different even though the fields contain the same values. To fix this, you will need to override Equals on the object type or pass a custom IEqualityComparer implementation into the Distinct overload. You should be able to find a number of examples online searching for "Distinct IEqualityComparer".
Try this (moved your distinct to the first query and corrected your bugged if/then/else):
List<BTWStudents> students = (from V in db.vwStudentCoursesSD
where classIds.Contains(V.Class.Value)
select new {V}).Distinct().ToList().Select(x => new BTWStudents
{
classId = V.Class.HasValue ? V.Class.Value : 0,
studentName = V.StudentName,
paymentAmount = V.PaymentMethod == "Cashier Check" ? String.Format("{0:c}",x.V.AmountOwed): "0.00"
}).ToList();
You can get around using Distinct all together if you Group by StudentID
var studentsGroupedByPayment =
(from V in db.vwStudentCoursesSD
where classIds.Contains(V.Class.Value)
group V by V.StudentId into groupedV
select new
{
StudentID = groupedV.Key,
Amount = string.Format("{0:C}",
groupedV.First().PaymentMethod == "Cashier Check" ?
groupedV.First().Amount : 0.0)
}
).ToList();

Entity Framework 4 - What is the syntax for joining 2 tables then paging them?

I have the following linq-to-entities query with 2 joined tables that I would like to add pagination to:
IQueryable<ProductInventory> data = from inventory in objContext.ProductInventory
join variant in objContext.Variants
on inventory.VariantId equals variant.id
where inventory.ProductId == productId
where inventory.StoreId == storeId
orderby variant.SortOrder
select inventory;
I realize I need to use the .Join() extension method and then call .OrderBy().Skip().Take() to do this, I am just gettting tripped up on the syntax of Join() and can't seem to find any examples (either online or in books).
NOTE: The reason I am joining the tables is to do the sorting. If there is a better way to sort based on a value in a related table than join, please include it in your answer.
2 Possible Solutions
I guess this one is just a matter of readability, but both of these will work and are semantically identical.
1
IQueryable<ProductInventory> data = objContext.ProductInventory
.Where(y => y.ProductId == productId)
.Where(y => y.StoreId == storeId)
.Join(objContext.Variants,
pi => pi.VariantId,
v => v.id,
(pi, v) => new { Inventory = pi, Variant = v })
.OrderBy(y => y.Variant.SortOrder)
.Skip(skip)
.Take(take)
.Select(x => x.Inventory);
2
var query = from inventory in objContext.ProductInventory
where inventory.ProductId == productId
where inventory.StoreId == storeId
join variant in objContext.Variants
on inventory.VariantId equals variant.id
orderby variant.SortOrder
select inventory;
var paged = query.Skip(skip).Take(take);
Kudos to Khumesh and Pravin for helping with this. Thanks to the rest for contributing.
Define the join in your mapping, and then use it. You really don't get anything by using the Join method - instead, use the Include method. It's much nicer.
var data = objContext.ProductInventory.Include("Variant")
.Where(i => i.ProductId == productId && i.StoreId == storeId)
.OrderBy(j => j.Variant.SortOrder)
.Skip(x)
.Take(y);
Add following line to your query
var pagedQuery = data.Skip(PageIndex * PageSize).Take(PageSize);
The data variable is IQueryable, so you can put add skip & take method on it. And if you have relationship between Product & Variant, you donot really require to have join explicitly, you can refer the variant something like this
IQueryable<ProductInventory> data =
from inventory in objContext.ProductInventory
where inventory.ProductId == productId && inventory.StoreId == storeId
orderby inventory.variant.SortOrder
select new()
{
property1 = inventory.Variant.VariantId,
//rest of the properties go here
}
pagedQuery = data.Skip(PageIndex * PageSize).Take(PageSize);
My answer here based on the answer that is marked as true
but here I add a new best practice of the code above
var data= (from c in db.Categorie.AsQueryable().Join(db.CategoryMap,
cat=> cat.CategoryId, catmap => catmap.ChildCategoryId,
cat, catmap) => new { Category = cat, CategoryMap = catmap })
select (c => c.Category)
this is the best practice to use the Linq to entity because when you add AsQueryable() to your code; system will converts a generic System.Collections.Generic.IEnumerable to a generic System.Linq.IQueryable which is better for .Net engine to build this query at run time
thank you Mr. Khumesh Kumawat
You would simply use your Skip(itemsInPage * pageNo).Take(itemsInPage) to do paging.

Calling a function within a Linq query

If I want to iterate through a collection, and call a function on each element in the collection, I could go with :
foreach (var obj in objColl)
{
MyFunction(obj);
}
Should I want to do this with linq, I can use either of those :
//#1
var unused = (from var obj in objColl select MyFunction(obj)).ToList();
//#2
var unused = objColl.Select(obj => MyFunction(obj)).ToList();
I know this works, but it doesn't seem right. Of course, my actual cases are more complex queries that that, but it comes down to this since I could build my IQueryable with Linq and iterate through it and call the function.
Edit:
Here is one example of what I did. (Item# are things I can't disclose)
var dummyValue = (from
Item7 in dal.GetAgencyConvertions().Where(age => age.SourceName == "Item1" && age.TargetName == "Item2")
join Item6 in dal.GetAgencyConvertions().Where(age => age.SourceName == "Item2" && age.TargetName == "Item3") on Item6.TargetValue equals Item7.SourceValue
join agency in dal.GetAgencies() on Item7.SourceValue equals agency.Agency
orderby Item7.TargetValue
select vl.ValueListItems.Add(agency.ID, Item7.TargetValue)).ToList();
Go with the simple foreach, as you are clearly wanting to perform an action on (and/or using) the objects in your collection as opposed to wishing to project/filter/group/etc. the items in the sequence. LINQ is about the latter set of operations.
Edit: In the case of your update, I would simply create a query, and then iterate over the query in the foreach to perform the action.
var query = from Item7 in dal.GetAgencyConvertions().Where(age => age.SourceName == "Item1" && age.TargetName == "Item2")
join Item6 in dal.GetAgencyConvertions().Where(age => age.SourceName == "Item2" && age.TargetName == "Item3") on Item6.TargetValue equals Item7.SourceValue
join agency in dal.GetAgencies() on Item7.SourceValue equals agency.Agency
orderby Item7.TargetValue
select new { ID = agency.ID, Value = Item7.TargetValue };
foreach (var item in query)
vl.ValueListItems.Add(item.ID, item.Value);
To be frank, you have the same loop happening in your code, you merely mask it by using the ToList() extension method. As a byproduct, you are creating a list of values that you have no intention of using, while somewhat obfuscating the true intention of the code, all to save maybe a few characters.
Typically, a query shouldn't have any side effects (i.e. it shouldn't modify the state of the data or other data in your application) which raises the question, does MyFunction modify the state of your application? If it does, then you should use a foreach loop.
How about an Each() extension method?
public static void Each<T>(this IEnumerable<T> target, Action<T> action)
{
if (target == null) return;
foreach (T obj in target)
action(obj);
}

LINQ Query Similar to SQL WHERE x IN

I have a need to create a LINQ query that returns results based on a subquery. Not quite sure I'm wording that properly but the best way I know to ask is to show an example. How can I turn the following TSQL query into a LINQ query (tables are same name as objects):
SELECT CuisineId, Name
FROM Cuisine
WHERE CuisineId NOT IN (SELECT CuisineId
FROM RestaurantCuisine
WHERE RestaurantId = #id)
As you can guess, I'm trying to get a list of "available" cuisines to be listed for a user to add to a list of cuisines that a restaurant offers. The LINQ I have thus far returns ALL cuisines and doesn't take in account of the existing CuisineId's that have already been added to the other table:
I've looked all over for an example but not quite sure how to describe exactly what I need. I looked at the MSDN reference for LINQ queries but couldn't find anything like what I need:
MSDN LINQ Sample Queries
Anyone able to give me an example?
In C#:
var query =
from c in db.Cuisine
where !(from rc in db.RestaurantCuisine
where rc.RestaurantId == id
select rc.CuisineId)
.Contains(c.CuisineId)
select new {
c.CuisineId,
c.Name
};
In VB.NET:
Dim availableCuisines = _
From c In db.Cuisines _
Where Not (From rc In db.RestaurantCuisines _
Where rc.RestaurantId = id _
Select rc.CuisineId) _
.Contains(c.CuisineId) _
Select c
var cuisines = db.Cuisine.Where(c => !RestaurantCuisine.Any(
rc => rc.RestaurantId == c.Id && rc.CuisineId == c.CuisineId);
Try:
Cuisine.Where(c => !RestaurantCuisine.Select(rc => rc.CuisineID).Contains(c.CuisineID)).Select(c => c);
var query = (from c in Cuisine
where !(from rest in RestaurantCuisine
where (rest.RestaurantID == id)).Contains(c.CuisineId)
select new
{
CuisineID = c.CuisineID,
Name = c.Name
});

Help required to optimize LINQ query

I am looking to optimize my LINQ query because although it works right, the SQL it generates is convoluted and inefficient...
Basically, I am looking to select customers (as CustomerDisplay objects) who ordered the required product (reqdProdId), and are registered with a credit card number (stored as a row in RegisteredCustomer table with a foreign key CustId)
var q = from cust in db.Customers
join regCust in db.RegisteredCustomers on cust.ID equals regCust.CustId
where cust.CustomerProducts.Any(co => co.ProductID == reqdProdId)
where regCust.CreditCardNumber != null && regCust.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.DisplayName,
RegNumber = cust.RegNumber
};
As an overview, a Customer has a corresponding Person which has the Name; PersonID is a foreign key in Customer table.
If I look at the SQL generated, I see all columns being selected from the Person table. Fyi, DisplayName is an extension method which uses Customer.FirstName and LastName. Any ideas how I can limit the columns from Person?
Secondly, I want to get rid of the Any clause (and use a sub-query) to select all other CustomerIds who have the required ProductID, because it (understandably) generates an Exists clause.
As you may know, LINQ has a known issue with junction tables, so I cannot just do a cust.CustomerProducts.Products.
How can I select all Customers in the junction table with the required ProductID?
Any help/advice is appreciated.
The first step is to start your query from CustomerProducts (as Alex Said):
IQueryable<CustomerDisplay> myCustDisplay =
from custProd in db.CustomerProducts
join regCust in db.RegisteredCustomers
on custProd.Customer.ID equals regCust.CustId
where
custProd.ProductID == reqProdId
&& regCust.CreditCardNumber != null
&& regCust.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
This will simplify your syntax and hopefully result in a better execution plan.
Next, you should consider creating a foreign key relationship between Customers and RegisteredCustomers. This would result in a query that looked like this:
IQueryable<CustomerDisplay> myCustDisplay =
from custProd in db.CustomerProducts
where
custProd.ProductID == reqProdId
&& custProd.Customer.RegisteredCustomer.CreditCardNumber != null
&& custProd.Customer.RegisteredCustomer.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
Finally, for optimum speed, have LINQ compile your query at compile time, rather than run time by using a compiled query:
Func<MyDataContext, SearchParameters, IQueryable<CustomerDisplay>>
GetCustWithProd =
System.Data.Linq.CompiledQuery.Compile(
(MyDataContext db, SearchParameters myParams) =>
from custProd in db.CustomerProducts
where
custProd.ProductID == myParams.reqProdId
&& custProd.Customer.RegisteredCustomer.CreditCardNumber != null
&& custProd.Customer.RegisteredCustomer.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
);
You can call the compiled query like this:
IQueryable<CustomerDisplay> myCustDisplay = GetCustWithProd(db, myParams);
I'd suggest starting your query from the product in question, e.g. something like:
from cp in db.CustomerProducts
join .....
where cp.ProductID == reqdProdID
As you have found, using a property defined as an extension function or in a partial class will require that the entire object is hydrated first and then the select projection is done on the client side because the server has no knowledge of these additional properties. Be glad that your code ran at all. If you were to use the non-mapped value elsewhere in your query (other than in the projection), you would likely see a run-time exception. You can see this if you try to use the Customer.Person.DisplayName property in a Where clause. As you have found, the fix is to do the string concatenation in the projection clause directly.
Lame Duck, I think there is a bug in your code as the cust variable used in your select clause isn't declared elsewhere as a source local variable (in the from clauses).

Resources