使用下面的简单示例,使用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
创建自己的类并在select中指定该类型?还是有另一种更简单的方法吗?
我倾向于采用这种模式:
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;
}
这意味着你有一个额外的类,但它编码快速简单,易于扩展,可重用和类型安全。
您不能直接返回匿名类型,但可以通过泛型方法循环它们。大多数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;
}
好吧,如果你要回狗,你会做:
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结构。
BreedId
表中的Dog
显然是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);
}
如果主要想法是使发送到数据库服务器的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;
}
}
}
试试这个来获取动态数据。您可以转换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);
如果你的数据库中的关系设置与BreedEd上的外键约束你不是已经得到了吗?
所以我现在可以打电话给:
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将有助于减少数据库调用。
您可以返回匿名类型,but it really isn't pretty。
在这种情况下,我认为创建适当的类型会好得多。如果仅在包含该方法的类型中使用它,则将其设置为嵌套类型。
就个人而言,我希望C#获得“命名匿名类型” - 即与匿名类型相同的行为,但具有名称和属性声明,但就是这样。
编辑:其他人建议返回狗,然后通过属性路径等访问品种名称。这是一个非常合理的方法,但IME导致你以特定方式完成查询的情况,因为你想要的数据使用 - 当你只返回IEnumerable<Dog>
时,元信息会丢失 - 查询可能会期望你使用(比方说)Breed
而不是Owner
due来加载某些加载选项等,但是如果你忘记了并开始使用其他属性,你的应用可能会工作但不如你原先设想的那样有效。当然,我可能会说垃圾,或过度优化等等......
只是为了增加我的两分钱:-)我最近学会了一种处理匿名对象的方法。它只能在面向.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's advice:确保您的[project's]目标框架是“.NET Framework 4”而不是“.NET Framework 4 Client Profile”。
不,你不能通过一些诡计返回匿名类型。
如果您没有使用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);
}
}
你可以这样做:
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();
}
您必须首先使用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()
。它立即进行查询并从数据库中获取数据。第二个技巧是选择项目并使用对象初始化程序生成包含项目的新对象。
希望这可以帮助。
在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包。
现在我意识到编译器不会让我返回一组匿名类型,因为它期待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;
}
只选择狗,然后使用dog.Breed.BreedName
,这应该工作正常。
如果您有很多狗,请使用DataLoadOptions.LoadWith来减少db调用次数。