返回匿名类型的结果?

使用下面的简单示例,使用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;
}

现在我意识到编译器不让我返回一组匿名类型,因为它期待狗,但有没有一种方法来返回这个而不必创建一个自定义类型?或者我必须为DogsWithBreedNames创建自己的类并在select中指定该类型?或者还有其他更简单的方法吗?

232307 次浏览

如果你要归还《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构造。

可以返回匿名类型,但它真的不漂亮

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

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

编辑:其他人建议返回狗,然后通过属性路径访问品种名称等。这是一个完全合理的方法,但是输入法导致的情况下你所做的一个查询以特定的方式,因为您想要使用的数据,元信息丢失,当你返回IEnumerable<Dog>——查询可能期待您使用(说)Breed而不是Ownerdue一些加载选项等,但如果你忘了,开始使用其他属性,应用程序可以工作但不是你最初设想的一样有效。当然,我可能在胡说八道,或者是过度优化等等……

不,如果不使用一些技巧,就不能返回匿名类型。

如果您不使用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);
}
}

只需要选择狗,然后使用dog.Breed.BreedName,这应该可以正常工作。

如果你有很多狗,使用DataLoadOptions。LoadWith来减少db调用的数量。

如果你在你的数据库中有一个关系设置,在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将有助于减少数据库调用。

我倾向于这样的模式:

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

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

你可以这样做:


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

只是补充我的两分钱的价值:-) 我最近学习了一种处理匿名对象的方法。它只能在针对.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客户端配置文件”。

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

希望这能有所帮助。

不能直接返回匿名类型,但可以通过泛型方法对它们进行循环。大多数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;
}

Dog表中的BreedId显然是Breed表中相应行的外键。如果您已经正确地设置了数据库,LINQ to SQL应该自动在两个表之间创建一个关联。生成的Dog类将有一个Breed属性,而Breed类应该有一个Dogs集合。这样设置它,你仍然可以返回IEnumerable<Dog>,它是一个包含breed属性的对象。唯一需要注意的是,您需要在查询中预加载breed对象和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);
}

现在我意识到编译器不让我返回一组匿名类型,因为它期待狗,但有没有一种方法来返回这个而不必创建一个自定义类型?

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

如果主要的想法是让SQL选择语句发送到数据库服务器只有必需的字段,而不是所有的实体字段,那么你可以这样做:

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

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

尝试这样获取动态数据。您可以转换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);

这并没有完全回答你的问题,但谷歌根据关键字引导我到这里。这是从列表中查询匿名类型的方法:

var anon = model.MyType.Select(x => new { x.Item1, x.Item2});