Kembali hasil jenis anonim?

194

Menggunakan contoh sederhana di bawah ini, apa cara terbaik untuk mengembalikan hasil dari beberapa tabel menggunakan Linq ke SQL?

Katakanlah saya punya dua tabel:

Dogs:   Name, Age, BreedId
Breeds: BreedId, BreedName

Saya ingin mengembalikan semua anjing dengan mereka BreedName. Saya harus membuat semua anjing menggunakan sesuatu seperti ini tanpa masalah:

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

Tetapi jika saya ingin anjing ras dan coba ini, saya punya masalah:

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

Sekarang saya menyadari bahwa kompiler tidak akan membiarkan saya mengembalikan satu set jenis anonim karena itu mengharapkan Anjing, tetapi apakah ada cara untuk mengembalikan ini tanpa harus membuat jenis kustom? Atau apakah saya harus membuat kelas sendiri untuk DogsWithBreedNamesdan menentukan jenis itu di pilih? Atau ada cara lain yang lebih mudah?

Jonathan S.
sumber
Karena penasaran, mengapa semua contoh Linq ditampilkan menggunakan tipe anonim, jika tidak berhasil. Misalnya, contoh ini tidakforeach (var cust in query) Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);
Hot Licks
@ Hot Licks - tabel Pelanggan dalam contoh tersebut adalah entitas yang diwakili oleh kelas. Contoh tidak muncul untuk menunjukkan definisi dari kelas-kelas tersebut.
Jonathan S.
Juga tidak memberitahu Anda bahwa swizzle compiler menggantikan "var" dengan nama kelas.
Hot Licks

Jawaban:

213

Saya cenderung memilih pola ini:

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

Ini berarti Anda memiliki kelas tambahan, tetapi cepat dan mudah dikodekan, mudah diperluas, dapat digunakan kembali, dan tipe-aman.

Teedyay
sumber
Saya suka pendekatan ini, tetapi sekarang saya tidak yakin bagaimana menampilkan nama anjing. Jika saya mengikat hasilnya ke DataGrid, dapatkah saya mendapatkan properti dari Dog tanpa mendefinisikannya secara eksplisit di kelas DogWithBreed atau apakah saya harus membuat pengambil / penyetel untuk setiap bidang yang ingin saya tampilkan?
Jonathan S.
4
Apakah DataGrids tidak memungkinkan Anda menentukan properti sebagai "Dog.Name"? Saya lupa sekarang mengapa saya cukup membenci mereka untuk tidak menggunakannya ...
teedyay
@ JonathanS. bagaimana Anda melakukan ini di kolom templat? tolong beri tahu saya dalam situasi yang sama
rahularyansharma
Hei, saya suka metode ini merangkum dua kelas menjadi satu. Itu membuatnya mudah untuk dikerjakan. Belum lagi membuat kelas sederhana untuk digunakan hanya dalam konteks saat ini membuatnya lebih bersih.
Linger
6
Ini benar-benar tidak menjawab pertanyaan yang dimiliki OP, "apakah ada cara untuk mengembalikan ini tanpa harus membuat jenis khusus"?
tjscience
69

Anda dapat mengembalikan jenis anonim, tetapi sebenarnya tidak cantik .

Dalam hal ini saya pikir akan jauh lebih baik untuk membuat tipe yang sesuai. Jika itu hanya akan digunakan dari dalam tipe yang berisi metode, buat itu menjadi tipe bersarang.

Secara pribadi saya ingin C # untuk mendapatkan "nama jenis anonim" - yaitu perilaku yang sama dengan jenis anonim, tetapi dengan nama dan deklarasi properti, tetapi hanya itu.

SUNTING: Yang lain menyarankan anjing yang kembali, dan kemudian mengakses nama trah melalui jalur properti dll. Itu pendekatan yang sangat masuk akal, tetapi IME itu mengarah ke situasi di mana Anda telah melakukan kueri dengan cara tertentu karena data yang ingin Anda gunakan - dan meta-informasi itu hilang ketika Anda baru saja kembali IEnumerable<Dog>- kueri mungkin mengharapkan Anda untuk menggunakan (katakanlah) Breeddaripada Ownerkarena beberapa opsi muat dll, tetapi jika Anda lupa itu dan mulai menggunakan properti lain, aplikasi Anda mungkin berfungsi tetapi tidak seefisien yang Anda bayangkan. Tentu saja, saya dapat berbicara tentang sampah, atau terlalu mengoptimalkan, dll ...

