匿名型の結果を返しますか?


194

以下の簡単な例を使用して、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そのタイプを選択で指定する必要がありますか?または別の簡単な方法はありますか?


好奇心から、なぜ機能しない場合でも、すべてのLinqの例で匿名型の使用が示されるのはなぜですか。たとえば、この例で次のようになりますforeach (var cust in query) Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);
Hot Licks 2014

@Hot Licks-これらの例のCustomerテーブルは、クラスによって表されるエンティティです。この例は、これらのクラスの定義を示していないようです。
ジョナサンS.

また、コンパイラのスウィズルによって「var」がクラス名に置き換えられていることもわかりません。
ホットリックス14

回答:


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から取得できますか、または表示するフィールドごとにゲッター/セッターを作成する必要がありますか?
Jonathan S.

4
DataGridsでは、プロパティを "Dog.Name"として指定できませんか?なぜ私はそれらを使用しないように十分に嫌うのか今は忘れます...
teedyay

@JonathanS。テンプレート列でこれをどのように行いましたか?私は同じような状況にあると教えてください
rahularyansharma

ねえ、私はこの2つのクラスを1つにカプセル化する方法が好きです。作業が簡単になります。言うまでもなく、現在のコンテキストでのみ使用される単純なクラスを作成すると、よりクリーンになります。
Linger

6
これは、OPが持っていた「カスタムタイプを作成せずにこれを返す方法はありますか」という質問には実際には答えません。
tjscience 2013年

69

匿名型返すことはできますが、実際にはそれほどきれいではありません

この場合、適切なタイプを作成する方がはるかに良いと思います。メソッドを含む型内からのみ使用する場合は、ネストされた型にします。

個人的には、C#で「名前付きの匿名型」を取得したいと考えています。つまり、匿名型と同じ動作ですが、名前とプロパティ宣言がありますが、それだけです。

編集:他の人は犬を返すことを提案し、次にプロパティパスなどを介して品種名にアクセスします。これは完全に合理的なアプローチですが、IMEは、必要なデータのために特定の方法でクエリを実行した状況につながります使用-そして返すだけでそのメタ情報は失われますIEnumerable<Dog>-クエリはいくつかのロードオプションなどのためではなく使用することを期待しているかもしれませんが、それを忘れて他のプロパティの使用を開始すると、アプリは動作するかもしれませんが当初想定していたほど効率的ではありません。もちろん、私はゴミを話したり、過度に最適化したりすることもできます...BreedOwner


3
ねえ、私は機能が悪用されるのを恐れて機能を望まない人ではありませんが、名前付きの匿名型が渡されることを許可した場合に、私たちが見るであろう種類の粗悪なコードを想像できますか?(震え)
デイブ・マークル

19
虐待が見られるかもしれません。また、基本的には、タプルが必要なだけの非常に単純なコードも表示される場合があります。すべてが複雑な動作を持つオブジェクトである必要はありません。時々「ただのデータ」は正しいものです。もちろんIMO。
Jon Skeet、

1
おかげで、このような1回限りのビューの場合でも、タイプを作成することが優先されますか?同じデータをさまざまな方法で分割する多くのレポートがあり、これらのさまざまなタイプ(DogsWithBreeds、DogsWithOwnerNamesなど)をすべて作成する必要がないことを望んでいました
Jonathan S.

1
私はそれを非常に多くの方法でスライスする必要がないようにしたり、データを必要とする場所にスライス部分を置いたりして匿名型使用できるようにします、それ以上にそうです。それはいくつかの方法で吸いますが、それは私が恐れている人生です:(
Jon Skeet

17

ちょうど私の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 Framework 4クライアントプロファイル」ではなく「.NET Framework 4」であることを確認してください。


2
ASP.NET Mvcスクール;)
T-moty

8

いいえ、いくつかの策略を行わずに匿名型を返すことはできません。

C#を使用していない場合、探しているもの(具体的な型なしで複数のデータを返す)はタプルと呼ばれます。

多くの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
これは動作しません。例外非サポート例外をのみパラメータなしのコンストラクタと初期化子は、エンティティへのLINQでサポートされている
mshsayem

1
True、Tupleクラスにはデフォルトのコンストラクターがなく、この方法ではLINQ to Entitiesで正しく機能しません。ただし、質問と同様に、LINQ to SQLでも問題なく機能します。私はこれを試していませんが、で動作する可能性があり... select Tuple.Create(d, b)ます。
joshperry 2016

1
タプルは一部のLINQプロバイダーでサポートされていないため、匿名型を選択し、それをIEnumerableに変換してから、その中からタプルを選択できませんでしたか?
TehPers 2017年

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()です。それはすぐにクエリを作成し、データベースからデータを取得します。2番目のトリックは、アイテムを選択し、オブジェクト初期化子使用して、アイテムが読み込まれた新しいオブジェクトを生成することです。

お役に立てれば。


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

3

犬を選択してを使用するだけでdog.Breed.BreedName、これで問題なく動作するはずです。

犬がたくさんいる場合は、DataLoadOptions.LoadWithを使用してdb呼び出しの数を減らします。


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

さて、あなたが犬を返すなら、あなたはそうするでしょう:

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構成体を使用してください


0

BreedIdDog表明らかに対応する行への外部キーであるBreedテーブル。データベースが適切に設定されている場合、LINQ to SQLは2つのテーブル間の関連付けを自動的に作成します。結果のDogクラスにはBreedプロパティがあり、BreedクラスにはDogsコレクションが必要です。このように設定しても、返すことができIEnumerable<Dog>ます。これは、breedプロパティを含むオブジェクトです。唯一の注意点は、データコンテキストが破棄された後にブリードオブジェクトにアクセスできるように、クエリ内の犬オブジェクトをプリロードする必要があることです(別の投稿者が示唆しているように)、すぐに実行するクエリ(この場合は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ステートメントに、すべてのエンティティフィールドではなく、必要なフィールドのみを含めることである場合、uはこれを実行できます。

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});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.