Foreign keys cause more queries? - linq-to-sql

I have 2 objects - Order and Product. On the Order only productID is saved and when I view orders I want to see the product name. According to ScuttGu blog this is easily done by using a template field with Eval("Product.ProductName"). However, when reviewing the actual queries I see that for each order a separate query is made.
It doesn't sould right to me because for many rows and/or foreign keys many additional queries will be made. Doesn't it make the whole this too inefficient (i.e. why linq doesn't use a join)?
Thanks

That is because your products are lazy loaded - that is they are loaded when needed.
You can DataLoadOptions to set your fetchingstrategy, and load the products with your order:
MyDataContext db = new MyDataContext();
DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Order>(order => order.Product);
db.LoadOptions = options;
var orders = from c in db.Orders
If you don't like the pr. datacontext specification of loadoptions you do something like this (not testet):
MyDataContext db = new MyDataContext();
db.Orders
.Select(o => new { Order = o, Products = o.Products})
.ToList()
.Select(x => x.Order)
.ToList();
I have implemented something like this guys fethingstrategies, which works out nicely with my repositories and the specification pattern.

This happens because at the point that Eval has run, the query already has, without said join.
When you're fetching the query you can use DataLoadOptions to include this using the .LoadWith() method:
var dlo = new DataLoadOptions();
dlo.LoadWith<Order>(o => o.Product);
var dc = new DataContext();
dc.LoadOptions = dlo;
var orders = from dc.Orders select o;

Related

How do I make a newsfeed without many join statements

What is the best way to approach making a newsfeed?
Currently I have an observer that creates a new newsfeedactivity record everytime someone creates a record. But, to deal with privacy I end up have 7 or 8 joins to get the appropriate output.
It seems like this is going to be slow and inefficient. What's another strategy for pulling out the right newsfeedactivity records as a scope?
More details:
Currently I have a site to help users track projects that they're working on. There are public and private projects (where people can invite collaborators).
I want my newsfeed to include when public projects are created. When you are invited to a private project. When a user follows a project. And then all of the actions of the other users that you're following. Then for the private projects I have another join table to determine who has access to the projects. (There are also comments on each of these projects that I want to show up in the newsfeed as well).
All of the following relationships are currently in join tables, which is why I have a lot of joins.
To get an idea of the type of query - I'm thinking it would look something like this:
SELECT news_feed_activities.* FROM news_feed_activities LEFT JOIN
user_following_relationships ON
user_following_relationships.following_id =
news_feed_activities.user_id LEFT JOIN
user_project_relationships ON
user_project_relationships.project_id =
news_feed_activities.responding_to_id AND
news_feed_activities.responding_to_type = 'Project' WHERE
(user_following_relationships.user_id = 1 OR
user_project_relationships.user_id = 1 OR
news_feed_activities.user_id = 1 OR
up2.user_id = 1) GROUP BY news_feed_activities.id ORDER BY
news_feed_activities.id DESC
EDIT:
I think I'm probably going to end up using Redis along these lines http://blog.waxman.me/how-to-build-a-fast-news-feed-in-redis
As RoR.
In your controller:
#user = current_user # (?)
recent_since = 24.hours.ago
#news_feed = []
# 1) I want my newsfeed to include when public projects are created.
#news_feed += Project.recent.open
# 2) When you are invited to a private project.
#news_feed += #user.invites.received.pending
# 3) When a user follows a project.
#news_feed += #user.user_following_relationships.recent
# 4) And then all of the actions of the other users that you're following.
#news_feed += #user.follows.collect(&:activities)
# 5) Then for the private projects I have another join table to determine who has access to the projects. (There are also comments on each of these projects that I want to show up in the newsfeed as well).
#news_feed += #user.projects.closed
#news_feed.sort!{ |a,b| a.created_at <=> b.created_at }
I did some sample scopes for you too.
project.rb
scope :recent, :conditions => ["created_at >= ?", 24.hours.ago]
scope :open, :conditions => "publicity = 'Public'"
scope :closed, :conditions => "publicity = 'Private'"
This is based on the precept that your news feed is actually a summary of recent activity across models rather than having a 'newsfeed' model.

LINQ to SQL select all fields in a table but with a distinct column

I need to return a list of counties, but I need to filter out duplicate phone code values. For some reason I'm having trouble with the syntax. Can anyone show me how to do this? Should I be using the group by instead?
Group by would work if you need the actual entity.
var query = db.Counties.GroupBy( c => new { c.CountyName, c.PhoneCode } )
.Select( g => g.FirstOrDefault() );
Or if you are constructing it for a view model and only need the data, you could use Distinct. The following creates an anonymous type that could be used to populate the model.
var query = db.Counties.Select( c => new { c.CountyName, c.PhoneCode } )
.Distinct();

LINQ 2 SQL Query ObjectDisposed Exception

This one i had today is a strange one.
I have this query in an assembly method.
public Order[] SelectAllOrders()
{
Order[] orders;
using (MyDataContext context = new MyDataContext())
{
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Order>(order => order.OrderDetails);
context.LoadOptions = dlo;
orders = context.Orders.Select(p => p).ToArray();
}
return orders;
}
Supposed i already called the ToArray() the SQL Command executed and gave me the objects i need and i give them to a new Order[] array this should not need the DataContext instance.
While im serializing the Order[] i get from the method return, serializer tries to access the DataContext again and i get an exception that cannot access disposed object.
Tried without the using() statement and works like it should. But, why i get this behavior?
Anyone could give an explanation why deferred loading still remains while I'm calling .ToArray() and assigning new variable with the contents?
The Select(p=>p) achieves very little; you might as well just call:
orders = context.Orders.ToArray();
Re the problem - I would guess that either OrderDetails hasn't really loaded, or it is trying to load some other data lazily. I would suggest investigating by (in a dev session):
Order[] orders;
using (MyDataContext context = new MyDataContext())
{
context.Log = Console.Out; // show me
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Order>(order => order.OrderDetails);
context.LoadOptions = dlo;
Console.WriteLine("> Calling ToArray");
orders = context.Orders.ToArray();
Console.WriteLine("> ToArray complete");
// TODO: your extra code that causes serialziation, probably
// involving `DataContractSerializer`
Console.WriteLine("> Calling Dispose");
}
With this, you should be able to see any extra database trips that are happning after the ToArray but before the Dispose(). The point being: this data is needed for serialization, so either a: ensure it gets loaded, or b: exclude it from serialization.

What's the best way to save a one-to-many relationship in Linq2Sql?

I'm trying to figure out the best way to save a simple one-to-many relationship in Linq2Sql.
Lets assume we have the following POCO model (pseduo code btw):
Person has zero to many Vechicles.
class Person
{
IList<Vehicle> Vehicle;
}
class Vehicle
{
string Name;
string Colour;
}
Now, when i save a Person, i pass that poco object to the repository code (which happens to be L2S). I can save the person object fine. I usually do this.
using (Db db = new Db())
{
var newPerson = db.People.SingleOrDefault(p => p.Id == person.Id) ?? new SqlContext.Person();
// Left to right stuff.
newPerson.Name = person.Name;
newPerson.Age = person.Age;
if (newPerson.Id <= 0)
db.People.InsertOnSubmit(newPerson);
db.SubmitChanges();
}
i'm not sure where and how i should handle the list of vehicles the person might have? any suggestions?
using (Db db = new Db())
{
var newPerson = db.People.SingleOrDefault(p => p.Id == person.Id) ?? new SqlContext.Person();
// Left to right stuff.
newPerson.Name = person.Name;
newPerson.Age = person.Age;
// add vehicles.
Vehicle firstV = new Vehicle();
firstV.Name = "some name";
firstV.Person = newPerson; // need to do this to set the person Id on the vehicle.
newPerson.Vehicle.Add(firstV);
// now when you save the Person it should save the Vehicle list
// if you set Cascade save update on the list. (not sure how to do that in L2S
if (newPerson.Id <= 0)
db.People.InsertOnSubmit(newPerson);
db.SubmitChanges();
}
Now you may choose to construct the list of vehicles at another level , with the data that's coming from the interface.
But you need to remember that it's not enough to add the Vehicle to the list on the Person object , you also need to set the vehicles Person property to the person that has the vehicles.
Observation I'm not sure about this but when you do db.People.SingleOrDefault you might be loading the whole People table in memory . That's not something you want to do. Corrected by Slace in the comments.
All you need to do is ensure that there are the appropriate relationships set up within the database.
If your Vehicle table has a PersonId and there is a foreign key between them when you add them to the DBML Linq to SQL will detect that there is a relationship between them and create a Table<T> representation of the relationship.

Problem with Linq2Sql Many-to-Many relationship & Inserting new objects

i'm trying to do a simple linq 2 sql many-to-many, insert some data, operation.
here's the stock Northwind model representing a many to many:
alt text http://www.iaingalloway.com/images/linq-detail.jpg
Now what i'm trying to do is insert a new order and if the product doesn't exist, then insert that at the same time, within the same transaction. The error i'm getting is:
System.Data.Linq.DuplicateKeyException: Cannot add an entity with a
key that is already in use.
So this is my (pseduo) code:
using (SqlContext db = new SqlContext())
{
// Get existing or create a new instance.
Order newOrder = GetOrder(order.Id) ?? new Order();
// Left to right stuff.
newOrder.Foo = order.Foo;
// Now associate this new order to a product (which might not exist).
if (!order.ProductList.IsNullOrEmpty())
{
// We have some products...
IList<Order_Detail> orderDetailList = new List<Order_Detail>();
foreach(Models.Product product in order.ProductList)
{
// Associate each product to the a new order_detail.
orderDetailList.Add(new Order_Detail
{
Product = new SqlContext.Product
{
Foo = product.Foo
}
});
}
// Now associate all the order_details to this order.
newOrder.Order_Details.AddRange(orderDetailList);
if (newOrder.Id <= 0)
db.InsertOnSubmit(newOrder);
db.SubmitChanges(); // <-- exception throw here.
}
}
I'm assuming i need to save the products first before i try and save the order? I'm so confused :(
// Associate each product to the a new order_detail.
orderDetailList.Add(new Order_Detail
{
Product = new SqlContext.Product
{
Foo = product.Foo
}
});
One thing that is wrong here, is that you create a new product to set on your Order_Detail.Product property. Instead , you should take the product that's comming from the database and set it on the property.
I'm not sure what order.ProductList has inside - if these products are loaded from the database then you should set them directly to your Order_Detail.Product instead of doing new SqlContext.Product.
#jfar L2S does support many-to-many relationships , you just can't have a property Products on your Order ( in this case this is actually a good thing because OrderDetails has Quantity and other properties).
Many to Many relationships aren't supported in Linq2Sql. :(
There are a couple of workarounds:
http://www.iaingalloway.com/many-to-many-relationships-in-linq-to-sql
http://blogs.msdn.com/mitsu/archive/2008/03/19/how-to-implement-a-many-to-many-relationship-using-linq-to-sql-part-ii-add-remove-support.aspx
Weird that the picture of your db schema is the same as one of the articles...