This has been bugging me for a while since I'm trying to come up with an optimized way of querying this.
So lets say I have 3 cross reference tables that share a common column, where that common column will do a final join on a main table that contains more info.
For example:
Let's say I have the following:
Customers //properties: ID, Name, Address
IEnumberable<CustomerSports> //properties: CustomerID, SportsID
IEnumberable<CustomerLocation> //properties: CustomerID, LocationID
IEnumberable<CustomerPets> //properties: CustomerID, PetsID
So I can make queries such as:
Give me a list of customers that plays lacrosse, football, soccer (CustomerSports)... and have dogs and cats (CustomerPets), that live in New York (CustomerLocation). The lookup tables can be nullable, so Customers could play sports, but have no pets.
Then when I get a list of customers, I'll join that common column (CustomerID) on the customer table to retrieve the ID, Name, and Address.
I was thinking about having the customer table join on each lookup, and then doing a union to fetch the list of customers, but I don't know if that is the correct way of doing it.
As long as you have setup your design correctly then each Customer should have a Sports collection, a Pets collection and a Locations (unless this last one is a one-to-one join?).
If those relationships are setup, then you can query as follows:
var sports = new string[] { "lacrosse", "football", "soccer" };
var pets = new string[] { "cat", "dog" };
var locations = new string[] { "new york" };
var sportyPetLoversInNewYors = db.Customers
.Where(cust => sports.All(sport => cust.Sports.Any(custSport => custSport.Name == sport)))
.Where(cust => pets.All(pet => cust.Pets.Any(custPet => custPet.Name == pet)))
.Where(cust => locations.All(loc => cust.Locations.Any(custLoc => custLoc.Name = loc)))
// could customise the select here to include sub-lists or whatever
.Select();
This assumes that you only want people that have all 6 criteria. If you want people that like at least one of those sports, with at least one of those pets, and (assuming you used more than one location) are in at least one of those locations, the Where expression would change like the following
.Where(cust => cust.Sports.Any(custSport => sports.Contains(custSport.Name)))
Let me know if you need further explanation.
One method of doing this, if i understood what you were after. Allows multiple sports, and multiple pets, or none.
var contacts = from cust in customer
join sport in sports on cust.CustomerID equals sport.CustomerID into multisport from sport in multisport.DefaultIfEmpty()
join loc in location on cust.CustomerID equals loc.CustomerID
join pet in pets on cust.CustomerID equals pet.CustomerID into multipet from pet in multipet.DefaultIfEmpty()
select new
{
cust.CustomerID,
multisport,
loc.LocationID,
multipet
};
Related
I supposed in the process developed is such that it must show all the movies that are into film tablen and showing off, but this is how I have tried to do this:
it must find out which genres have in users tablen where after to show the users who like the first.
//As I said, I have a session at the top of the code.
int brugerid = Convert.ToInt16(Session["id"]);
var result = (from f in db.films
//it must find out which genres have in users tablen where after to show the users who like the first.
//brugere are users
//gener It is the genes users like.
join usersgenerId in brugere.Fk_generId on gener.generId equals usersgenerId.BrugereId
select new
{
image_navn = ((f.imgs.FirstOrDefault(i => i.feature == true)).navn == null ? "default.png" : (f.imgs.FirstOrDefault(i => i.feature == true)).navn),
image_feature = f.imgs.Where(A => A.feature == true),
film_navn = f.navn,
film_id = f.filmId,
film_tekst = f.tekst,
film_gener = f.gener.navn
}).ToList();
RepeaterFilmList.DataSource = result;
RepeaterFilmList.DataBind();
Table information
Brugere the name
id = BrugereId
Fk_generId belonging to the genes that user has selected.
and many other
Gener is the name
has generId as id
As mentioned in the comment, the question really is: show all movies that is in the same genre that the user preferred and then show everything else.
Although the following approach might not be db efficient (too lazy to create the db for this, so I am simulating everything in memory and using Linq to Object to solve the issue), it can certainly be resolved by the following steps:
Get the recommendation (matching the user's movie genre preference) like so:
var recommendation =
from f in films
from ug in userGenres
where ug.UserId == user.Id && ug.GenreId == f.GenreId
select f;
Now that we know what the user preferred, we can further filter this to just the preferred films' Id... and use that to get the rest of the unpreferred films (basically anything not matching the preferred film Ids):
var recommendedFilmIds = recommendation.Select(f => f.Id);
var everythingElse =
from f in films
where !recommendedFilmIds.Contains(f.Id)
select f;
Finally, join them together using Union and injecting the nessary fields for display purpose like Genre.Name, etc. like so:
var filmList = recommendation.Union(everythingElse).Select(f => new {
f.Id,
f.Title,
Genre = genres.Where(g => g.Id == f.GenreId).Select(g => g.Name).First()
});
And there you have it, the combined list will now contains both preferred films first (at top), followed by unpreferred films afterward.
The simulated tables are as follows: films which contains its own Id and genreId and userGenres which contains many to many relationship between user and genre and a particular user object which contains the user id.
An example of this can be found at: https://dotnetfiddle.net/Skuq3o
If you use EF, and you have a navigation property to genre table and you want to include those table as part of the query, use .Include(x => x.genre) or whatever you call your genre table after from f in films to avoid n+1 select if you wish to include the genre info in the final select clause.
I can perform a join using a navigation property, which to me is more DRY because I'm not repeating the join criteria everywhere:
(from c in db.Companies
from e in c.Employees
select new { Employee = e, Company = c}).ToList();
Because the ORM knows how Companies is related to Employees, the c.Employees navigation property is used to infer the FK criteria for the join.
What is the direct translation to extension/lambda syntax of a multiple from clause via navigation property?
I know there is a Join extension method, but requires you explicitly name the FK's being compared, rather than it imply that criteria by the navigation property. This is non-working but hopefully expresses my intent:
db.Companies
.Join(c.Employees, /* don't want to explicitly name FKs*/)
.Select(x => new { Employee = x.e, Company = x.c}).ToList();
Of course Join(c.Employees doesn't work because there is no c in this context, but idea being somehow use the Companies.Employees navigation property to imply the join criteria.
I know I can do:
db.Companies.Select(c => new { Employees = c.Employees, Company = c })
but that is a different result set, as it returns one record per company, and then the list of employees as a nested property. Rather than the first which is a join, thus there is a record for every related combination, and the result has a Employee property instead of a Employees collection.
I'm not sure, but guessing .SelectMany is the direct translation. You don't get a c reference to the parent, so if you do multiple of these:
db.Companies.SelectMany(c=>c.Employees).SelectMany(e=>e.VacationDays).Select(v => new { VacationDay = v, Employee = v.Employee, Company = v.Employee.Company })
You have to walk backwards across the relationships to flatten out the join. In linq it's much simpler because you would have c, e and v all in the context of the select. I don't know if you can express the same thing in extension methods, such that all three alias/references are passed down. Maybe just a consequence of the extension method syntax, but hoping someone will provide a better equivalent.
SelectMany is indeed what multiple from clauses are mapped into.
In order to keep variables in scope the projection each SelectMany needs to project the sequence into a new anonymous object that keeps all of the appropriate variables in scope:
var query = db.Companies.SelectMany(company => company.Employees,
(company, employee) => new
{
company,
employee
});
To add additional projections for additional nested navigation properties, simply repeat the pattern with a subsequent call to SelectMany:
var query = db.Companies.SelectMany(company => company.Employees,
(company, employee) => new
{
company,
employee
}).SelectMany(pair => pair.employee.VacationDays,
(pair, vactionDay) => new
{
pair.company,
pair.employee,
vactionDay,
});
See this blog post for some more details and an in-depth description of this transformation, and how it scales.
Wouldn't it just something like:
db.Employees.Select(m => new { Employee = m, Company = m.Company });
As each employee has a Company, why don't just add navigation property "Company" to Employee entity?
To get vacations, just change it to the following:
db.Employees.SelectMany(
employee => employee.VacationDays,
(employee, vacationDay) => new
{
Employee = employee,
Company = employee.Company,
VacationDay = vacationDay
});
Update:
Actually, there is no difference between:
(from c in db.Companies
from e in c.Employees
select new { Employee = e, Company = c}).ToList();
and:
(from e in c.Employees
from c in db.Companies
select new { Employee = e, Company = c}).ToList();
Thanks for looking!
Background
I am writing in C# and using LINQ to query entities for a report. In this report, I have a set of entities that basically look like this:
Customer{
Name: "Bob",
ProductsPurchased: ArrayOfChildEntities[{
ProductId: 1,
ProductTypeId: 5,
ProductName: "FooBuzz"
},
{...},
{...}]
}
ProductsPurchased is an array of child entities that contain a product type id. Let's say that I pass in an array of type ids that the user chose from a filter list in the view:
var ProductTypesToShow = [1, 3, 5];
So for each customer returned, I want to show only the products they have purchased that are of type 1,3, or 5. If a customer has never purchased at least one product of type 1,3, or 5, then that entire customer object should be removed from the result set.
What I have tried
I have tried using something like this:
var customers = db.Customers.Where(c => c.ProductsPurchased.Select(p => ProductTypesToShow.Contains(p.ProductTypeId));
But this fails. I have also tried various versions of Intersect and Any but unfortunately they all fail for one reason or another, or they fail to do all of the things I need:
Select only customers who have purchased a product of type 1,3, or 5.
Of those customers, remove any products not of type 1,3, or 5 before sending the data back to the view.
Finally, I wrote a foreach monstrosity that iterates all customers found in an initial query and then iterates their products to filter by product type, but this was unacceptably slow (about 3 minutes per query!!).
I feel like I must be missing something obvious here. Any suggestions are appreciated!
It's hard to say if it's gonna work with LINQ to Entities, but I would try that one:
var results = (from c in customers
select new
{
Name = c.Name,
Products = c.ProductsPurchased.Where(p => ProductTypesToShow.Contains(p.ProductTypeId))
} into c2
where c2.Products.Any()
select new
{
Name = c2.Name,
Products = c2.Products.ToArray()
}).ToArray();
It should return an array of anonymous type with 2 properties: Name and Products.
This is how I'd do it in LinqToSql. I'm unsure if LinqToEntities supports Contains.
List<int> ProductTypesToShow = new List<int>() {1,3,5};
IQueryable<Product> productQuery = dc.Products
.Where(p => ProductTypesToShow.Contains(p.ProductTypeId)); //deferred
var customerProductQuery =
from c in dc.Customers
join p in productQuery on c.CustomerID equals p.CustomerID into g //groupjoin!
where g.Any()
select new {Customer = c, Products = g.ToList()}; //customers with their products. deferred
List<Customer> result = new List<Customer>();
foreach(var pair in customerProductQuery) //query executed
{ //manual result shaping
Customer resultItem = pair.Customer;
resultItem.Products = pair.Products;
result.Add(resultItem);
}
return result;
var results = customers
.Where(cust=>cust.ProductsPurchased.Any(ProductsToShow.Contains))
.Select(cust=>new{
cust.Name,
Purchases=cust.ProductsPurchased.Where(ProductsToShow.Contains)
});
Another assignment block!
Basically the problem is that I can't get my output to order the price in a descending order while keeping it grouped by Country.
I know it's probably something so simple but I just can't seem to get it.
Any solutions?
Thanks!
Here is the question:
"6. Allow the user view the top five selling products in descending order grouped by country.
(10 marks)"
Here is my code:
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var q6 = (from t in northwind.Products
orderby t.UnitPrice descending
join o in northwind.Suppliers on t.SupplierID equals o.SupplierID
group t.UnitPrice by new {o.Country, t.UnitPrice} into grouped
select new
{
Output = grouped.Key
}).Take(5);
lbxTop5.ItemsSource = q6;
}
"6. Allow the user view the top five selling products in descending order grouped by country. (10 marks)"
I could read that two ways.
A) Get the top five selling products, group those 5 products by country.
OR
B) For each country, what are the top five selling products?
I think B makes more sense, so I'll do that one.
Also - what is a top selling product? and how is the country related to it? I think that the Customer's country matters more than the Supplier's. Also - I think that Quantity in the OrderDetails can tell me which products are top selling. Note: your instructor may have other ideas than I do, so use these assumptions at your own peril.
from c in northwind.Customers
from o in c.Orders //all froms except first are calls to SelectMany (one to many)
from od in o.OrderDetails //navigational properties -> no need to write explicit joins
let p = od.Product // now we go many to one, so we don't need SelectMany
group od
by new {c.Country, Product = p } //anon grouping key
into productGroup
let country = productGroup.Key.Country
let product = productGroup.Key.Product
let quantity = productGroup.Sum(od2 => od2.Quantity)
group new {Product = product, Quantity = quantity} //anon group elements
by country
into countryGroup
select new {
Country = countryGroup.Key,
Summaries = countryGroup
.OrderByDescending(summary => summary.Quantity)
.ThenBy(summary => summary.Product.ProductId) //tiebreaker
.Take(5)
.ToList()
}
I have a fairly complicated join query that I use with my database. Upon running it I end up with results that contain an baseID and a bunch of other fields. I then want to take this baseID and determine how many times it occurs in a table like this:
TableToBeCounted (One to Many)
{
baseID,
childID
}
How do I perform a linq query that still uses the query I already have and then JOINs the count() with the baseID?
Something like this in untested linq code:
from k in db.Kingdom
join p in db.Phylum on k.KingdomID equals p.KingdomID
where p.PhylumID == "Something"
join c in db.Class on p.PhylumID equals c.PhylumID
select new {c.ClassID, c.Name};
I then want to take that code and count how many orders are nested within each class. I then want to append a column using linq so that my final select looks like this:
select new {c.ClassID, c.Name, o.Count()}//Or something like that.
The entire example is based upon the Biological Classification system.
Assume for the example that I have multiple tables:
Kingdom
|--Phylum
|--Class
|--Order
Each Phylum has a Phylum ID and a Kingdom ID. Meaning that all phylum are a subset of a kingdom. All Orders are subsets of a Class ID. I want to count how many Orders below to each class.
select new {c.ClassID, c.Name, (from o in orders where o.classId == c.ClassId select o).Count()}
Is this possible for you? Best I can do without knowing more of the arch.
If the relationships are as you describe:
var foo = db.Class.Where(c=>c.Phylum.PhylumID == "something")
.Select(x=> new { ClassID = x.ClassID,
ClassName = x.Name,
NumOrders= x.Order.Count})
.ToList();
Side question: why are you joining those entities? Shouldn't they naturally be FK'd, thereby not requiring an explicit join?