Anonim tür sonuçlar döndürülsün mü?


194

Aşağıdaki basit örneği kullanarak, Linq kullanarak SQL'e birden çok tablodan sonuç döndürmenin en iyi yolu nedir?

Diyelim ki iki masam var:

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

Ben tüm köpekleri ile geri dönmek istiyorum BreedName. Tüm köpekleri böyle bir şey kullanarak sorunsuz bir şekilde almalıyım:

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

Ama ırklı köpekler istiyor ve bunu deniyorsam sorun yaşıyorum:

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

Şimdi, derleyicinin köpekleri beklediğinden bir dizi anonim tür döndürmeme izin vermediğini, ancak özel bir tür oluşturmak zorunda kalmadan bunu döndürmenin bir yolu var mı? Yoksa seçim için kendi sınıfımı oluşturmak DogsWithBreedNamesve bu türü belirtmek zorunda mıyım? Yoksa daha kolay bir yol var mı?


Merakın dışında, tüm Linq örnekleri işe yaramazsa neden anonim türler kullanarak gösteriliyor? Örneğin, bu örnek yaparforeach (var cust in query) Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);
Sıcak Licks

@Hot Licks - bu örneklerde yer alan Müşteri tablosu, bir sınıf tarafından temsil edilen bir varlıktır. Örnek sadece bu sınıfların tanımlarını göstermiyor gibi görünüyor.
Jonathan

Ayrıca derleyici swizzle "var" yerine sınıf adını değiştirdiğini de söylemez.
Hot Licks

Yanıtlar:


213

Bu model için gitme eğilimindeyim:

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

Ekstra bir sınıfınız olduğu anlamına gelir, ancak kodlanması hızlı ve kolaydır, kolayca genişletilebilir, tekrar kullanılabilir ve tipte güvenlidir.


Bu yaklaşımı seviyorum ama şimdi köpeğin adını nasıl göstereceğinden emin değilim. Sonucu bir DataGrid'e bağlıyorsam, DogWithBreed sınıfında bunları açıkça tanımlamadan Dog'dan özellikleri alabilir miyim veya görüntülemek istediğim her alan için alıcı / ayarlayıcı oluşturmam gerekir mi?
Jonathan S.

4
DataGrids özelliği "Dog.Name" olarak belirtmenize izin vermiyor mu? Şimdi neden onları asla kullanmak için yeterince nefret ediyorum unutuyorum ...
teedyay

@JonathanS. bunu şablon sütununda nasıl yaptın? lütfen bana benzer durumda olduğumu söyle
rahularyansharma

Hey, bu iki sınıfı bire birleştirme yöntemini seviyorum. Çalışmayı kolaylaştırır. Sadece mevcut bağlamda kullanılacak basit sınıflar oluşturmayı bahsetmemek, onu daha temiz hale getirir.
Linger

6
Bu gerçekten OP'nin "özel bir tür oluşturmak zorunda kalmadan bunu döndürmenin bir yolu var mı?" Sorusuna cevap vermiyor mu?
tjscience

69

Sen edebilirsiniz anonim dönüş türünde ama gerçekten hoş değil .

Bu durumda uygun türü oluşturmak çok daha iyi olacağını düşünüyorum. Yalnızca yöntemi içeren türün içinden kullanılacaksa, bunu iç içe bir tür yapın.

Şahsen C # "anonim türleri" almak istiyorum - yani anonim türleri ile aynı davranış, ancak isimleri ve özellik bildirimleri ile, ama bu kadar.

DÜZENLEME: Diğerleri köpekleri geri döndürmeyi ve daha sonra bir mülkiyet yolu vb yoluyla cins adına erişmeyi öneriyor. Kullanım - sadece döndüğünüzde ve bu meta-bilgileri kaybolur IEnumerable<Dog>- sorgu olabilir bekliyor Eğer (söz) kullanımı Breedyerine Ownervb bazı yük seçenekleri nedeniyle, ancak unutma ve diğer özellikleri kullanmaya başlamak, uygulamanız çalışabilir ancak başlangıçta öngördüğünüz kadar verimli değil. Tabii ki, çöp veya aşırı optimizasyon vb.


