How to get two table's common table using linq - c#

I want to combine these two linq queries to single query
is it possible?
var chestProducts = (from w in WareHouse
join c in Chests on w.Id equals c.WareHouseId
join p in Products on c.Id equals p.ContainerId
where (p.IsContainerChest == true && w.Id == 1)
select p
).ToList();
var boxProducts = (from w in WareHouse
join b in Boxes on w.Id equals b.WareHouseId
join p in Products on b.Id equals p.ContainerId
where (p.IsContainerChest != true && w.Id == 1)
select p
).ToList();
var allProducts = chestProducts.AddRange(boxProducts);
Should I use two queries?
And is this relation is healty?
Edit: Boxes and Chests tables are simplifed they have different fields

OK, from your comments I can see that you are using EF6 with code first. In that case I would make use of Table per Hierarchy and put both Box and Chest into one table (they will be separate classes still). One (big) caveat: I have been working exclusively with EF Core for a while now, and I haven't tested this. But I have used this pattern repeatedly and it works nicely.
Your entities should look something like this:
public class WareHouse
{
[Key]
public int Id { get;set; }
public string Name {get;set;}
public ICollection<Container> Containers {get;set;}
}
public abstract class Container
{
[Key]
public int Id {set;set;}
public int WareHouseId {get;set;}
[ForeignKey(nameof(WareHouseId))]
public WareHouse WareHouse {get;set;}
public string Name {get;set;}
public ICollection<Product> Products {get;set;}
}
public class Box : Container
{
// box specific stuff here
}
public class Chest : Container
{
// chest specific stuff here
}
public class Product
{
[Key]
public int Id {set;set;}
public int ContainerId {get;set;}
[ForeignKey(nameof(ContainerId))]
public Container Container {get;set;}
}
And your context something like this:
public class MyContext : DbContext
{
public virtual DbSet<WareHouse> WareHouses {get;set;}
public virtual DbSet<Container> Containers {get;set;}
public virtual DbSet<Product> Products {get;set;}
protected override void OnModelCreating(ModelBuilder builder)
{
// puts the class name in a column, makes it human readable
builder.Entity<Container>().Hasdiscriminator<string>("Type");
// i don't think you need to do this, but if it doesn't work try this
// builder.Entity<Box>().HasBaseType(typeof(Container));
// builder.Entity<Chest>().HasBaseType(typeof(Container));
}
}
Then you can get all the products from the warehouse with id=1 like this:
int warehouseId = 1;
Product[] allProducts = myContext.WareHouses
.Where(wh => wh.Id == warehouseId)
.SelectMany(wh => wh.Container)
//.OfType<Box>() if you only want products in boxes
.SelectMany(wh => wh.Products)
.ToArray();
I know you said in your comment that you tend to use linq's lambda syntax, but I feel I should point out that you are doing a lot of unnecessary joins in your query syntax example. linq to entities will take care of all that for you if you have set things up correctly.

Try this:
var allProducts = chestProducts.Concat(boxProducts);
Or you can also use Union
var allProducts = Enumerable.Union(chestProducts, boxProducts);

Related

How to write T-SQL many-to-many with subquery in EF

