2009-02-10 92 views
168

使用下面的简单示例,使用Linq to SQL返回多个表的结果的最佳方法是什么?返回匿名类型的结果?

说我有两个表:

Dogs: Name, Age, BreedId 
Breeds: BreedId, BreedName 

我想与他们BreedName返回所有的狗。我应该使用这样的事情,没有任何问题让所有的狗:

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; 
} 

但是,如果我想与品种的狗,并尝试这个,我有问题:

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; 
} 

现在我认识到,编译器将不会让我返回一组匿名类型,因为它期望Dogs,但有没有办法在不必创建自定义类型的情况下返回它?或者我必须创建我自己的课程DogsWithBreedNames并在选择中指定该类型?还是有另一种更简单的方法?

+0

出于好奇,为什么所有Linq示例都显示使用匿名类型,如果它们不起作用。例如,[这个例子](http://msdn.microsoft.com/en-us/library/bb399375%28v=vs.110%29.aspx)做了`foreach(查询中的var cust)Console.WriteLine(“id = {0},City = {1}“,cust.CustomerID,cust.City);` – 2014-04-08 18:30:09

+0

@Hot Licks - 这些示例中的Customer表是一个由类表示的实体。这个例子似乎并没有显示这些类的定义。 – 2014-04-08 19:37:26

+0

它也没有告诉你,编译器swizzle正在用类名替换“var”。 – 2014-04-08 19:43:54

回答

178

我倾向于去这个模式:

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; 
} 

这意味着你有一个额外的类,但它的快速和容易的代码,易于扩展,可重用性和类型安全。

0

好吧,如果你回狗,你会怎么做:

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; 
} 

如果你想要的品种渴望加载,而不是延迟加载,只要使用适当的DataLoadOptions结构。

64

可以返回匿名类型,but it really isn't pretty

在这种情况下,我认为创建适当的类型会好得多。如果它只是在包含方法的类型中使用,则使其成为嵌套类型。

就我个人而言,我希望C#获得“命名匿名类型” - 即与匿名类型相同的行为,但具有名称和属性声明,但就是这样。

编辑:其他人建议返回狗,然后通过属性路径访问品种名称等。这是一个非常合理的方法,但IME它会导致您以特定方式完成查询的情况,因为你想要使用的数据 - 并且当你仅仅返回IEnumerable<Dog>时,元信息会丢失 - 查询可能是,由于某些加载选项等原因,您将使用(比如说)Breed而不是Owner,但是如果您忘记并开始使用其他属性,您的应用可能会工作,但效果不如您原先设想的那样高。当然,我可能会说垃圾,或过度优化等...

+3

嘿,我不是一个不想要功能的人,因为害怕他们会被滥用,但是如果允许匿名类型被传递出去,我们可以想象一下我们会看到的各种代码: (颤抖) – 2009-02-10 23:16:29

+17

我们可能会看到一些滥用。基本上,我们也可能会看到一些简单的代码,我们只需要一个元组。不*所有*都需要成为具有复杂行为的对象。有时候“只是数据”是正确的。国际海事组织,当然。 – 2009-02-10 23:18:13

+1

谢谢,所以你的首选是创建类型,即使它是一次性的视图,如这样?我有很多报告以不同的方式分割相同的数据,并希望不必创建所有这些不同的类型(DogsWithBreeds,DogsWithOwnerNames等) – 2009-02-10 23:19:10

7

不,你不能返回匿名类型,而不经过一些欺骗。

如果您没有使用C#,那么您将要查找的内容(不带具体类型返回多个数据)称为元组。

有很多C#元组实现,使用一个shown here,你的代码会像这样工作。

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; 
} 

而且主叫网站:

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); 
    } 
} 
3

只需选择狗,然后用dog.Breed.BreedName,这应该能正常运行。

如果您有很多狗,请使用DataLoadOptions.LoadWith来减少数据库调用次数。

1

如果你的数据库中有一个关系设置,在BreedId上有一个foriegn关键限制,你不知道吗?

DBML relationship mapping http://www.doodle.co.uk/userfiles/image/relationship.png

所以我现在就可以拨打电话:

internal Album GetAlbum(int albumId) 
{ 
    return Albums.SingleOrDefault(a => a.AlbumID == albumId); 
} 

而且在调用代码:

var album = GetAlbum(1); 

foreach (Photo photo in album.Photos) 
{ 
    [...] 
} 

所以在你的情况下,你会是调用像狗。 Breed.BreedName - 正如我所说的,这依赖于你的数据库与这些关系的建立。

正如其他人所说,DataLoadOptions将有助于减少数据库调用,如果这是一个问题。

6

你可以做这样的事情:


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(); 
} 
16

我想补充我的两分钱的价值:-) 我最近了解到的处理匿名对象的方式。针对.NET框架4时,它只能使用和添加时才会System.Web.dll程序的参考,但随后很简单:

... 
using System.Web.Routing; 
... 