Jon Skeet
sumber
3
Hai, saya bukan orang yang tidak ingin fitur karena takut keluar dari cara mereka akan disalahgunakan, tetapi dapatkah Anda bayangkan jenis kode crufty yang akan kita lihat jika mereka mengizinkan jenis anonim untuk pingsan? (menggigil)
Dave Markle
19
Kami mungkin melihat beberapa penyalahgunaan. Kita mungkin juga melihat beberapa kode yang lebih sederhana di mana kita hanya ingin sebuah tuple, pada dasarnya. Tidak semuanya perlu menjadi objek dengan perilaku yang kompleks. Terkadang "hanya data" adalah Hal yang Benar. IMO, tentu saja.
Jon Skeet
1
Terima kasih, jadi preferensi Anda adalah untuk membuat jenis bahkan jika itu untuk tampilan satu kali seperti ini? Saya memiliki banyak laporan bahwa mengiris data yang sama dengan cara yang berbeda dan berharap untuk tidak harus membuat semua ini jenis (DogsWithBreeds, DogsWithOwnerNames, dll)
Jonathan S.
1
Saya mencoba untuk tidak perlu mengirisnya dalam banyak cara, atau meletakkan bagian pengiris di tempat yang membutuhkan data sehingga Anda dapat menggunakan jenis anonim - tetapi lebih dari itu, ya. Ini menyebalkan dalam beberapa hal, tapi begitulah hidup aku takut :(
Jon Skeet
17

Hanya untuk menambah nilai dua sen saya :-) Baru-baru ini saya belajar cara menangani objek anonim. Ini hanya dapat digunakan ketika menargetkan kerangka .NET 4 dan itu hanya ketika menambahkan referensi ke System.Web.dll tetapi kemudian cukup sederhana:

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

Untuk dapat menambahkan referensi ke System.Web.dll Anda harus mengikuti saran rushonerok : Pastikan kerangka kerja target [proyek] Anda adalah ".NET Framework 4" bukan ".NET Framework 4 Profil Klien".

Peter Perháč
sumber
2
ASP.NET Mvc School;)
T-moty
8

Tidak, Anda tidak dapat mengembalikan jenis anonim tanpa melalui beberapa tipu daya.

Jika Anda tidak menggunakan C #, apa yang akan Anda cari (mengembalikan beberapa data tanpa tipe konkret) disebut Tuple.

Ada banyak implementasi tuple C #, menggunakan yang ditunjukkan di sini , kode Anda akan berfungsi seperti ini.

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

Dan di situs panggilan:

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);
    }
}
Joshperry
sumber
7
Ini tidak bekerja. Melempar NotSupportedException : Hanya konstruktor dan penginisialisasi tanpa parameter yang didukung di LINQ ke Entitas
mshsayem
1
Benar, kelas Tuple tidak memiliki konstruktor default dan tidak akan berfungsi dengan benar dengan LINQ untuk Entitas dengan cara ini. Namun itu berfungsi dengan baik dengan LINQ to SQL seperti dalam pertanyaan. Saya belum mencoba ini, tetapi mungkin berhasil ... select Tuple.Create(d, b).
Joshperry
1
Karena Tuples tidak didukung oleh beberapa penyedia LINQ, tidak bisakah Anda memilih jenis anonim, mengubahnya menjadi IEnumerable, lalu pilih tuple dari itu?
TehPers
8

Anda dapat melakukan sesuatu seperti ini:


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

Anda harus menggunakan ToList()metode terlebih dahulu untuk mengambil baris dari database dan kemudian memilih item sebagai kelas. Coba ini:

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

Jadi, triknya adalah yang pertamaToList() . Segera membuat permintaan dan mendapatkan data dari database. Trik kedua adalah Memilih item dan menggunakan penginisialisasi objek untuk menghasilkan objek baru dengan item dimuat.

Semoga ini membantu.

Hakan KOSE
sumber
8

Di C # 7 sekarang Anda dapat menggunakan tuple! ... yang menghilangkan kebutuhan untuk membuat kelas hanya untuk mengembalikan hasilnya.

Berikut ini contoh kode:

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

Anda mungkin perlu menginstal paket nuget System.ValueTuple.

Rosdi Kasim
sumber
4

