ส่งคืนผลลัพธ์ประเภทที่ไม่ระบุชื่อหรือไม่


194

การใช้ตัวอย่างง่าย ๆ ด้านล่างวิธีที่ดีที่สุดในการส่งคืนผลลัพธ์จากหลายตารางโดยใช้ Linq เป็น 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และระบุประเภทนั้นในตัวเลือก? หรือมีวิธีอื่นที่ง่ายกว่านี้ไหม?


เพียงแค่อยากรู้อยากเห็นทำไมตัวอย่าง Linq ทั้งหมดแสดงโดยใช้ประเภทที่ไม่ระบุชื่อหากพวกเขาไม่ทำงาน เช่นตัวอย่างนี้foreach (var cust in query) Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);
Hot Licks

@ Hot Licks - ตารางลูกค้าในตัวอย่างเหล่านั้นเป็นเอนทิตีที่แสดงโดยคลาส ตัวอย่างไม่ปรากฏขึ้นเพื่อแสดงคำจำกัดความของคลาสเหล่านั้น
Jonathan S.

มันไม่ได้บอกคุณว่าคอมไพเลอร์ swizzle กำลังแทนที่ "var" ด้วยชื่อคลาส
Hot Licks

คำตอบ:


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 ฉันจะได้รับคุณสมบัติจาก Dog โดยไม่ต้องกำหนดอย่างชัดเจนในคลาส DogWithBreed หรือฉันต้องสร้าง getter / setter สำหรับแต่ละฟิลด์ที่ฉันต้องการแสดงหรือไม่
โจนาธานเอส

4
DataGrids ไม่อนุญาตให้คุณระบุคุณสมบัติเป็น "Dog.Name" หรือไม่? ฉันลืมว่าทำไมตอนนี้ฉันเกลียดพวกเขาไม่เคยพอที่จะใช้พวกเขา ...
teedyay

@JonathanS คุณทำแบบนี้ในคอลัมน์เทมเพลตได้อย่างไรโปรดบอกฉันว่าฉันอยู่ในสถานการณ์ที่คล้ายคลึงกัน
rahularyansharma

เฮ้ฉันชอบวิธีนี้ในการห่อหุ้มสองคลาสให้เป็นหนึ่งเดียว มันทำให้ง่ายต่อการทำงานกับ ไม่ต้องพูดถึงการสร้างคลาสง่าย ๆ ที่จะใช้เฉพาะในบริบทปัจจุบันจะทำให้มันสะอาดขึ้น
อิทธิพล

6
นี่ไม่ได้ตอบคำถามที่ OP มี "มีวิธีคืนสินค้าโดยไม่ต้องสร้างประเภทกำหนดเอง" หรือไม่?
tjscience

69

คุณสามารถกลับมาชนิดที่ไม่ระบุชื่อแต่มันคือเรื่องจริงไม่สวย

ในกรณีนี้ฉันคิดว่ามันจะดีกว่าที่จะสร้างประเภทที่เหมาะสม หากเป็นเพียงจะใช้จากภายในประเภทที่มีวิธีการทำให้เป็นประเภทซ้อนกัน

โดยส่วนตัวฉันต้องการ C # เพื่อรับ "ชื่อประเภทไม่ระบุชื่อ" - นั่นคือพฤติกรรมเช่นเดียวกับประเภทที่ไม่ระบุชื่อ แต่มีชื่อและการประกาศคุณสมบัติ แต่นั่นคือมัน

แก้ไข: คนอื่น ๆ แนะนำให้สุนัขที่กลับมาแล้วเข้าถึงชื่อสายพันธุ์ผ่านเส้นทางของทรัพย์สิน ฯลฯ ซึ่งเป็นวิธีการที่เหมาะสมอย่างสมบูรณ์ แต่ IME นำไปสู่สถานการณ์ที่คุณทำแบบสอบถามด้วยวิธีเฉพาะเนื่องจากข้อมูลที่คุณต้องการ การใช้ - และข้อมูลเมตานั้นหายไปเมื่อคุณเพิ่งกลับมาIEnumerable<Dog>- การค้นหาอาจคาดหวังให้คุณใช้ (พูด) Breedแทนที่จะOwnerใช้ตัวเลือกในการโหลดเป็นต้น แต่ถ้าคุณลืมและเริ่มใช้คุณสมบัติอื่น ๆ แอปของคุณอาจทำงานได้ ไม่ได้มีประสิทธิภาพอย่างที่คุณคิดไว้ตั้งแต่แรก แน่นอนฉันอาจจะพูดถึงขยะหรือเพิ่มประสิทธิภาพมากเกินไป ฯลฯ ...


3
เฮ้ฉันไม่ใช่คนที่ไม่ต้องการฟีเจอร์เพราะกลัวว่าพวกเขาจะถูกทารุณกรรม แต่คุณสามารถจินตนาการรหัส crufty ชนิดที่เราเห็นว่าพวกเขาอนุญาตให้ส่งชื่อประเภทที่ไม่ระบุชื่อหรือไม่ (ตัวสั่น)
Dave Markle

19
เราอาจเห็นการละเมิด เราอาจเห็นโค้ดที่ง่ายกว่าซึ่งเราต้องการให้ tuple โดยทั่วไป ไม่ใช่ทุกสิ่งที่จะต้องเป็นวัตถุที่มีพฤติกรรมที่ซับซ้อน บางครั้ง "แค่ข้อมูล" เป็นสิ่งที่ถูกต้อง IMO แน่นอน
Jon Skeet