I have two classes with a many-to-many relationship in a ASP.NET EF application. I'm trying to find all Listings that have any Categories which is posted from a view. The categories are checkboxes on the view form.
These are the classes with navigation properties simplified for example:
public class Listing
{
public int ID { get; set; }
public ICollection<Category> Categories { get; set; }
...
}
public class Category
{
public int ID { get; set; }
public ICollection<Listing> Listings { get; set; }
...
}
// this is the join table created by EF code first for reference
public class CategoryListings
{
public int Category_ID { get; set; }
public int Listing_ID { get; set; }
}
This is the query I am trying to use in my MVC Controller but it doesn't work and I don't really know what else to try:
if (model.Categories !=null && model.Categories.Any(d => d.Enabled))
{
List<Listing> itemsSelected = null;
foreach (var category in model.Categories.Where(d => d.Enabled))
{
var itemsTemp = items.Select(x => x.Categories.Where(d => d.ID == category.ID));
foreach (var item1 in itemsTemp)
{
itemsSelected.Add((Listing)item1); //casting error here
}
}
items = itemsSelected;
}
In SQL, I would write this using a subquery (the subquery represents the multiple categories that can be searched for):
select l.id, cl.Category_ID
from
listings as l inner join CategoryListings as cl
on l.id=cl.Listing_ID
inner join Categories as c on c.ID = cl.Category_ID
where c.id in (select id from Categories where id =1 or id=3)
How do I write that SQL query in EF using navigators or lambda? The subquery in the SQL will change each search and can be any id or IDs.
You forgot to tell us what objects are in your collection items. I think they are Listings. Your case doesn't work, because itemsTemp is a collection of Categories, and every item1 is a Category, which of course can't be cast to a Listing.
Advice: to debug casting problems, replace the word var
with the type you actually expect. The compiler will warn you about
incorrect types. Also use proper identifiers in your lambda expressions.
This makes them easier to read
IQueryable<???> items = ... // collection of Listings?
List<Listing> itemsSelected = null;
IQueryable<Category> enabledCategories = model.Categories.Where(category => category.Enabled));
foreach (Category category in enabledCategories)
{
IEnumerable<Category> itemsTemp = items
.Select(item => item.Categories
.Where(tmpCategory => tmpCategory.ID == category.ID));
foreach (Category item1 in itemsTemp)
{
// can't cast a Category to a Listing
We'll come back to this code later.
If I look at your SQL it seems that you want the following:
I have a DbContext with (at least) Listings and Categories.
I want all Listings with their Categories that have category Id 1 or 3
It's good to see that you followed the entity framework code-first conventions, however you forgot to declare your collections virtual:
In entity framework the columns in a table are represented by
non-virtual properties. The virtual properties represent the relations
between the table.
With a slight change your many-to-many relation can be detected automatically by entity framework. Note the virtual before the ICollection
class Listing
{
public int ID { get; set; }
// every Listing has zero or more categories (many-to-many)
public virtual ICollection<Category> Categories { get; set; }
...
}
class Category
{
public int ID { get; set; }
// every Category is used by zero or more Listings (many-to-many)
public ICollection<Listing> Listings { get; set; }
...
public bool Enabled {get; set;}
}
And the DbContext
public MyDbContext : DbContext
{
public DbSet<Listing> Listings {get; set;}
public DbSet<Category> Categories {get; set;}
}
Although a relational database implements a many-to-many relationship with a junction table, you don't need to declare it in your DbContext. Entity framework detects that you want to design a many-to-many and creates the junction table for you.
But how can I perform my joins without access to the junction table?
Answer: Don't do joins, use the ICollections!
Entity Framework knows which inner joins are needed and will do the joins for you.
Back to your SQL code:
Give me all (or some) properties of all Listings that have at least one Category with Id equal to 1 or 3
var result = myDbcontext.Listings
.Select(listing => new
{ // select only the properties you plan to use
Id = listing.Id,
Name = listing.Name,
...
Categories = listing.Categories
// you don't want all categories, you only want categories with id 1 or 3
.Where(category => category.Id == 1 || category.Id == 3)
.Select(category => new
{
// again select only the properties you plan to use
Id = category.Id,
Enabled = category.Enabled,
...
})
.ToList(),
})
// this will also give you the Listings without such Categories,
// you only want Listings that have any Categories left
.Where(listing => listing.Categories.Any());
One of the slower parts of database queries is the transfer of the selected data from the DBMS to your local process. Hence it is wise to only transfer the properties you actually plan to use. For example, you won't need the foreign keys of one-to-many relationships, you know it equals the Id value of the one part in the one-to-many.
Back to your code
It seems to me, that your items are Listings. In that case your code wants all Listings that have at least one enabled Category
var result = myDbContext.Listings
.Where(listing => ...) // only if you don't want all listings
.Select(listing => new
{
Id = listing.Id,
Name = list.Name,
Categories = listing.Categories
.Where(category => category.Enabled) // keep only the enabled categories
.Select(category => new
{
Id = category.Id,
Name = category.Name,
...
})
.ToList(),
})
// this will give you also the Listings that have only disabled categories,
// so listings that have any categories left. If you don't want them:
.Where(listing => listing.Categories.Any());
Do you have a relation between Listing/Category and CategoryListings?
Here is example for EF 6: http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
If you have it the query will be simple, something like that:
CategoryListing.Where(cl => new List<int>{1, 3}.Contains(cl.CategoryRefId))
.Select(x => new {x.ListingRefId, x.CategoryRefId});
If you need all properties of Listing or Category, Include() extension will help.

Entity Framework - Get 'fake' navigation property within one query

I have a Product table that has no relation defined to the translation table. I added a Translation property to the Product POCO as [NotMapped].
**My Product POCO: **
public partial class Product
{
public int ProductID { get; set; }
public double Price { get; set; }
[NotMapped]
public virtual Translation Translation{ get; set; }
/** Other properties **/
}
I also have a Translation table, and like the name says, it contains all the translations.
Now, the right translation can be retrieved from the database by providing three parameters: LanguageID, TranslationOriginID and ValueID.
LanguageID: ID from the language that the user has defined.
TranslationOriginID: Simply said, 'What table contains the entity that I want the translation for?' In other words, this ID points to another table that contains all possible origins. An origin is a table/entity that can have a translation. E.g: The origin in this example is Product.
ValueID: This is the ID of the entity that I want a translation for.
My Translation POCO:
public partial class Translation
{
public int TranslationID { get; set; }
public byte LanguageID { get; set; }
public short TranslationOriginID { get; set; }
public int ValueID { get; set; }
public string TranslationValue { get; set; }
/** Other properties **/
public virtual TranslationOrigin TranslationOrigin { get; set; }
public virtual Language Language { get; set; }
}
When I want to retrieve all products with their Translation, I execute this code:
List<Product> products = context.Products.ToList();
foreach (Product product in products)
{
product.Translation = context.Translations.FirstOrDefault(y => y.LanguageID == 1 && y.TranslationOriginID == 2 && y.ValueID == product.ProductID);
}
Like you can see, I execute for every product in the list another query to get the translation.
My question:
Is it possible to get all the products and their translation in one query? Or even that I automatically retrieve the right translation when I select a product?
I already tried an .Include() and a .Select(). It didn't work, maybe I did something wrong?
I also tried this method, didn't work either.
Btw, I use Entity framework 5 with .NET 4 (so, Entity Framework 4.4).
Thanks in advance.
Greetings
Loetn
Answer
With the example given by Ed Chapel, I came up with a solution.
return (from p in context.Products
join t in context.Translations
on new
{
Id = p.ProductID,
langId = languageID,
tOriginId = translationOriginID
}
equals new
{
Id = d.ValueID,
langId = d.LanguageID,
tOriginId = d.TranslationOriginID
}
into other
from x in other.DefaultIfEmpty()
select new
{
Product = p,
Translation = x
})
.ToList().ConvertAll(x => new Product()
{
Code = x.Product.Code,
Translation = x.Translation,
/** Other properties **/
});
I don't like proper LINQ in most cases. However, join is one scenario where the LINQ is easy than the extensions methods:
from p in context.Products
join t in context.Translations
on t.ValueID equals p.ValueID
&& t.LanguageID == 1
&& t.TranslationOriginID == 2
into joinT
from x in joinT
select new {
Product = p,
Translation = t,
};
You then loop over the result setting x.Product.Translation = x.Translation.
First of all you should realize that your translations table is not structured like a dba would like it You have a non enforced relationship because depending on the OriginId your valueId references a different table.
Because of this you cannot use lazy loading or includes from EF.
My best idea at this point would to manually join the table on an anonymous type(to include your originId). Afterwards you can iterate over the results to set the translation property
The result would look like this :
var data = from p in context.Products
join pt in context.Translations on new{p.Id,2} equals new {pt.ValueId, pt.OriginId} into trans
select new {p, trans};
var result = data.ToList().Select( a =>
{
a.p.Translations = a.trans;
return a.p;
}).ToList();
With the example that Ed Chapel proposed as a solution, I came up with this.
return (from p in context.Products
join t in context.Translations
on new
{
Id = p.ProductID,
langId = languageID,
tOriginId = translationOriginID
}
equals new
{
Id = d.ValueID,
langId = d.LanguageID,
tOriginId = d.TranslationOriginID
}
into other
from x in other.DefaultIfEmpty()
select new
{
Product = p,
Translation = x
})
.ToList().ConvertAll(x => new Product()
{
Code = x.Product.Code,
Translation = x.Translation,
/** Other properties **/
});

EF and LINQ querying by subcollection properties

I'm trying to order a list of "parent" items based on a value in its sub-collection's sub-collection. Here's the specifics...
I have a Film entity (mapped to the Films table) that has a one-to-many collection of Release entities (mapped to the Releases table). Each Release has one or more ReleaseDate entities (mapped to the ReleaseDates table).
public class Film {
public int Id {get;set;}
public string Title {get;set;}
/* ... more properties here ...*/
}
public class Release {
public int Id {get;set;}
public int FilmId {get;set;}
public virtual Film Film {get;set;}
public virtual ICollection<ReleaseDate> ReleaseDates { get; set; }
/* ... more properties here ...*/
}
public class ReleaseDate {
public int Id {get;set;}
public DateTime Date {get;set;}
public int ReleaseId {get;set;}
public virtual Release Release {get;set;}
/* ... more properties here ...*/
}
Now, I want to order the Films by the earliest release date, but obviously a film could have no releases, and a release could have no release dates (again, 1-* relationships). The SQL equivalent would be...
SELECT * /* or whatever columns...*/
FROM dbo.Films F
LEFT OUTER JOIN dbo.Releases R ON R.FilmId = F.Id
LEFT OUTER JOIN dbo.ReleaseDates RD ON RD.ReleaseId = R.Id
ORDER BY RD.[Date] ASC /* or DESC */
How can I go about doing this?
var orderedFilms = Films.OrderBy(a=> a.Releases.Any() ?
a.Releases.Select(x=>x.ReleaseDates.Any() ?
x.ReleaseDates.Min(d=>d.Date).Date :
DateTime.Now).Min() : DateTime.Now);
Well, I changed my approach to this problem and resolved it based on the "normal" approaches out there, one of which was given as answer but subsequently deleted by the poster. What I ended up doing is moving my select down to the repository layer where I do have the DbContext and was able to do a "simple" left outer join style query like this...
var films = (from f in db.Films
join r in db.Releases on f.Id equals r.FilmId into FR
from a in FR.DefaultIfEmpty()
join d in db.ReleaseDates on a.Id equals d.ReleaseId into RRD
from b in RRD.DefaultIfEmpty()
orderby b.Date ascending
select f);
#KingKing, thanks for your answer, I think it may come in handy in some other places where we have these sort of aggregate fields based on properties of sub-collections or even properties of sub-sub-collections.

Entity Framework, select many to many

I am working with Entity Framework code first.
I have the following tables :
Companies : PK ID int, Name, ...
Customers : PK ID int, Name, ...
CustomersCompanies : CustomerID, CompanyID
I can create customers and companies without problems.
But I don't know how to get all the companies a customer has.
I tried that :
_customer = ...
var companies = from c in _db.Companies
where c.Customers.Contains(_customer)
select c;
But companies does not contains anything...
Try to compare by ID's of customers, like:
_customer = ...
var companies = from c in _db.Companies
where c.Customers.Where(x => x.CustomerID == c.CompanyID)
select c;
Or shorter:
var comapnies = _db.Companies.Select(x => x.CustomerID == c.CompanyID);
With properly created Entities you should be able to just call:
var companies = _customer.Companies;
you have to have ICollection<Company> within your Customer class, and ICollection<Customer> within Company class.
Check out this tutorial: Creating a Many To Many Mapping Using Code First.
If you are using code first you can just add a virtual collection of Companies to your Customer class, and a virtual collection of Customers to your Company class :
public class Customer
{
public int Id { get; set; }
public virtual ICollection<Company> Companies { get; set; }
}
public class Company
{
public int Id { get; set; }
public virtual ICollection<Customer> Customers { get; set; }
}
then to fetch customers and include their companies you can do :
var customers = _db.Customers.Include(x => x.Companies);
I'm not sure what your _db class looks like so I don't know if you just have your entity collections as properties on it. Typically I use the entity framework DbContext which has a GetDbSet method. So I would do something like :
var customers = _dbContext.GetDbSet<Customer>().Include(x => x.Companies);
Hope that helps!

NHibernate Criteria API with unmapped tables

I have a class with corresponding mapping as below:
public class Customer
{
public virtual int CustomerId { get; private set; }
//...
public virtual List<int> Orders { get; set; }
}
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.PatientId)
.GeneratedBy.Native();
HasMany(x => x.Orders)
.Element("OrderId", t => t.Type<int>())
.Table("CustomerOrder")
.KeyColumn("CustomerId")
.ForeignKeyConstraintName("FK_Customer_Order")
.Cascade.All();
}
}
Assume class Order is in another database, so I can't map it in this assembly. (I'm not sure this is the best way of doing this, please feel free to comment on the mapping too.)
So I would like to be able to find Customers with more than N orders, SQL query would look like this:
select * from Customer c where
(select count(*) from orders where CutsomerId = c.CustomerId) > N
What would be Criteria API equivalent?
As another option could you not just add an OrderCount property to your Customer class so you don't need the join to the other DB.
Anything you do which joins cross DB or joins to unmapped classes feels a bit wrong.

Categories