3
Hey, istismar edileceklerinden korkma nedeniyle özellik istemeyecek biri değilim, ama isimsiz türlerin geçmesine izin verdiklerinde görebileceğimiz acımasız kod türlerini hayal edebiliyor musunuz? (titreme)
Dave Markle

19
Bazı istismarlar görebiliriz. Temelde sadece bir demet istediğimiz daha basit bir kod da görebiliriz. Her şeyin karmaşık davranışlara sahip bir nesne olması gerekmez. Bazen "sadece veriler" Doğru Şeydir. IMO, elbette.
Jon Skeet

1
Teşekkürler, yani tercihiniz böyle bir kerelik bir görünüm için olsa bile türler oluşturmak mı? Ben farklı şekillerde aynı bölümlemek raporların çok şey var ve bu farklı türleri (DogsWithBreeds, DogsWithOwnerNames, vs.) oluşturmak zorunda değil umuyordum
Jonathan S.

1
Ben o kadar çok yönden dilim ihtiyacına değil deneyin veya bu nedenle veri ihtiyacı yerine dilimleme bölümünü koyardım olabilir anonim türleri kullanmak - ama bunun ötesinde, evet. Bazı yönlerden berbat, ama korkarım hayat böyle :(
Jon Skeet

17

Sadece iki sent değerinde eklemek için :-) Anonim nesneleri işlemek için bir yol öğrendim. Yalnızca .NET 4 çerçevesini hedeflerken ve yalnızca System.Web.dll dosyasına bir başvuru eklerken kullanılabilir, ancak oldukça basittir:

...
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 dosyasına bir referans eklemek için rushonerok'un tavsiyelerine uymanız gerekir : [Projenin] hedef çerçevenizin ".NET Framework 4 İstemci Profili" değil, ".NET Framework 4" olduğundan emin olun.


2
ASP.NET Mvc Okulu;)
T-moty 15:05

8

Hayır, bazı hile yapmadan anonim türleri geri getiremezsiniz.

C # kullanmıyorsanız, aradığınız şeye (somut tip olmadan birden fazla veri döndürmek) Tuple denir.

Burada gösterileni kullanarak bir sürü C # demet uygulaması vardır, kodunuz böyle çalışır.

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

Ve arayan sitede:

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
Bu çalışmıyor. NotSupportedException Özel
Durumunu

1
Doğru, Tuple sınıfının varsayılan bir yapıcısı yoktur ve bu şekilde LINQ to Entities ile düzgün çalışmaz. Ancak soruda olduğu gibi LINQ to SQL ile iyi çalışır. Bunu denemedim, ama işe yarayabilir ... select Tuple.Create(d, b).
joshperry

1
Tuples bazı LINQ sağlayıcıları tarafından desteklenmediğinden, anonim bir tür seçip IEnumerable'a dönüştürüp ardından bir demet seçemediniz mi?
TehPers

8

Böyle bir şey yapabilirsiniz:


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()Veritabanından satır almak için önce yöntemi kullanmanız ve ardından öğeleri sınıf olarak seçmeniz gerekir. Bunu dene:

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

Yani, ilk önceToList() numara . Hemen sorguyu yapar ve verileri veritabanından alır. İkinci hile öğeleri seçmek ve yüklü öğeler ile yeni nesneler oluşturmak için nesne başlatıcı kullanmaktır .

Bu yardımcı olur umarım.


8

C # 7'de artık sadece sonuç döndürmek için bir sınıf oluşturma ihtiyacını ortadan kaldıran tuples! ... kullanabilirsiniz.

İşte bir örnek kod:

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

Yine de System.ValueTuple nuget paketini yüklemeniz gerekebilir.