1
ขอบคุณดังนั้นการตั้งค่าของคุณคือการสร้างประเภทแม้ว่าจะเป็นมุมมองแบบครั้งเดียวเช่นนี้ ฉันมีรายงานจำนวนมากที่เชือดข้อมูลเดียวกันด้วยวิธีที่แตกต่างกันและหวังว่าจะไม่ต้องสร้างประเภทที่แตกต่างเหล่านี้ทั้งหมด (DogsWithBreeds, DogsWithOwnerNames, ฯลฯ )
Jonathan S.

1
ฉันพยายามที่จะไม่ต้องฝานมันในหลาย ๆ ทางหรือใส่ส่วนแบ่งในสถานที่ที่ต้องการข้อมูลเพื่อให้คุณสามารถใช้ประเภทที่ไม่ระบุชื่อ - แต่นอกเหนือจากนั้นใช่ มัน sucks ในบางวิธี แต่นั่นคือชีวิตฉันกลัว :(
จอนสกีต

17

เพียงเพิ่มมูลค่าสองเซ็นต์ของฉัน :-) ฉันเพิ่งเรียนรู้วิธีจัดการวัตถุที่ไม่ระบุชื่อ มันสามารถใช้ได้เมื่อกำหนดเป้าหมายกรอบงาน. 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 School;)
T-moty

8

ไม่คุณไม่สามารถส่งคืนประเภทนิรนามโดยไม่ต้องใช้เล่ห์เหลี่ยมบางอย่าง

หากคุณไม่ได้ใช้ C # สิ่งที่คุณต้องการ (ส่งคืนข้อมูลจำนวนมากโดยไม่มีประเภทที่เป็นรูปธรรม) เรียกว่า Tuple

มีการใช้งาน 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);
    }
}

7
สิ่งนี้ใช้ไม่ได้ โยนNotSupportedException : มีเพียงตัวสร้างและพารามิเตอร์แบบไม่มีพารามิเตอร์เท่านั้นที่ได้รับการสนับสนุนใน LINQ ไปยังเอนทิตี
mshsayem

1
True, Tuple class ไม่มี constructor เริ่มต้นและจะทำงานไม่ถูกต้องกับ LINQ to Entities ด้วยวิธีนี้ มันทำงานได้ดีกับ LINQ ไปยัง SQL เช่นเดียวกับคำถาม ฉันไม่ได้ลอง แต่อาจใช้งาน... select Tuple.Create(d, b)ได้
joshperry

1
เนื่องจาก Tuples ไม่ได้รับการสนับสนุนจากผู้ให้บริการ LINQ บางรายคุณไม่สามารถเลือกประเภทที่ไม่ระบุชื่อแปลงเป็น IEnumerable จากนั้นเลือก tuple จากนั้น
TehPers

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()มันทำให้แบบสอบถามทันทีและรับข้อมูลจากฐานข้อมูล เคล็ดลับที่สองคือการเลือกรายการและใช้วัตถุเริ่มต้นเพื่อสร้างวัตถุใหม่ที่มีรายการโหลด

หวังว่านี่จะช่วยได้


8

ใน C # 7 ตอนนี้คุณสามารถใช้ tuples! ... ซึ่งไม่จำเป็นต้องสร้างคลาสเพียงเพื่อส่งคืนผลลัพธ์

นี่คือตัวอย่างรหัส:

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 แต่มีวิธีที่จะคืนค่านี้โดยไม่ต้องสร้างประเภทที่กำหนดเองหรือไม่

ใช้วัตถุใช้เพื่อส่งกลับรายการประเภทไม่ระบุชื่อโดยไม่ต้องสร้างประเภทที่กำหนดเอง สิ่งนี้จะทำงานได้โดยไม่มีข้อผิดพลาดของคอมไพเลอร์ (ใน. 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
ในความคิดของฉันมันจะถูกต้องมากขึ้นและสามารถอ่านได้ถ้าลายเซ็นวิธีการของคุณมีลักษณะเช่นนี้: สาธารณะ 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

BreedIdในDogตารางจะเห็นได้ชัดว่าเป็น foreign key ไปยังแถวที่เกี่ยวข้องในBreedตาราง หากคุณตั้งค่าฐานข้อมูลไว้อย่างถูกต้อง LINQ กับ SQL ควรสร้างการเชื่อมโยงระหว่างสองตารางโดยอัตโนมัติ คลาส Dog ที่เกิดขึ้นจะมีคุณสมบัติสายพันธุ์และคลาส Breed ควรมีชุดสุนัข การตั้งค่าด้วยวิธีนี้คุณยังคงสามารถกลับมา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);        
}

0

หากแนวคิดหลักคือการทำให้คำสั่งการเลือก SQL ที่ส่งไปยังเซิร์ฟเวอร์ฐานข้อมูลมีเพียงฟิลด์ที่จำเป็นและไม่ใช่ฟิลด์เอนทิตีทั้งหมดดังนั้นคุณสามารถทำสิ่งนี้ได้:

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

ลองใช้วิธีนี้เพื่อรับข้อมูลแบบไดนามิก คุณสามารถแปลงรหัสสำหรับรายการ <>

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

หากคุณมีการตั้งค่าความสัมพันธ์ในฐานข้อมูลของคุณด้วยการ จำกัด คีย์ foriegn บน 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.