Entity Framework: Get Model with Linked Models in Many to Many Relationship - linq

I'm coming from TSQL + C# land and have been trying to adapt to linq and EF. Many-to-many relationships have been tripping me up. I have models with many-to-many relationships that I want to query from a database. Such as:
class Product{
public int ID {get;set;}
public string ProductName {get;set;}
public virtual ICollection<Tag> Tags {get;set;}
}
class Tag {
public int ID {get;set;}
public string TagName {get;set;}
public virtual ICollection<Product> Products {get;set;}
}
I'm able to get a product itself out of the DbContext, and then later fetch it's associated Tags like this:
// product exists in memory as a Product with an empty product.Tags
var query = from p in db.Product
from t in db.Tags
where p.ID == product.ID
select p.Tags;
Then I can assign the product.Tags with the fetched Tags. Obviously, this is very inefficient when dealing with multiple products if I have to query for every product.
With linq and EF, I want to be able to get a Product with all of its associated Tags in one round trip to the database. Also, I want to be able to get all Products and their associated Tags (or a filtered list of Products). How do would the linq look?
Edit:
Ok, after some more fiddling around, I've got this:
var query = db.Product.Include("Tags")
.Where(p => p.Tags.Any(t => t.Products.Select(m => m.ID).Contains(p.ID)));
This is almost what I need. The results are all products with tags. Missing are the products that don't have tags. I think of this as the equivalent of a SQL inner join. I want to left outer join the tags to the product, and return all products with tags optional. How to get all products with their associated tags without excluding products that have no tags?
Edit:
This was easier than I thought.
var query2 = db.Product.Include("Tags").DefaultIfEmpty();
This gets all the products and their respective tags, including products without tags. Hopefully it works for the right reasons...

The purpose of using an object-relational mapper like EF is that it maps relationships for you. If you are manually joining objects that have foreign keys in the database, you are doing it wrong.
See my question Why use LINQ Join on a simple one-many relationship?
The correct answer is simply context.Products.Include("Tags"), which will auto-magically join Products and Tags for you. This is literally the biggest (only?) benefit of using an ORM.

Related

Returning a list inside Linq query from left outer join

I'm new with Linq and hoping for some clarity on a particular query.
I have two tables (simplified for demonstration):
Table: Customer
CustomerId | Name
1 | John Smith
2 | Peter James
Table: Order
id | CustomerId | Total
1 | 1 | $100
2 | 1 | $200
Sample CustomerDto:
public class CustomerDto
{
public long CustomerId { get; set; }
public string Name{ get; set; }
public CustomerOrder[] CustomerOrderList{ get;set;}
}
Linq example for left outer join in the select here, they return string.empty if the join fails, I'm not sure the equivalent when I'm returning an object rather than a string.
My Linq query looks as follows. I've used the DefaultIfEmpty() to assist in a left outer join, however given I'm dealing with my object, I'm not sure how to return null if there isn't anything.
IQueryable<CustomerDto> search =
from customer in _database.Customer
join customerOrder in _database.CustomerOrder on customer.CustomerId equals customerOrder.CustomerId into CS
from subCustomerSale in CS.DefaultIfEmpty()
select new CustomerDto
{
CustomerId = customer.CustomerId,
Name = customer.Name,
CustomerOrderList = subCustomerSale
};
As I mentioned, I want to return a list of orders rather than one row per order. So there should be two records returned (the two customers), one with a list of orders and the other without any.
How do I achieve this?
The first step to make the entities easier to work with is to ensure that navigation properties are set up. If the table is called "Order" then the entity can be Order, or renamed to CustomerOrder if you like. A Customer entity can have an ICollection<Order> collection which EF can automatically map provided a consistent naming convention and normalization is used. (Otherwise explicit mapping can be provided)
For example:
public class Customer
{
[Key]
public int CustomerId { get; set; }
// other customer fields.
public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
}
From here you are projecting Customers to a CustomerDTO, so we should also project the Orders to an OrderDTO. Note that when using navigation properties we don't have to explicitly join entities. We don't even have to eager load related data via Include(). The later would apply if/when we want to work with the entities rather than projections.
The resulting query would end up looking like:
IQueryable<CustomerDto> search = _database.Customer
.Select(c => new CustomerDto
{
CustomerId = c.CustomerId,
Name = c.Name,
Orders = c.Orders.Select(o => new OrderDto
{
OrderId = o.OrderId,
Total = o.Total
}).ToList()
});
The benefit is no need to explicitly write Join expressions. EF can help simplify accessing related data considerably rather than just facilitating using Linq as an alternative to SQL. This would return an empty list rather than #null if there are no Orders for that customer. It may be possible to substitute a #null if there aren't any orders, though worst case it could be post-processed after the results are materialized Ie:
var customers = await search.ToListAsync();
var noOrderCustomers = customers.Where(c => !c.Orders.Any()).ToList();
foreach(var customer in noOrderCustomers)
customer.Orders = null;
It really just boils down to whether the consumer is Ok knowing there is always an Orders collection that will be empty if there are no orders, or in the Orders collection is only present if there are orders. (via JSON etc. serialization)
The important details to consider: When filtering, such as filling in search criteria, do this before the Select as the IQueryable is working with entities so you have full access to the table fields. Adding Where clauses after the Select will limit the available fields to the ones you have selected for the DTO. (They will still bubble down into the SQL) There is a ToList inside the Select to build the Orders collection. This may look "bad" that it might be materializing data synchronously, but it will be executed only when the main query is. (Such as an awaited async operation on the IQueryable)
When projecting to DTOs be sure not to mix DTOs and entities such as:
IQueryable<CustomerDto> search = _database.Customer
.Select(c => new CustomerDto
{
CustomerId = c.CustomerId,
Name = c.Name,
Orders = c.Orders
});
... which can be tempting. The issue here is that "c.Orders" would return a collection of Order entities. Those Orders may have references to other entities or information you don't need to/want to expose to the consumer. Accessing references could result in lazy load costs, null references, or exceptions (i.e. disposed DbContext) depending on when/where they occur.
just put the ternary condition to achieve it like follows:
IQueryable<CustomerDto> search =
from customer in _database.Customer
join customerOrder in _database.CustomerOrder on customer.CustomerId equals customerOrder.CustomerId into CS
from subCustomerSale in CS.DefaultIfEmpty()
select new CustomerDto
{
CustomerId = customer.CustomerId,
Name = customer.Name,
CustomerOrderList = subCustomerSale == null ? null : subCustomerSale // add this line and you will get the null as well if there is no record
};

