We have a 3 table relationship in an MVC application that is using the EF6. For example a Customer Table related to an Orders Table(the many side) and the Orders Table is related to an OrderItems Table (the many side).
We would like in ONE TRIP to the database get all the records for all tables and be able to order each table.
We know we can use the include extension like context.Customers.Include("Orders.OrderItems") to eagerly load all data. But we keep getting errors when we try to order each table.
For example we would like to order the Customer Table by CustomerId column, and the Orders Table by Date column and the OrderItems by ProductId column.
Any assistance would be apreciated
If you have navigation properties from OrderItem to Order and from Order to Customer, then you can get ordered `OrderItems' you can achieve this like:
var orderItems = context.OrderItems
// Filter if needed with .When
.Include(m => m.Order.Customer)
.OrderBy(m => m.Order.Customer.CustomerId)
.ThenBy(m => m.Order.Date)
.ThenBy(m => m.ProducetId)
.ToList();
But, if you want to get customers, what you can do is load ordered customers like:
var customers = context.Custoemr
.Include(m => m.Orders.Select(o => o.OrderItem))
.OrderBy(m => m.CustomerId)
.ToList();
Then order Order and OrderItem. As you have loaded all data, ordering collections will not make any database call, and it will remain one-trip to database:
foreach(Customer customer in customers)
{
customer.Orders = customer.Orders.OrderBy(m => m.Date);
foreach (Order order in customer.Orders)
{
order.OrderItems = order.OrderItems.OrderBy(m => m.ProducetId);
}
}
Related
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
};
I have many-to-many rel between Products and Category. I use ef6 to mapping them
when I query to filter by request.categoryId I get these problems:
with var query = from product in _context.Products select product ;
-> I cannot get category of each product (product.categories==null)
With var query = from product in _context.Products select new {product, product.Categories };
->I cannot filter by categoryId, I want to use something like:
query = query.Where(p => p.Categories.ForEach(category=>category.Id == request.categoryId) );}
or
query = query.Where(p => p.Categories.Find(category=>category.Id==request.categoryId) )
-> The result : Anonymous type :'a{product,product.Categories}
how to product.Categories auto assign to List<Category> Categories in Product entity
I don't know how to describe my problem, help me!!
Use first query, but just add Include
var query =
from product in _context.Products.Include(x => x.Categories)
select product;
Since it is too verbose to use Query Comperhention in such case, you can try Method Chain syntax.
var query = _context.Products
.Include(x => x.Categories)
.AsQueryable(); // just for changing type to IQueryable
I created new customer attribute 'personal_number' and now want to show it in new column in adminhtml sales_order_grid.
I've done all stuff to show column in grid (class rewrite in config.xml), created MyName_MyModule_Block_Adminhtml_Order_Grid where need to rewrite _getCollectionClass() and _prepareColumns(). My problem is in _getCollectionClass() where I need to do database query to join customers attribute data to orders collection. Becouse I'am very new in Magento, logic of magento-way queries for me is very hard to follow. Can someone help me to write MySql query below in Magento-way to get value of my customer attribute 'personal_number' in orders grid:
SELECT Orders.*, Customers.customer_id, Custumer.personal_namber FROM Orders INNER JOIN Customers ON Orders.customer_id = Customer.customer_id
You usually don't need to change _getCollectionClass, but rather do the join on the grid, on _prepareCollection. Ie:
protected function _prepareCollection()
{
$collection = Mage::getResourceModel($this->_getCollectionClass());
//we changed mysql query, we added inner join to order item table
$collection->join(
// Alias => Table name
array('customers' => "customer/customer"),
// Join condition
'main_table.customer_id = customer.customer_id',
// Fields to select
array('personal_number'=>'personal_number');
$this->setCollection($collection);
return parent::_prepareCollection();
}
Taken from here, have a look at the full article for more help: http://inchoo.net/magento/how-to-extend-magento-order-grid/
In in a website's database I am working on, I have two tables for example: Country & CountryLocale. The two tables contain the following columns:
Country: Id, Longitude, Latitude
CountryLocale: Id, CountryId, CountryName, CultureId
What I want is:
- When I retrieve a Country, I want the Entity to contain: CountryId, CountryName, CultureId, Latitude & Longitude.
- When I create a new Country, to insert one record into Table Country and one to Table CountryLocale.
- When I create a new Country Locale, to create a record inside CountryLocale only
etc ...
Is this attainable?
Thanks
You can use entity splitting to partially achieve that. CountryLocale would use the PK of Country as its PK. You can not insert to CountryLocale without a Country. Essentially its a single entity split into multiple tables.
modelBuilder.Entity<Country>()
.Map(mc =>
{
mc.Properties(n => new
{
n.Id,
n.Longitude,
n.Latitude
});
mc.ToTable("Country");
})
.Map(mc =>
{
mc.Properties(n => new
{
n.CountryName,
n.CultureId
});
mc.ToTable("CountryLocale");
});
use discriminator (Table per Hierarchy)
modelBuilder.Entity<CountryBase>()
.Map<Country>(c => c.Requires("Type").HasValue((int)CountryType.Country))
.Map<CountryLocale>(c => c.Requires("Type").HasValue((int)CountryType.CountryLocale));
and you can define your sub-entities as you like
How to retrieve the records from more than one table which has one to many relationship.
Categories[table]
CategoryId
CategoryName
Products[table]
ProductId
CategoryId
ProductName
Description
Entites
Category[Entity]
CategoryId
CategoryName
List<Product>
Product[Entity]
ProductId
ProductName
Description
So if i give categoryId, i should get the category details with list of products associated with the category.
How to do this in linq to sql?
In linq to sql you get a reference property generated in each of your entities. This said if you do this:
Category cat = context.Categories.FirstOrDefault(x=>x.CategoryId == 1); //Where one is the //id of a random category
foreach(Product prd in cat.Products)
{
//do some logic here
}
you will get all the products.
See Include for LINQ to SQL