views:

3354

answers:

7

Using the simple example below, what is the best way to return results from multiple tables using Linq to Sql?

Say I have two tables:

Dogs: Name, Age, BreedId

Breeds: BreedId, BreedName

I want to return all dogs with their BreedName. I should get all dogs using something like this with no problems:

public IQueryable<Dog> GetDogs()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select d;
    return result;
}

But if I want dogs with breeds and try this I have problems:

public IQueryable<Dog> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new
                        {
                            Name = d.Name,
                            BreedName = b.BreedName
                        };
    return result;
}

Now I realize that the compiler won't let me return a set of anonymous types since it's expecting Dogs, but is there a way to return this without having to create a custom type? Or do I have to create my own class for DogsWithBreedNames and specify that type in the select? Or is there another easier way?

Thanks in advance.

+1  A: 

Well, if you're returning Dogs, you'd do:

public IQueryable<Dog> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    return from d in db.Dogs
           join b in db.Breeds on d.BreedId equals b.BreedId
           select d;
}

If you want the Breed eager-loaded and not lazy-loaded, just use the appropriate DataLoadOptions construct.

Dave Markle
Will this give me Dogs with their breed names or just the fields in the Dogs table?
Jonathan S.
+4  A: 

You can return anonymous types, but it really isn't pretty.

In this case I think it would be far better to create the appropriate type. If it's only going to be used from within the type containing the method, make it a nested type.

Personally I'd like C# to get "named anonymous types" - i.e. the same behaviour as anonymous types, but with names and property declarations, but that's it.

EDIT: Others are suggesting returning dogs, and then accessing the breed name via a property path etc. That's a perfectly reasonable approach, but IME it leads to situations where you've done a query in a particular way because of the data you want to use - and that meta-information is lost when you just return IEnumerable<Dog> - the query may be expecting you to use (say) Breed rather than Ownerdue to some load options etc, but if you forget that and start using other properties, your app may work but not as efficiently as you'd originally envisaged. Of course, I could be talking rubbish, or over-optimising, etc...

Jon Skeet
Hey, I'm not one to not want features because of fear out of the way they'll be abused, but can you imagine the kinds of crufty code that we'd see if they allowed named anonymous types to be passed out? (shiver)
Dave Markle
We might see some abuse. We might also see some much simpler code where we just want a tuple, basically. Not *everything* needs to be an object with complex behaviour. Sometimes "just the data" is the Right Thing. IMO, of course.
Jon Skeet
Thanks, so your preference is to create types even if it's for a one-off view such as this? I have a lot of reports that slice the same data in different ways and was hoping to not have to create all of these different types (DogsWithBreeds, DogsWithOwnerNames, etc.)
Jonathan S.
I'd try not to need to slice it in quite so many ways, or put the slicing part in the place which needs the data so you *can* use anonymous types - but beyond that, yes. It sucks in some ways, but such is life I'm afraid :(
Jon Skeet
Hey I'm just glad that Jon Skeet answered one of my questions. Life is good!
Jonathan S.
I sometimes feel like SO is just a big disappointment waiting to happen if people ever actually meet me.
Jon Skeet
+1 I love the idea of named anonymous types, especially if able to be set immutable
Maslow
I was looking for something similar to "Named Anonymous types" (as you mentioned) here http://stackoverflow.com/questions/793415/use-of-anonymous-class-in-c, all got is definition of word "Anonymous"
Prashant
+1  A: 

No you cannot return anonymous types without going through some trickery.

If you were not using C#, what you would be looking for (returning multiple data without a concrete type) is called a Tuple.

There are alot of C# tuple implementations, using the one shown here, your code would work like this.

public IEnumerable<Tuple<Dog,Breed>> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new Tuple<Dog,Breed>(d, b);

    return result;
}

And on the calling site:

void main() {
    IEnumerable<Tuple<Dog,Breed>> dogs = GetDogsWithBreedNames();
    foreach(Tuple<Dog,Breed> tdog in dogs)
    {
        Console.WriteLine("Dog {0} {1}", tdog.param1.Name, tdog.param2.BreedName);
    }
}
joshperry
A: 

Just select dogs, then use dog.Breed.BreedName, this should work fine.

If you have a lot of dogs, use DataLoadOptions.LoadWith to reduce the number of db calls.

Andrey Shchekin
A: 
Zhaph - Ben Duguid
+7  A: 

I tend to go for this pattern:

public class DogWithBreed
{
    public Dog Dog { get; set; }
    public string BreedName  { get; set; }
}

public IQueryable<DogWithBreed> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new DogWithBreed()
                        {
                            Dog = d,
                            BreedName = b.BreedName
                        };
    return result;
}

It means you have an extra class, but it's quick and easy to code, easily extensible, reusable and type-safe.

teedyay
I like this approach but now I'm not sure how to display the dog's name. If I'm binding the result to a DataGrid, can I get the properties from Dog without defining them explicitly in the DogWithBreed class or do I have to create the getter/setter for each field that I want to display?
Jonathan S.
Do DataGrids not allow you to specify the property as "Dog.Name"? I forget now why I hate them enough never to use them...
teedyay
I was able to get it working using TemplateColumns. Thanks.
Jonathan S.
A: 

You could do something like this:


public System.Collections.IEnumerable GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new
                        {
                            Name = d.Name,
                            BreedName = b.BreedName
                        };
    return result.ToList();
}