I'm trying to associate two entity types in EF4, there is no foreign key available.
Is there any manual tweaks I can do to so when I load an instance of order I get something like:
order.orderID = 455
order.date = 2012-08-12
order.OrderItems = <List>OrderItem // OrderItem being the other entity I want to map on order
I guess I'll have to do this manually when I Select() an order and set it's OrderItems property since no FKs are avail.
Am I on the right track ?
**EDIT:
I was able to create an association between orders and orderitems and here's a Select() method I have for an order:
public Order Select(long orderID)
{
using (var ctx = new BillingSystemEntities())
{
var res = from n in ctx.Orders
where n.OrderID == orderID
select n;
if (res.Count() == 0)
return null;
else
return res.First();
}
}
According to SQL Profiler, EF is not doing any JOIN on OrderItem table. I guess I need to load them into the OrderItems navigation property myself ?
You just need to include the OrderItems in your query:
public Order Select(long orderID)
{
using (var ctx = new BillingSystemEntities())
{
var res = from n in ctx.Orders.Include("OrderItems")
where n.OrderID == orderID
select n;
return res.FirstOrDefault();
}
}
Also FirstOrDefault is more appropriate here than your Count()...First() construct because it hits the database only once while Count() and First() will issue a separate query each.
You can write it even more compact with:
public Order Select(long orderID)
{
using (var ctx = new BillingSystemEntities())
{
return ctx.Orders.Include("OrderItems")
.FirstOrDefault(n => n.OrderID == orderID);
}
}
Related
I have a DbContext that has a table Gizmos
public Gizmo
{
public Guid Id {get;set;}
public ICollection<Widget> Widgets {get;set;} = new List<Widget>();
}
So a Gizmo can have a list of Widgets.
I want to delete 1 widget from the Gizmo.
I cannot directly access Widget from the context. It has to be done through the Gizmo.
I want to do something similar to:
_context.Gizmos.SelectMany(x => x.Widgets).Remove(x => x.Id == myId);
_context.SaveChanges();
You can't use Linq as what you want because queries don't allow to manipulate original source since they are collection results containing filtered and transformed references.
You can simply achieve your goal without Linq
foreach ( var gizmo in _context.Gizmos )
foreach (var widget in gizmo.Widgets.ToList() )
if ( widget.Id == myId )
gizmo.Widgets.Remove(widget);
The ToList() is to avoid index conflict when removing.
Fastest alternative
for ( int indexGizmo = 0; indexGizmo < _context.Gizmos.Count(); indexGizmo++ )
{
var gizmo = _context.Gizmos[indexGizmo];
for ( int indexWidget = gizmo.Widgets.Count() - 1; indexWidget >= 0; indexWidget-- )
{
var widget = gizmo.Widgets[indexWidget];
if ( widget.Id == myId )
gizmo.Widgets.Remove(widget);
}
}
Using Linq and a loop on the query to remove items
var query = from gizmo in _context.Gizmos
from widget in gizmo.Widgets
where widget.Id == myId
select new { gizmo, widget };
foreach ( var result in query )
result.gizmo.Widgets.Remove(result.widget);
You cannot do a Query (the Select) and a NonQuery( the Remove) at the same time. You first have to download it, and manifest it in memory, this is what "ToList()" does. Than you can Remove it. the Where-Clause you can append to the Select, so you download only the items you want to remove.
var list = _context.Gizmos.SelectMany(x => x.Widgets).Where(x => x.Id == myId).ToList();
_context.Widgets.RemoveRange(list);
_context.SaveChanges();
enter code here
But usually, if you have an ID of widget, you go to the DbSet of Widget and delete it there.
You don't need to take care of deleting it from Gizmos. EF and SQL will take care of that.
If your ID is unique and you expect only one Item, you can also just use
SingleOrDefault(x => x.Id == myId)
instead of Where and ToList()
And if ID is the primary key, you can just use
var widget = context.Widgets.Find(myID);
context.Widgets.Remove(widget);
context.SaveChanges();
As further optimization you can read about "Delete without Fetch" to get around the requirement to download first, before you can delete something.
That would be something like
var widget = new Widget { ID = myID };
context.Widgets.Attach(widget);
context.Entry(width).State = EntityState.Deleted;
context.SaveChanges();
Can I include a related object without a query?
With query:
Item item = _db.Items
.Include(i=>i.Supplier)
.Where(....)
Without query:
var item = new Item { Name = "Test", SupplierId = 1 };
item.Include(i => i.Supplier); //something like that...
I don't really understand your question...
First of all Where return multiple objects
IQueryable<Item> items = _db.Items
.Include(i=>i.Supplier)
.Where(....)
Then the result is an IQueryable, the items and suppliers objects are not materialized for the moment. You have to use ToList() for example to enable materialization and query the database.
For the Include method, it's just a join to query Items and Suppliers relationship.
But the Include extension method is only available in IQueryable and not for the entity.
Supplier is normal a simple navigation property on item entity
class Item
{
public virtual Supplier Supplier {get; set;}
}
so you can access using
var item = new Item { Name = "Test", SupplierId = 1 };
item.Supplier = ....
if you want establish a relation with you have to get the supplier
item.Supplier = _db.Suppliers.First(s => s.SupplierId = 1);
In the DB, I have a two tables with a one-to-many relationship:
orders suborders
----------- -----------
id id
name order_id
name
I'd like to query these tables and end up with a list of order objects, each of which contains a list (or empty list) of suborder objects. I'd also like to do this in a single DB query so it performs well.
In traditional SQL query land, I'd do something like (forgive the pseudocode):
rs = "select o.id, o.name, so.id, so.name from orders o left join suborders so on o.id = so.order_id order by o.id"
orders = new List<Order>
order = null
foreach (row in rs) {
if (order == null || row.get(o.id) != order.id) {
order = new Order(row.get(o.id), row.get(o.name), new List<Suborders>)
orders.add(order)
}
if (row.get(so.id) != null) {
order.suborders.add(new Suborder(row.get(so.id) row.get(so.name))
}
}
Is there a way to get this same resulting object structure using LINQ-to-Entities? Note that I want to get new objects out of the query, not the Entity Framework generated objects.
The following gets me close, but throws an exception: "LINQ to Entities does not recognize the method..."
var orders =
(from o in Context.orders
join so in Context.suborders on o.id equals so.order_id into gj
select new Order
{
id = o.id,
name = o.name,
suborders = (from so in gj select new Suborder
{
so.id,
so.name
}).ToList()
}).ToList();
The solution ends up being pretty simple. The key is to use a group join to get SQL to do the left join to suborders, and add a second ToList() call to force the query to be run so you're not trying to do object creation on the SQL server.
orders = Context.orders
.GroupJoin(
Context.suborders,
o => o.id,
so => so.order_id,
(o, so) => new { order = o, suborders = so })
.ToList()
.Select(r => new Order
{
id = r.order.id,
name = r.order.name,
suborders = r.suborders.Select(so => new Suborder
{
id = so.id,
name = so.name
}.ToList()
}).ToList();
This code only makes a single query to SQL for all objects and their child objects. It also lets you transform the EF objects into whatever you need.
I Always create a virtualized Property for Relations
so just extend (add a property) to your order class :
public class Order{
...
List<Suborder> _suborders;
public List<Suborder> Suborders{
get {
return _suborders ?? (_suborders = MyContext.Suborders.Where(X=>X.order_id==this.id).ToList());
}
...
}
so data will be fetched (pulled) only when you call the getters
How about this code ?
You can get a local cache.
List<Orders> orders = new List<Orders>();
private void UpdateCache(List<int> idList)
{
using (var db = new Test(Settings.Default.testConnectionString))
{
DataLoadOptions opt = new DataLoadOptions();
opt.LoadWith<Orders>(x => x.Suborders);
db.LoadOptions = opt;
orders = db.Orders.Where(x => idList.Contains(x.Id)).ToList();
}
}
private void DumpOrders()
{
foreach (var order in orders)
{
Console.WriteLine("*** order");
Console.WriteLine("id:{0},name:{1}", order.Id, order.Name);
if (order.Suborders.Any())
{
Console.WriteLine("****** sub order");
foreach (var suborder in order.Suborders)
{
Console.WriteLine("\torder id:{0},id{1},name:{2}", suborder.Order_id, suborder.Id, suborder.Name);
}
}
}
}
private void button1_Click(object sender, EventArgs e)
{
UpdateCache(new List<int> { 0, 1, 2 });
DumpOrders();
}
Output example below
*** order
id:0,name:A
****** sub order
order id:0,id0,name:Item001
order id:0,id1,name:Item002
order id:0,id2,name:Item003
*** order
id:1,name:B
****** sub order
order id:1,id0,name:Item003
*** order
id:2,name:C
****** sub order
order id:2,id0,name:Item004
order id:2,id1,name:Item005
Maybe I'm going about this the wrong way...
I have an Order table and an OrderItem table. I create a new Order using linq2sql generated classes.
I then attempt to get all orderable items out of my database using a query that goes after various tables.
I try to then create a new list of OrderItem from that query, but it squawks that I can't explicitly create the object.
Explicit construction of entity type OrderItem in query is not allowed.
Here is the query:
return (from im in dc.MasterItems
join c in dc.Categories
on im.CATEGORY equals c.CATEGORY1
select new OrderItem()
{
OrderItemId = im.ItemId
});
The idea is to populate the database with all orderable items when a new order is created, and then display them in a grid for updates. I'm taking the results of that query and attempting to use AddRange on Order.OrderItems
Is there a proper strategy for accomplishing this using linq2sql?
Thanks in advance for your help.
From my understanding of L2S, I don't think you can use explicit construction (in other words new SomeObj() { ... }) in a query because you aren't enumerating the results yet. In other words, the query has just been built, so how are you supposed to do this:
SELECT new OrderItem() FROM MasterItems im JOIN Categories c on c.CATEGORY1 = im.CATEGORY
This is what you're trying to do, which doesn't work because you can't return a POCO (unless you join the OrderItem back somehow and do OrderItem.* somewhere). Ultimately, what you would have to do is just enumerate the collection (either in a foreach loop or by calling ToList()) on the query first and then build your OrderItem objects.
var query = (from im in dc.MasterItems
join c in dc.Categories
on im.CATEGORY equals c.CATEGORY1
select new { MasterItem = im, Category = c});
List<OrderItem> returnItems = new List<OrderItem>();
foreach(var item in query)
{
returnItems.Add(new OrderItem() { OrderItemId = item.MasterItem.ItemId });
}
return returnItems;
OR
return (from im in dc.MasterItems
join c in dc.Categories
on im.CATEGORY equals c.CATEGORY1
select new { MasterItem = im, Category = c})
.ToList()
.Select(tr => new OrderItem() { OrderItemId = tr.MasterItem.ItemId });
Try that out and let me know if that helps.
Expand the order class by creating a partial file where that class OrderItem now has property(ies) which lend itself to business logic needs, but don't need to be saved off to the database.
public partial class OrderItem
{
public int JoinedOrderItemId { get; set; }
public bool HasBeenProcessed { get; set; }
}
I have a very simple linq query that gets data from two datatables (orderHeader & OrderDetails) and joins them together. What I would like to do is take the order items for each order and pass them to a method for processing.
Here is the query:
var results = from orderHeader in dtOrderHeader.AsEnumerable()
join orderDetails in dtOrderDetails.AsEnumerable() on
orderHeader.Field<int>("order_ref") equals
orderDetails.Field<int>("order_id")
select new {
orderID = orderHeader.Field<int>("order_ref"),
orderItem = orderDetails.Field<string>("product_details")
};
What is the best way to iterate the results for each order?
Thanks
This result set contains multiple orders, this would require a nested foreach
foreach (var order in results.Select(r => r.orderID).Distinct()) {
Console.WriteLine("Order: " + order);
Console.WriteLine("Items:");
foreach (var product in results.Where(r => r.orderItem == order)) {
Console.WriteLine(product.orderItem);
}
}