class Program 
{ 
    static void Main(string[] args) 
    { 

     object anonymous = CallMethodThatReturnsObjectOfAnonymousType(); 
     //WHAT DO I DO WITH THIS? 
     //I know! I'll use a RouteValueDictionary from System.Web.dll 
     RouteValueDictionary rvd = new RouteValueDictionary(anonymous); 
     Console.WriteLine("Hello, my name is {0} and I am a {1}", rvd["Name"], rvd["Occupation"]); 
    } 

    private static object CallMethodThatReturnsObjectOfAnonymousType() 
    { 
     return new { Id = 1, Name = "Peter Perhac", Occupation = "Software Developer" }; 
    } 
} 

为了能够添加一个参考系统。您必须遵循rushonerok's advice:确保您的[项目]目标框架是“.NET Framework 4”而不是“.NET Framework 4客户端配置文件”。

8

您必须首先使用ToList()方法从数据库获取行,然后选择项作为类。 试试这个:

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

List<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 
        }).ToList() 
        .Select(x=> 
          new Dog{ 
           Name = x.Name, 
           BreedName = x.BreedName, 
          }).ToList(); 
return result;} 

所以,关键是第一ToList()。它立即进行查询并从数据库中获取数据。第二个技巧是选择项目并使用对象初始化程序生成装入项目的新对象。

希望这会有所帮助。

2

您不能直接返回匿名类型,但可以通过泛型方法循环它们。所以大部分的LINQ扩展方法。这里没有魔法,但看起来它会返回匿名类型。如果参数是匿名的,结果也可以是匿名的。

var result = Repeat(new { Name = "Foo Bar", Age = 100 }, 10); 

private static IEnumerable<TResult> Repeat<TResult>(TResult element, int count) 
{ 
    for(int i=0; i<count; i++) 
    { 
     yield return element; 
    } 
} 

下面的例子基于从原来的问题代码:

var result = GetDogsWithBreedNames((Name, BreedName) => new {Name, BreedName }); 


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

BreedIdDog表显然是一个外键对应的行中的Breed表。如果您的数据库设置正确,则LINQ to SQL应自动在两个表之间创建关联。由此产生的Dog类将拥有Breed属性,而Breed类应该拥有Dogs集合。通过这种方式设置,您仍然可以返回IEnumerable<Dog>,这是一个包含品种属性的对象。唯一需要注意的是,您需要在查询中预先加载品种对象以及狗对象,以便在处理完数据上下文后访问它们,并且(如另一个海报所建议的)在集合上执行一个方法,查询中(在这种情况下,ToArray的)立即执行:

public IEnumerable<Dog> GetDogs() 
{ 
    using (var db = new DogDataContext(ConnectString)) 
    { 
     db.LoadOptions.LoadWith<Dog>(i => i.Breed); 
     return db.Dogs.ToArray(); 
    } 

} 

它是那么微不足道访问品种的每一只狗:

foreach (var dog in GetDogs()) 
{ 
    Console.WriteLine("Dog's Name: {0}", dog.Name); 
    Console.WriteLine("Dog's Breed: {0}", dog.Breed.Name);   
} 
4

现在我认识到,编译器不会让我返回一组匿名类型,因为它期望Dogs,但有没有办法在不必创建自定义类型的情况下返回此类型?

使用用途对象返回匿名类型列表而不创建自定义类型。 这将工作没有编译器错误(在.net 4.0中)。我回到列表给客户端,然后解析它的JavaScript:

public object 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; 
} 
0

如果其主要思想是使发送到数据库服务器只有所需的字段的SQL SELECT语句,而不是所有的实体领域,然后ü可以做到这一点:

public class Class1 
{ 
    public IList<Car> getCarsByProjectionOnSmallNumberOfProperties() 
    { 

     try 
     { 
      //Get the SQL Context: 
      CompanyPossessionsDAL.POCOContext.CompanyPossessionsContext dbContext 
       = new CompanyPossessionsDAL.POCOContext.CompanyPossessionsContext(); 

      //Specify the Context of your main entity e.g. Car: 
      var oDBQuery = dbContext.Set<Car>(); 

      //Project on some of its fields, so the created select statment that is 
      // sent to the database server, will have only the required fields By making a new anonymouse type 
      var queryProjectedOnSmallSetOfProperties 
       = from x in oDBQuery 
        select new 
        { 
         x.carNo, 
         x.eName, 
         x.aName 
        }; 

      //Convert the anonymouse type back to the main entity e.g. Car 
      var queryConvertAnonymousToOriginal 
       = from x in queryProjectedOnSmallSetOfProperties 
        select new Car 
        { 
         carNo = x.carNo, 
         eName = x.eName, 
         aName = x.aName 
        }; 

      //return the IList<Car> that is wanted 
      var lst = queryConvertAnonymousToOriginal.ToList(); 
      return lst; 

     } 
     catch (Exception ex) 
     { 
      System.Diagnostics.Debug.WriteLine(ex.ToString()); 
      throw; 
     } 
    } 
} 
0

在C#7,你现在可以使用元组......这样就无需创建一个类只是返回的结果!

这里是一个示例代码:

public List<(string Name, string BreedName)> 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 
      }.ToList(); 

    return result.Select(r => (r.Name, r.BreedName)).ToList(); 
} 

您可能需要安装虽然System.ValueTuple NuGet包。