返回匿名类型的结果?


194

使用下面的简单示例,使用Linq将多个表中的结果返回到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并在选择中指定该类型?还是有另一种更简单的方法?


出于好奇,为什么所有的Linq示例都无法使用匿名类型进行显示。例如,此示例确实有效foreach (var cust in query) Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);
热门点击时间2014年

@Hot Licks-这些示例中的Customer表是由类表示的实体。该示例似乎没有显示这些类的定义。
乔纳森·S.

它也没有告诉您编译器麻烦正在用类名替换“ var”。
Hot Licks

Answers:


213

我倾向于这种模式:

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

这意味着您有一个额外的类,但它编写起来又快又容易,易于扩展,可重用并且是类型安全的。


我喜欢这种方法,但是现在我不确定如何显示狗的名字。如果将结果绑定到DataGrid,是否可以在DogWithBreed类中未明确定义它们的情况下从Dog获取属性,还是必须为要显示的每个字段创建getter / setter方法?
乔纳森·S.2009年

4
DataGrid不允许您将属性指定为“ Dog.Name”吗?我现在忘记了为什么我恨他们到永远都不会使用它们……
teedyay

@乔纳森 您如何在模板列中执行此操作?请告诉我我处于类似情况
rahularyansharma 2012年

嘿,我喜欢这种将两个类封装为一个类的方法。它使操作变得容易。更不用说创建仅在当前上下文中使用的简单类了,这会使它变得更整洁。
Linger

6
这确实不能回答OP所具有的问题,“是否有一种无需创建自定义类型就可以返回它的方法”?
tjscience 2013年

69

可以返回匿名类型,但这确实不是很漂亮

在这种情况下,我认为创建适当的类型会更好。如果仅在包含该方法的类型中使用它,则将其设置为嵌套类型。

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

编辑:其他人建议回狗,然后通过属性路径等访问品种名称。这是一种完全合理的方法,但是IME会导致您由于需要数据而以特定方式进行查询的情况使用-并且当您刚返回时该元信息会丢失IEnumerable<Dog>-该查询可能期望您使用(例如),Breed而不是Owner由于某些加载选项等,但是如果您忘记了这一点并开始使用其他属性,则您的应用可能会运行,但效率不如您最初设想的那样。当然,我可能在谈论垃圾,或者过度优化等等。


3
嘿,我不是不想因为滥用功能而不想使用功能的人,但是您能想象一下如果允许命名匿名类型被传递出去,我们会看到哪种繁琐的代码?(颤抖)
戴夫·马克尔

19
我们可能会看到一些虐待。基本上,我们可能还会看到一些更简单的代码,我们只需要一个元组。并非所有事物都需要成为具有复杂行为的对象。有时,“只是数据”就是正确的事情。海事组织,当然。
乔恩·斯基特

1
谢谢,所以您的首选是创建类型,即使它是用于诸如此类的一次性视图?我有很多报告以不同的方式对相同的数据进行切片,并希望不必创建所有这些不同的类型(DogsWithBreeds,DogsWithOwnerNames等)
Jonathan S.

1
我会尽量不需要以很多方式对其进行切片,或者将切片部分放在需要数据的位置,以便您可以使用匿名类型-但除此之外,是的。它在某种程度上很烂,但是我担心的就是这种生活:(
Jon Skeet

17

只是为了增加我两美分的价值:-)我最近学会了一种处理匿名对象的方法。仅在以.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" };
    }
}

为了能够添加对System.Web.dll的引用,您必须遵循rushonerok的建议:确保[项目的]目标框架是“ .NET Framework 4”而不是“ .NET Framework 4 Client Profile”。


2
ASP.NET Mvc School;)
T-moty

8

不,您必须经过一些技巧才能返回匿名类型。

如果您不使用C#,那么您要查找的内容(在没有具体类型的情况下返回多个数据)称为Tuple。

有很多C#元组实现,使用此处显示的实现,您的代码将像这样工作。

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

7
这是行不通的。引发NotSupportedExceptionLINQ to Entities仅支持无参数的构造函数和初始化程序
mshsayem 2012年

1
的确,Tuple类没有默认的构造函数,因此无法通过LINQ to Entities正常工作。但是,与问题一样,它在LINQ to SQL上也能正常工作。我还没有尝试过,但是它可能适用于... select Tuple.Create(d, b)
joshperry

1
由于某些LINQ提供程序不支持元组,因此您不能选择匿名类型,将其转换为IEnumerable,然后从中选择一个元组吗?
TehPers

8

您可以执行以下操作:


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

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()。它立即进行查询并从数据库获取数据。第二个技巧是选择项目并使用对象初始化程序生成加载了项目的新对象。

希望这可以帮助。


8

在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软件包。


4

现在,我意识到编译器不允许我返回一组匿名类型,因为它期望使用Dogs,但是有没有一种方法可以不必创建自定义类型而返回它呢?

使用use 对象可返回匿名类型列表,而无需创建自定义类型。这将工作而不会出现编译器错误(在.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;
}

1
我认为,如果您的方法签名看起来像这样,它将更加正确和可读:public IEnumerable <object> GetDogsWithBreedNames()
pistol-pete


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

好吧,如果您要归还Dogs,可以这样做:

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

如果要让Breed优先加载而不是延迟加载,请使用适当的DataLoadOptions构造。


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

0

如果主要思想是使发送到数据库服务器的SQL select语句仅具有必填字段,而不是所有Entity字段,那么您可以执行以下操作:

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

尝试此操作以获取动态数据。您可以转换List <>的代码

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

dynamic dogInfo=GetDogsWithBreedNames();
var name = dogInfo.GetType().GetProperty("Name").GetValue(dogInfo, null);
var breedName = dogInfo.GetType().GetProperty("BreedName").GetValue(dogInfo, null);

0

如果您在数据库中建立了关系,并且对BreedId进行了前瞻性的键约束,那么您是否已经明白了?

DBML关系映射

现在我可以打电话给:

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

在调用该代码的代码中:

var album = GetAlbum(1);

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

因此,在您的实例中,您将调用诸如dog.Breed.BreedName之类的东西-如我所说,这取决于您使用这些关系设置的数据库。

正如其他人提到的那样,如果出现问题,DataLoadOptions将有助于减少数据库调用。


0

这不能完全回答您的问题,但是Google根据关键字将我引到了这里。这是您可以从列表中查询匿名类型的方法:

var anon = model.MyType.Select(x => new { x.Item1, x.Item2});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.