views:

741

answers:

3

I just started learning LINQ2SQL and one of the first things I wanted to try is a simple parent-child hierarchy, but I can't seem to find a good way to do it. I saw some examples here on SO and i've googled, but I couldn't apply them directly, so I'll explain exactly what i'm trying to accomplish.

Lets use the common example with tags.

Database tables: Post-- Post_Tags -- Tags

I've created a simple Post class so I avoid passing Linq2Sql classes around:

public class Post
{
    public int Id {get; set;}
    public int Title {get; set;}
    public IEnumerable<string> Tags {get; set;}
}

I would like to select 5 latest records from the Posts table, get their related tags and return the IList where each Post has their Tags property filled.

Can you show me a concrete Linq2Sql code how could I do that?

I tried:

      IList<Post> GetLatest()
      {
            return (from p in _db.Posts
                   orderby p.DateCreated descending
                   select new Post
                   {
                       Id = p.Id,
                       Title = p.Title,
                       Tags = p.Post_Tags.Select(pt => pt.Tag.Name)
                   }).Take(5).ToList();
       }

This works but duplicates Post records for each Tag record and I have to duplicate property mapping (Id=p.Id, ...) in every method I user. I then tried this approach, but in this case, I have a roundtrip to DB for every tag:

      IQueryable<Post> GetList()
      {
            return (from p in _db.Posts
                   select new Post
                   {
                       Id = p.Id,
                       Title = p.Title,
                       Tags = p.Post_Tags.Select(pt => pt.Tag.Name)
                   });
       }

      IList<Post> GetLatest()
      {
            return (from p in GetList()
                   orderby p.DateCreated descending
                   select p).Take(5).ToList();
       }

If I were doing it in classic ADO.NET, I would create a stored procedure that returns two resultsets. One with Post records and second with related Tag records. I would then map them in the code (by hand, by DataRelation, ORM, etc.). Could I do the same with LINQ2SQL?

I'm really curious to see some code samples on how do you guys handle such simple hierarchies.

And yes, I would really like to return IList<> objects and my custom classes and not queryable Linq to Sql objects, because I would like to be flexible about the data access code if I for example decide to abandon Linq2Sql.

Thanks.

+1  A: 

If you create a DataContext, the parent-child relationship is maintained automatically for you.

i.e. If you model the Posts and Tags and their relationship inside a Linq2Sql DataContext, you can then fetch posts like this:

var allPosts = from p in _db.Posts
               orderby p.DateCreated descending
               select p;

Then you won't have to worry about any tags at all, because they are accessible as a member of the variable p as in:

var allPostsList = allPosts.ToList();

var someTags = allPostsList[0].Post_Tags;
var moreTags = allPostsList[1].Post_Tags;

And then any repeated instance is then automatically updated across entire DataContext until you ask it to SubmitChanges();

IMO, That's the point of an ORM, you don't re-create the model class and maintain the mapping across many places because you want all those relationships managed for you by the ORM.

As for the roundtrip, if you refrain from any code that explicitly requests a trip to the database, all queries will be stored in an intermediate query representation and only when the data is actually needed to continue, is when the query will be translated to sql and dispatched to the database to fetch results.

i.e. the following code only access the database once

 // these 3 variables are all in query form until otherwise needed
 var allPosts = Posts.All();
 var somePosts = allPosts.Where(p => p.Name.Contains("hello"));
 var lesserPosts = somePosts.Where(p => p.Name.Contains("World"));

 // calling "ToList" will force the query to be sent to the db
 var result = lesserPosts.ToList();
chakrit
Thank you for your answer, but the most important this is that I don't want to rely on Linq2Sql classes OUTSIDE the DAL.
muerte
A: 

How about if you set your DataLoadOptions first to explicitly load tags with posts? Something like:

IList<Post> GetLatest()
{
     DataLoadOptions options = new DataLoadOptions();
     options.LoadWith<Post>(post => post.Tags);
     _db.LoadOptions = options;

     return (from p in _db.Posts
             orderby p.DateCreated descending)
             Take(5).ToList();
   }
Eric King
I tried it, it still goes n-times to the database. And I can't do post => post.Tags, but post => post.Post_Tags
muerte
A: 
List<Post> latestPosts = db.Posts
  .OrderByDescending( p => p.DateCreated )
  .Take(5)
  .ToList();

  // project the Posts to a List of IDs to send back in
List<int> postIDs = latestPosts
  .Select(p => p.Id)
  .ToList();

// fetch the strings and the ints used to connect 
ILookup<int, string> tagNameLookup = db.Posts
  .Where(p => postIDs.Contains(p.Id))
  .SelectMany(p => p.Post_Tags)
  .Select(pt => new {PostID = pt.PostID, TagName = pt.Tag.Name } )
  .ToLookup(x => x.PostID, x => x.TagName);
//now form results
List<Post> results = latestPosts
  .Select(p => new Post()
  {
    Id = p.Id,
    Title = p.Title,
    Tags = tagNameLookup[p.Id]
  })
  .ToList();
David B
So this will do 2 round trips to the DB?
Pure.Krome
Yes, that would be 2 trips.
David B