Sekarang saya menyadari bahwa kompiler tidak akan membiarkan saya mengembalikan satu set jenis anonim karena itu mengharapkan Anjing, tetapi apakah ada cara untuk mengembalikan ini tanpa harus membuat jenis kustom?

Gunakan objek gunakan untuk mengembalikan daftar jenis Anonim tanpa membuat jenis kustom. Ini akan berfungsi tanpa kesalahan kompiler (dalam .net 4.0). Saya mengembalikan daftar ke klien dan menguraikannya di 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;
}
Sergey
sumber
1
Menurut pendapat saya itu akan lebih benar dan dapat dibaca jika tanda tangan metode Anda terlihat seperti ini: public IEnumerable <object> GetDogsWithBreedNames ()
pistol-pete
3

Cukup pilih anjing, lalu gunakan dog.Breed.BreedName , ini akan bekerja dengan baik.

Jika Anda memiliki banyak anjing, gunakan DataLoadOptions.LoadWith untuk mengurangi jumlah panggilan db.

Andrey Shchekin
sumber
2

Anda tidak dapat mengembalikan tipe anonim secara langsung, tetapi Anda dapat memutarnya melalui metode generik Anda. Begitu juga sebagian besar metode ekstensi LINQ. Tidak ada sihir di sana, sementara sepertinya mereka akan mengembalikan jenis anonim. Jika parameter anonim, hasilnya juga bisa anonim.

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

Di bawah ini adalah contoh berdasarkan kode dari pertanyaan asli:

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;
}
George Mamaladze
sumber
0

Nah, jika Anda mengembalikan Anjing, Anda akan melakukannya:

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

Jika Anda ingin Breed bersemangat dan tidak malas, cukup gunakan konstruksi DataLoadOptions yang sesuai .

Dave Markle
sumber
0

BreedIddalam Dogtabel jelas merupakan kunci asing ke baris yang sesuai dalam Breedtabel. Jika database Anda sudah diatur dengan benar, LINQ ke SQL akan secara otomatis membuat hubungan antara dua tabel. Kelas Dog yang dihasilkan akan memiliki properti Breed, dan kelas Breed harus memiliki koleksi Anjing. Menyetelnya dengan cara ini, Anda masih bisa kembali IEnumerable<Dog>, yang merupakan objek yang menyertakan properti berkembang biak. Satu-satunya peringatan adalah bahwa Anda perlu memuat objek berkembang biak bersama dengan objek anjing di kueri sehingga mereka dapat diakses setelah konteks data telah dibuang, dan (seperti yang disarankan poster lain) jalankan metode pada koleksi yang akan menyebabkan permintaan yang harus segera dilakukan (ToArray dalam kasus ini):

public IEnumerable<Dog> GetDogs()
{
    using (var db = new DogDataContext(ConnectString))
    {
        db.LoadOptions.LoadWith<Dog>(i => i.Breed);
        return db.Dogs.ToArray();
    }

}

Maka sepele untuk mengakses jenis untuk setiap anjing:

foreach (var dog in GetDogs())
{
    Console.WriteLine("Dog's Name: {0}", dog.Name);
    Console.WriteLine("Dog's Breed: {0}", dog.Breed.Name);        
}
kad81
sumber
0

Jika ide utamanya adalah membuat pernyataan pilih SQL yang dikirim ke server Database hanya memiliki bidang yang diperlukan, dan tidak semua bidang Entitas, maka Anda dapat melakukan ini:

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;
        }
    }
}
Pembaca Man San
sumber
0

Coba ini untuk mendapatkan data dinamis. Anda dapat mengonversi kode untuk Daftar <>

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);
Yargicx
sumber
0

Jika Anda memiliki pengaturan hubungan di basis data Anda dengan pengekangan kunci asing di BreedId, apakah Anda belum mendapatkannya?

Pemetaan hubungan DBML

Jadi sekarang saya dapat menelepon:

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

Dan dalam kode yang memanggil itu:

var album = GetAlbum(1);

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

Jadi, dalam contoh Anda, Anda akan memanggil sesuatu seperti anjing.

Seperti yang disebutkan orang lain, DataLoadOptions akan membantu mengurangi panggilan database jika itu masalah.

Zhaph - Ben Duguid
sumber
0

Ini tidak persis menjawab pertanyaan Anda, tetapi Google membawa saya ke sini berdasarkan kata kunci. Ini adalah bagaimana Anda dapat meminta jenis anonim dari daftar:

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