以下の簡単な例を使用して、Linq to SQLを使用して複数のテーブルから結果を返す最良の方法は何ですか?
2つのテーブルがあるとします。
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;
}
これは、余分なクラスがあることを意味しますが、コーディングが迅速かつ簡単であり、簡単に拡張可能で、再利用可能で、タイプセーフです。
あなたはcan匿名型を返します しかし実際にはきれいではありません 。
この場合、適切なタイプを作成する方がはるかに良いと思います。メソッドを含む型内からのみ使用する場合は、ネストされた型にします。
個人的には、C#に「名前付き匿名型」を取得したいです。つまり、匿名型と同じ動作ですが、名前とプロパティの宣言がありますが、それだけです。
編集:他の人は犬を返すことを提案し、プロパティパスなどを介して品種名にアクセスします。それは完全に合理的なアプローチですが、IMEはあなたがしたいデータのために特定の方法でクエリを行った状況につながります使用-そして、IEnumerable<Dog>
を返すだけでそのメタ情報は失われます-クエリはexpectingになるかもしれません。たとえば、ロードオプションなどによってBreed
dueではなくOwner
を使用します。他のプロパティを使用すると、アプリは機能する可能性がありますが、当初想定したほど効率的ではありません。もちろん、私はゴミを話したり、過剰に最適化したりするかもしれません...
ちょうど2セントの価値を追加するために:-)最近、匿名オブジェクトを処理する方法を学びました。 .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ではなく" .NET Framework 4 "であることを確認してくださいフレームワーク4クライアントプロファイル」。
次のようなことができます:
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()
です。すぐにクエリを作成し、データベースからデータを取得します。 2番目のトリックは、アイテムの選択とオブジェクト初期化子の使用がロードされたアイテムで新しいオブジェクトを生成することです。
お役に立てれば。
いいえ、いくつかのトリックを経ずに匿名型を返すことはできません。
C#を使用していない場合、探しているもの(具体的な型なしで複数のデータを返す)はタプルと呼ばれます。
ここに示されている を使用するC#Tuple実装が多数あり、コードは次のように動作します。
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);
}
}
今、私はコンパイラがドッグを期待しているため、匿名型のセットを返さないことを理解していますが、カスタム型を作成せずにこれを返す方法はありますか?
Use objectを使用して、カスタムタイプを作成せずに匿名タイプのリストを返します。これは、コンパイラエラー(.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呼び出しの数を減らします。
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パッケージをインストールする必要がある場合があります。
匿名型を直接返すことはできませんが、一般的なメソッドを介して匿名型をループすることはできます。そのため、ほとんどの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;
}
データベースに関係の設定があり、BreedIdに主要な制約が設定されている場合、それはすでに得られていますか?
だから私は今呼び出すことができます:
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は、それが問題である場合、データベース呼び出しを減らすのに役立ちます。
データベースサーバーに送信される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;
}
}
}
これを試して、動的データを取得してください。 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);
BreedId
テーブルのDog
は、明らかにBreed
テーブルの対応する行への外部キーです。データベースが適切に設定されていれば、LINQ to SQLは2つのテーブル間の関連付けを自動的に作成します。結果の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);
}
まあ、あなたが犬を返しているなら、あなたはそうするでしょう:
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 構文を使用するだけです。