4

Şimdi, derleyicinin köpekleri beklediğinden bir dizi anonim tür döndürmeme izin vermediğini, ancak özel bir tür oluşturmak zorunda kalmadan bunu döndürmenin bir yolu var mı?

Özel bir tür oluşturmadan Anonim türlerin bir listesini döndürmek için use nesnesini kullanın . Bu derleyici hatası olmadan çalışacaktır (.net 4.0'da). Listeyi istemciye döndürdüm ve ardından JavaScript'te ayrıştırdım:

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
Bence, yöntem imzanız şöyle görünüyorsa daha doğru ve okunaklı olurdu: public IEnumerable <object> GetDogsWithBreedNames ()
pistol-pete

3

Sadece köpekleri seçin, sonra kullanın dog.Breed.BreedName, bu iyi çalışmalıdır.

Çok fazla köpek varsa, db çağrı sayısını azaltmak için DataLoadOptions.LoadWith kullanın.


2

Anonim türleri doğrudan döndüremezsiniz, ancak bunları genel yönteminizle döngüye alabilirsiniz. LINQ genişletme yöntemlerinin çoğu da öyle. Orada sihir yok, görünüşe göre anonim türleri geri getireceklerdi. Parametre anonim ise sonuç da anonim olabilir.

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

Orijinal sorunun koduna dayanan bir örnek aşağıdadır:

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

Eğer Köpekleri iade ediyorsanız, şunları yapardınız:

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

Cinsin istekli olarak yüklenmesini ve tembel olarak yüklenmesini istemiyorsanız, uygun DataLoadOptions yapısını kullanın .


0

BreedIdiçerisinde Dogtablo açıkça karşılık gelen satıra yabancı anahtar Breedtablo. Veritabanınızı doğru şekilde ayarladıysanız, LINQ to SQL otomatik olarak iki tablo arasında bir ilişki oluşturmalıdır. Ortaya çıkan Köpek sınıfı bir Cins özelliğine sahip olacak ve Cins sınıfı bir Köpek koleksiyonuna sahip olmalıdır. Bu şekilde ayarlayarak IEnumerable<Dog>, breed özelliğini içeren bir nesne olan yine de geri dönebilirsiniz . Tek uyarı, cinsi nesneyi sorgudaki köpek nesneleriyle birlikte önceden yüklemeniz, böylece veri bağlamı atıldıktan sonra erişilebilmeleri ve (başka bir posterin önerdiği gibi) koleksiyonda hemen yapılacak sorgu (bu durumda ToArray):

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

}

Daha sonra her köpek için cinse erişmek önemsizdir:

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

0

Ana fikir, Veritabanı sunucusuna gönderilen SQL select deyiminin tüm Varlık alanlarına değil, yalnızca gerekli alanlara sahip olmasını sağlamaksa, bunu yapabilirsiniz:

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

Dinamik veri almak için bunu deneyin. Liste <> için kodu dönüştürebilirsiniz

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

Veritabanınızda BreedId'de bir yabancı anahtar kısıtlaması ile bir ilişki kurulumunuz varsa, bunu zaten almadınız mı?

DBML ilişki eşlemesi

Şimdi arayabilirim:

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

Ve bunu çağıran kodda:

var album = GetAlbum(1);

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

Yani örneğinizde dog.Breed.BreedName gibi bir şey çağırıyorsunuz - dediğim gibi bu, veritabanınızın bu ilişkilerle kurulmasına dayanıyor.

Diğerlerinin de belirttiği gibi, DataLoadOptions bir sorun olduğunda veritabanı çağrılarını azaltmaya yardımcı olacaktır.


0

Bu, sorunuza tam olarak cevap vermiyor, ancak Google beni anahtar kelimelere göre buraya getirdi. Listeden anonim bir türü şu şekilde sorgulayabilirsiniz:

var anon = model.MyType.Select(x => new { x.Item1, x.Item2});
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.