How to get products of a "main category" if a product is related to only one "subcategory"?

How do I get all products of a “main category” if a product is related to only one “subcategory”? A product is only related to a sub-category, while a sub-category is always part of the main category. So I want to have all products in the main category. A query like the below would not work or return no products because no product is related to category #1.
Categories::where(['id' => 1])->products();
Models/Category.php
public function parent(): BelongsTo
{
return $this->belongsTo(Category::class, 'parent_id');
}
public function children(): HasMany
{
return $this->hasMany(Category::class, 'parent_id');
}
Models/Product.php
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
What do I need to do/change to get all products of the main category (preferably without checking if the category with ID #1 is the main category)?
One common solution to this problem of querying a hierarchical data structure is a Closure Table. There are many discussions of this pattern online, so I won't attempt to restate fully, but the short summary is that you store every path between each object and all of its ancestors along with the depth between them. This gives you a table with columns (ancestor_id, descendant_id, depth), so you can join through that table to collect all objects linked to any of a given descendant's ancestors, or any ancestor's descendants.
Here is an example query for how this works in practice to query all descendants of a given ancestor category, possibly with some syntax issues because I don't have a real database to run this against.
SELECT products.* FROM products
INNER JOIN category_closure ON products.category_id = category_closure.descendant_id
WHERE category_closure.ancestor_id = 1;
We currently use this solution for virtually the exact same problem, products assigned anywhere within a hierarchy of categories. However, we are doing this in a Doctrine project, so implementation details of our solution likely wouldn't help here.
There are existing libraries to make this easier in Laravel, such as https://github.com/franzose/ClosureTable (I can't vouch for quality, just found now in a search).
You can use hasManyThrough relation. You check from this link
Add this code to your Category model:
public function products(){
return $this->hasManyThrough(
Product::class,
Category::class,
"parent_id",
"category_id",
"id",
"id"
);
}
Maybe, I am wrong with your foreign and primary keys. Please check it.

Saving complex view model using Entity Framework 4.3

I have Customer, Order and OrderItem tables. OrderItem.OrderID points to Order.ID; and Order.CustomerID points to Customer.ID i.e. the common Customer -> Order -> OrderItem setup.
And I have a view model – Customer which contains Order objects and then OrderItem objects as well.
If the user creates a new Customer, new Order and new OrderItems on a view, which are then bound to the Customer view model object (containing all Customer, Order, OrderItem data); is there a way to save this Customer view model using EF?
My confusion comes from the fact that, since Customer, Order, OrderItem(s) are all new records; which means the Customer.ID (auto-incremented number) has not been generated yet (record not saved yet); so how does EF know what ID to use when saving Order.CustomerID?
Do I need to save Cusomer first, get the Customer.ID, then save Order, then get Order.ID and then save OrderItem(s)?
Thanks.
You just call this:
context.Customers.Add(customer);
context.SaveChanges();
and if everything is correctly configured in your mapping EF will understand relations and correctly save customer first, retrieve its Id and use it for saving related orders. It will handle order items in the same way.
As long as you establish the objects relationships before saving it should work
customer.Orders = new List<Orders>();
customer.Orders.Add(order);
order.OrderItems = new List<OrderItems>();
order.OrderItems.Add(orderItem);
context.Customer.Add(customer);
context.SaveChanges();

MVC 3 / EF 4.2 - Editing against ViewModel, do I save against Model or ViewModel?

My first MVC3 EF 4.2 site and I'm confused on some things, currently on ViewModels when querying and saving. Please correct me if I explain this poorly, i'm not sure how to term this. The .edmx automatically created the table classes but I read it was better to create a ViewModel, considering I need to join tables to display/edit my Product completely. The controller code below is where I join tables to output a Product to edit, and then save. My question - what is the right way to save the Product, to the Product.cs model generated by DbContext or my own ProductViewModel.cs?
Is there an easier method to query a product and join the tables and then map to the viewmodels parameters, or do I keep doing all this in the controller like below?
I also want to save/update the product each time someone views/clicks on the product, so I wasn't sure if I create a separate ViewModel for updating just that parameter or again, use the Product model.
Hope that makes sense! I can explain further if needed.
private SiteForgeEntities db = new SiteForgeEntities();
public ActionResult Edit(int id)
{
var viewModel = (
from a in db.Products
join b in db.Sites
on a.SiteId equals b.SiteId
join c in db.Sections
on a.SectionId equals c.SectionId
join d in db.Affiliates
on a.AffiliateId equals d.AffiliateId
select new ProductViewModel()
{
ProductId = a.ProductId,
Product = a.Product,
Description = a.Description,
Image = a.Image,
Price = a.Price,
Clicks = a.Clicks,
Link = a.Link,
Site = b.Site,
Section = c.Section,
Affiliate = d.Affiliate
}).Single(x => x.ProductId == id);
return View(viewModel);
}
[HttpPost]
public ActionResult Edit(Product product)
{
...update database...do I pass in and save back to Product or my ProductViewModel
}
You use ViewModel to pass multiple models to the view, but when you save data, you need to save it to the appropriate model. If you are adding or modifying products, you will add items to products (using your DbContext). If you have one-to-many relationship defined between two models (in your Product.cs model you might have a property declared as:
public virtual ICollection<SomeOtherModel> SomeOtherData { get; set; }
you can use this to build a table instead of passing everything in a ViewModel. There is a nice tutorial here regarding the CRUD operations using EF4. Have a look at these short tutorials that can give you an idea about your strategy http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc.

ASP.Net MVC View returned, but how can I show additional information?

I have a SQL database with has the following: Customer, Item, Clothing and Food.
Item holds a key to Clothing or Food.
Item also holds a key to Customer. Therefore a customer can have an item, which may be of food or clothing.
I am using ADO.Net Entity Framework and have this generated automatically.
I currently have the following set-up: A person may enter their ID on the webpage and this is sent via a form post where the controller picks it up and queries the database using LINQ to get the customer. The customer view (details) is then returned. I can now see all the customer details etc.
However, what I want is to be able to see the items the customer has, the different food items and clothing items, but I am unsure how to do this. I also want to be able to allow the user to edit one field of the clothes and food items tables. Any idea how I would implement this?
Here is an ActionResult in my CustomerController:
public ActionResult Details(int id)
{
var cust = (from c in dataModel.Customers
where (c.MembershipID == id)
select c).First();
return View(cust);
}
I can also write cust.Items which is the entity which I want to display in the view with the customer (their items). How would I display this in the view also?
Hopefully this makes it a little more clear on what I am trying to achieve and how.
Thanks.
Using Entity Framework, if you're tables are linked properly with the right foreign keys and all that then your Customer entity should have a property that is a collection of Items.
You could also create your own strongly typed ViewModel that has a field for Customer and implement your own properties for Clothing and Food and populate those with another query.
This question was asked last night but its similar. The guy in the question wanted information to populate a dropdown passed in. You want something similar, not for a dropdown, but to fill in textboxes to edit. How to properly populate drop downs from ViewData in controller on multiple views in ASP.NET MVC
To create a ViewModel start by creating a new class and name it CustomerAndItemsViewModel, for example.
public class CustomerAndItemsViewModel
{
public Customer Customer { get; set; }
public IQueryable<Items> Items { get; set; }
}
public ActionResult Details(int id)
{
var cust = (from c in dataModel.Customers
where (c.MembershipID == id)
select c).First();
var items = (from i in dataModel.Items
where (i.MembershipID == cust.MembershipID)
select i;
return View(new CustomerAndItemsViewModel { Customer = cust, Items = items });
}
And don't forget that you will no longer be passing a Customer to your view. So you need to change the line at the top to something like:
#model Your.Path.To.CustomerAndItemsViewModel
Typically, if you want to pass back information that is not contained in just one of your entities, you have to create a class that encompasses more than one object. So, if you want a page that displays your customer information, and all their items (which they can then edit), you would need to have a the controller action pass back a "CustomerAndItems" object (or something similarly named). This object would hold a reference to the Customer as well as a collection of their Items. (You build the CustomerAndItems object within your Action.)
Then, your view would be strongly typed to CustomerAndItems, and you can then display each piece of information as you normally would.

Resources