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!
Related
I have two entities, one is Product and looks something like this:
[Key]
public int ID {get; set;}
public virtual ICollection<Category> Categories { get; set; }
and the other is Category and looks like this:
[Key]
public int ID {get; set;}
[JsonIgnore]
public virtual ICollection<Product> Products { get; set; }
Both objects are simplified massively here.
My insert method looks like this:
public static Product AddProduct(ProductDTO product)
{
using var context = new ProjectDbContext();
Product newProduct = Product.ConvertDTO(product);
var contr = context.Products;
contr.Add(newProduct);
context.SaveChanges();
if (product.Categories != null && product.Categories.Count() > 0)
{
var list = from r in context.categories
where product.Categories.Contains(r.ID)
select r;
newProduct.Categories = list.ToList();
}
contr.Update(newProduct);
context.SaveChanges();
return newProduct;
}
The ProductDTO is just an object that has the product data and a list of category ids.
The product is inserted and the data is also written into the generated connection table inside the database. However when I now try to get the inserted product, its categories are null, even though it should have three category objects.
Ok, I think I found a solution. I forgot to eager load, when getting the entity:
contr = context.Products.Include(x => x.Categories).Where(x=> x.ID == id).FirstOrDefault();
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);
I have a problem with navigation properties and inheritance.
This is my problem:
I have a base Person class and classes User and Worker which inherit from Person. On the DB level I'm using single table inheritance or table per hierarchy (TPH) inheritance. So there a single table with a discriminator column.
Both User and Worker need to have a Company relation, so I would like to define it on the Person class.
I define my model like this:
[Table("mydb.person")]
public abstract partial class Person
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long ID { get; set; }
public long? CompanyID { get; set; }
[ForeignKey("CompanyID")]
public virtual Company Company { get; set; }
...
}
public partial class User : Person
{
...
}
public partial class Worker : Person
{
....
}
[Table("mydb.company")]
public partial class Company
{
public Company()
{
this.People = new HashSet<Person>();
this.Users = new HashSet<User>();
this.Workers = new HashSet<Worker>();
}
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long ID { get; set; }
public virtual ICollection<Person> People { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<Worker> Workers { get; set; }
...
}
Now, when I try to do a query to get the user and related company, for example:
dbSet.Where(u => u.Username == username).Include(x => x.Company).FirstOrDefault();
The query fails with this exception:
Unknown column 'Extent1.Company_ID' in 'field list
If I examine the result SQL it looks something like this:
SELECT
1 AS `C1`,
#gp2 AS `C2`,
`Extent1`.`ID`,
`Extent1`.`CompanyID`,
`Extent1`.`Username`,
...
`Extent1`.`Company_ID`
FROM `person` AS `Extent1`
WHERE `Extent1`.`Discriminator` = #gp1
It includes the extra Company_ID column, which doesn't exist.
I tried a few thing, nothing worked out:
renaming the column from CompanyID to Company_ID -> it generates a Column_ID1 in SQL and throws the same exception
removing the Users and Workers relations from Company -> it throws an exception saying it doesn't know how to map User and Company entities:
Unable to determine the principal end of an association between the
types 'Models.User' and 'Models.Company'. The principal end of this
association must be explicitly configured using either the
relationship fluent API or data annotations.
If I remove all 3 navigation properties from Company it throws the same mapping exception as above
I'm out of "clean" ideas at the moment. The only thing that could work is to do some dirty hack, define all the relations on child classes, and do separate queries and merging in the base class if both users and workers are required.
Do you have any suggestions?
Remove the Users and Workers collection properties.
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<Worker> Workers { get; set; }
As your Company navigation property is defined on Person the associated back navigation property has to be an ICollection of Person.
The People collection will contain all the associated workers and users. The two extra properties Users and Workers are interpreted as completely new relationships and because you do not have corresponding properties and foreign keys on User or Worker EF generates it virtually.
Answer to the comment. Just for the sake of formatting as a second answer ;-)
With eager loading if you start with the Company
var companies = db.Company.Include(p => p.People);
It will always get the Users and the Workers.
If you use eager loading starting at the people.
var users = db.People.OfType<User>().Include(p => p.Company).ToList();
var companies = users.Select(p => p.Company).Distinct().ToList();
the People navigation property of your companies has just the Users.
Also you could execute two separate statements and the fixup of the database context will automatically fill the Navigation properties.
var company = db.Company.Where(p => p.ID > 100).ToList();
var copanyUsers = db.Company.Where(p => p.ID > 100)
.SelectMany(p => p.People).OfType<User>().ToList();
Assuming I have Customer and Order objects, where one Customer can have many Orders (so the Order class has a CustomerId property), and I want to return a collection of all CustomerAndMostRecentOrder objects which are defined as follows:
public class CustomerAndMostRecentOrder
{
public Customer Customer { get; set; }
public Order MostRecentOrder { get; set; }
}
How would I write a Linq query which does this (I'm using Linq to SQL)?
You can use the following query:
from c in customers
select new CustomerAndMostRecentOrder
{
Customer = c,
MostRecentOrder = c.Orders.OrderByDescending(o => o.PurchaseDate).FirstOrDefault()
};
This will use a navigation property from customer to order. The MostRecentOrder is taken by ordering the Orders on some DateTime property and then loading the first one.
You will need to have an CreatedDate date in your Order table to get the most recent order. Then to get your CustomerAndMostRecentOrder object, do the following query:
from c in customers
join o in orders on c.ID equals o.CustomerID into co
select new CustomerAndMostRecentOrder
{
Customer = c,
MostRecentOrder = co.OrderByDescending(o => o.CreatedDate).FirstOrDefault()
}
public class CustomerAndMostRecentOrder
{
public CustomerAndMostRecentOrder(Customer customer, Order mostRecentOrder)
{
Customer = customer;
MostRecentOrder = mostRecentOrder;
}
public Customer Customer { get; set; }
public Order MostRecentOrder { get; set; }
}
public class Order
{
}
public class Customer
{
public IEnumerable<Order> GetOrders()
{
}
}
public static class UsageClass
{
public static void Sample(IEnumerable<Customer> allCustomers)
{
IEnumerable<CustomerAndMostRecentOrder> customerAndMostRecentOrders =
allCustomers.Select(customer => new CustomerAndMostRecentOrder(customer, customer.GetOrders().Last()));
}
}
As another alternative, you may want to take a look into the DataLoadOptions.AssociateWith discussed at http://msdn.microsoft.com/en-us/library/system.data.linq.dataloadoptions.associatewith.aspx. Simply set your requirements on the context and you don't need to worry about filtering the children at the query level.
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.