รองรับเฉพาะตัวสร้างและตัวเริ่มต้นที่ไม่มีพารามิเตอร์เท่านั้นใน LINQ ถึงเอนทิตี


132

ฉันมีข้อผิดพลาดนี้ในนิพจน์ linq นี้:

var naleznosci = (from nalTmp in db.Naleznosci
                              where nalTmp.idDziecko == idDziec
                              select new Payments
                              (
                                  nalTmp.Dziecko.Imie,
                                  nalTmp.Dziecko.Nazwisko,
                                  nalTmp.Miesiace.Nazwa,
                                  nalTmp.Kwota,
                                  nalTmp.RodzajeOplat.NazwaRodzajuOplaty,
                                  nalTmp.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
                                  nalTmp.DataRozliczenia,
                                  nalTmp.TerminPlatnosci
                              )).ToList();

มีความคิดอย่างไรในการแก้ปัญหานี้ ฉันลองใช้การแสดงออกร่วมกัน ... : /


1
คุณสามารถแสดงคลาส Payments ได้ไหม หรืออย่างน้อย ctor ถูกเรียกที่นี่และโดยเฉพาะอย่างยิ่งว่าการเรียก ctor 8 พารามิเตอร์นั้นสามารถสลับออกได้อย่างปลอดภัยสำหรับการเรียก ctor 0 พารามิเตอร์และการตั้งค่าคุณสมบัติ 8 บนวัตถุหรือไม่?
James Manning

23
ฉันได้รับข้อผิดพลาดเดียวกันนี้เมื่อใช้โครงสร้างแทนคลาสสำหรับวัตถุที่ฉันกำลัง "สร้างใหม่"
HuckIt

3
TL สิ่งที่ DR คือ EF-LINQ พยายามส่งคำสั่ง select ไปยังผู้ให้บริการ EF เช่น แปลงเป็น SQL หากต้องการออกจาก EF-LINQ ให้เรียก ToList () ก่อนสร้างวัตถุใด ๆ

คำตอบ:


127

หากไม่มีข้อมูลเพิ่มเติมเกี่ยวกับ "การชำระเงิน" สิ่งนี้ไม่ได้ช่วยอะไรมากนัก แต่สมมติว่าคุณต้องการสร้างออบเจ็กต์การชำระเงินและตั้งค่าคุณสมบัติบางอย่างตามค่าคอลัมน์:

var naleznosci = (from nalTmp in db.Naleznosci
                              where nalTmp.idDziecko == idDziec
                              select new Payments
                              {
                                  Imie = nalTmp.Dziecko.Imie,
                                  Nazwisko = nalTmp.Dziecko.Nazwisko,
                                  Nazwa= nalTmp.Miesiace.Nazwa,
                                  Kwota = nalTmp.Kwota,
                                  NazwaRodzajuOplaty = nalTmp.RodzajeOplat.NazwaRodzajuOplaty,
                                  NazwaTypuOplaty = nalTmp.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
                                  DataRozliczenia = nalTmp.DataRozliczenia,
                                  TerminPlatnosci = nalTmp.TerminPlatnosci,
                              }).ToList();

10
วิธีนี้ใช้งานได้ดีอย่าลืมเพิ่มตัวสร้างว่างสำหรับชั้นเรียน
live-love

58
เพียงเพื่อเพิ่มคำตอบนี้คุณไม่สามารถทำสิ่งนี้กับ Structs ได้มีเพียงคลาสเท่านั้น - เอาหน่อยสิ!
naspinski

4
ใช่ฉันคิดว่าคำตอบของโทนี่ดีกว่าคำตอบนี้เพราะมันช่วยแก้ปัญหาเฉพาะหน้าได้จริงในขณะที่ปัญหานี้หลีกเลี่ยงปัญหาโดยการเปลี่ยนลักษณะของคลาสการชำระเงินและอาจป้องกันไม่ให้ไม่เปลี่ยนรูป
Stephen Holt

มันดูน่าเกลียด af มีวิธีที่ดีกว่ากับ EF6 หรือไม่?
Toolkit

115

หากคุณยังคงต้องการที่จะใช้คอนสตรัคของคุณสำหรับการเริ่มต้นและไม่ได้คุณสมบัติ (บางครั้งพฤติกรรมนี้เป็นที่ต้องการสำหรับการเริ่มต้นวัตถุประสงค์) ระบุแบบสอบถามโดยการเรียกToList()หรือแล้วการใช้งานToArray() Select(…)ดังนั้นมันจะใช้ LINQ กับ Collections และข้อ จำกัด ของการไม่สามารถเรียกตัวสร้างที่มีพารามิเตอร์ในSelect(…)นั้นจะหายไป

ดังนั้นรหัสของคุณควรมีลักษณะดังนี้:

var naleznosci = db.Naleznosci
                          .Where(nalTmp => nalTmp.idDziecko == idDziec)
                          .ToList() // Here comes transfer to LINQ to Collections.
                          .Select(nalImp => new Payments
                              (
                                  nalTmp.Dziecko.Imie,
                                  nalTmp.Dziecko.Nazwisko,
                                  nalTmp.Miesiace.Nazwa,
                                  nalTmp.Kwota,
                                  nalTmp.RodzajeOplat.NazwaRodzajuOplaty,
                                  nalTmp.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
                                  nalTmp.DataRozliczenia,
                                  nalTmp.TerminPlatnosci
                              ))
                          .ToList();

21
เพียงเพื่อชี้แจงว่าเหตุใดจึงใช้งานได้ปัญหาของรหัสที่ระบุไว้ในตอนแรกคือ Entity Framework พยายามส่งการเรียกตัวสร้างผ่านไปยัง SQL พร้อมกับแบบสอบถาม LINQ ที่เหลือและแน่นอนว่าไม่มีวิธีใดที่ SQL จะดำเนินการสร้าง วัตถุที่ซับซ้อน! การแทรกการเรียก ToList () จะทำให้คุณย้าย enumerable จากแบบสอบถาม SQL ที่ยังไม่ได้ดำเนินการไปยังรายการวัตถุที่เป็นรูปธรรมในหน่วยความจำซึ่งคุณสามารถจัดการได้ตามต้องการ
Stephen Holt

19
อย่าใช้นี้ใช้ToX() AsEnumerable()
Rawling

1
.ToList () // นี่คือการโอน LINQ ไปยัง Collections เป็นบรรทัดที่ช่วยแก้ปัญหาให้ฉัน
ราม

15
โปรดทราบว่าสิ่งนี้จะเลือกคอลัมน์ทั้งหมดที่ระดับฐานข้อมูลโดยปกติจะเลือกคอลัมน์ที่ต้องการเท่านั้น
ฮิวจ์เจฟฟ์เนอร์

4
ไม่เพียงแค่นั้น แต่คุณอาจจะมีการแจงนับหลายรายการ ฉันไม่ชอบวิธีแก้ปัญหานี้
Bluebaron

47

หลังจากพบข้อผิดพลาดนี้ด้วยตัวเองฉันคิดว่าฉันจะเพิ่มว่าหากPaymentประเภทเป็น a structคุณจะพบข้อผิดพลาดเดียวกันเนื่องจากstructประเภทไม่รองรับตัวสร้างแบบไม่มีพารามิเตอร์

ในกรณีนั้นการแปลงPaymentเป็นคลาสและใช้ไวยากรณ์ของ object initializer จะช่วยแก้ปัญหาได้


วิธีนี้ช่วยแก้ปัญหาได้ จริงๆแล้วแบบสอบถามนี้ที่มีตัวเลือกโครงสร้างได้รับการสนับสนุนใน LINQ-2-SQL และเป็นปัญหาเมื่อคุณอัปเกรดเป็น EntityFramework
Tomas Kubes

ฉันเกลียดโครงสร้าง พวกเขาทำในสิ่งที่ฉันต้องการไม่สิ้นสุด
Simon_Weaver

สร้างDateTime(ซึ่งเป็นโครงสร้าง) ในแบบสอบถามของฉันซึ่งส่งผลให้เกิดข้อผิดพลาดเดียวกัน การแยกมันเป็นตัวแปรท้องถิ่นแก้ไขให้ฉัน ขอบคุณสำหรับคำใบ้โครงสร้าง
LuckyLikey

20

หากคุณเป็นเหมือนฉันและไม่ต้องการเติมคุณสมบัติของคุณสำหรับแต่ละแบบสอบถามที่คุณกำลังสร้างมีอีกวิธีหนึ่งในการแก้ปัญหานี้

var query = from orderDetail in context.OrderDetails
            join order in context.Orders on order.OrderId equals orderDetail.orderId
            select new { order, orderDetail };

ณ จุดนี้คุณมี IQueryable ที่มีวัตถุที่ไม่ระบุตัวตน หากคุณต้องการเติมข้อมูลวัตถุที่กำหนดเองของคุณด้วยตัวสร้างคุณสามารถทำสิ่งนี้ได้:

return query.ToList().Select(r => new OrderDetails(r.order, r.orderDetail));

ตอนนี้วัตถุที่กำหนดเองของคุณ (ซึ่งใช้สองวัตถุเป็นพารามิเตอร์) สามารถเติมคุณสมบัติของคุณได้ตามต้องการ


สิ่งนี้ได้ผลสำหรับฉันและเป็นทางออกที่สะอาดที่สุด ผู้ที่แนะนำให้กำจัดตัวสร้างและใช้ไวยากรณ์ของตัวเริ่มต้นจะต้องไม่มีตรรกะภายในตัวสร้าง นั่นเป็นครั้งเดียวที่ฉันพึ่งพาตัวสร้างเพื่อเติมคุณสมบัติให้กับวัตถุ ขอบคุณสำหรับการแชร์.
Bonez024

9

ก่อนอื่นฉันจะหลีกเลี่ยงการแก้ปัญหาด้วย

from ....
select new Payments
{
  Imie = nalTmp.Dziecko.Imie,
  ....
}

สิ่งนี้ต้องการตัวสร้างที่ว่างเปล่าและละเว้นการห่อหุ้มดังนั้นคุณจึงบอกว่าการชำระเงินใหม่ () เป็นการชำระเงินที่ถูกต้องโดยไม่มีข้อมูลใด ๆ แต่วัตถุนั้นจะต้องมีค่าอย่างน้อยแทนและอาจต้องมีช่องอื่น ๆ ที่จำเป็นขึ้นอยู่กับโดเมนของคุณ

ควรมีตัวสร้างสำหรับฟิลด์ที่จำเป็น แต่นำข้อมูลที่จำเป็นเท่านั้น:

from ....
select new
{
  Imie = nalTmp.Dziecko.Imie,
  Nazwisko = nalTmp.Dziecko.Nazwisko
  ....
}
.ToList() // Here comes transfer to LINQ to Collections.
.Select(nalImp => new Payments
 (
  nalTmp.Imie,//assume this is a required field
  ...........
  )
  {
     Nazwisko = nalTmp.Nazwisko //optional field
  })
.ToList();

นี่คือความชั่วร้ายที่น้อยกว่า
Chalky

ฉันชอบอะไรแบบนี้มากกว่า ฉันพยายามผูกเพื่อใช้ Tuple แต่ Tuple ไม่มีตัวสร้างพารามิเตอร์น้อยกว่า ฉันเติมข้อมูลวัตถุที่ไม่ระบุชื่อแล้วเลือก Tuple
Tchaps

หนึ่งในการโอบกอดการห่อหุ้มและโดเมน
inrandomwetrust

2

คุณสามารถลองทำเช่นเดียวกัน แต่ใช้วิธีการขยาย ผู้ให้บริการฐานข้อมูลใช้อะไร

var naleznosci = db.Naleznosci
                          .Where<TSource>(nalTmp => nalTmp.idDziecko == idDziec)
                          .Select<TSource, TResult>(
                             delegate(TSource nalTmp) { return new Payments
                             (
                                 nalTmp.Dziecko.Imie,
                                 nalTmp.Dziecko.Nazwisko,
                                 nalTmp.Miesiace.Nazwa,
                                 nalTmp.Kwota,
                                 nalTmp.RodzajeOplat.NazwaRodzajuOplaty,
                                 nalTmp.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
                                 nalTmp.DataRozliczenia,
                                 nalTmp.TerminPlatnosci
                             ); })
                          .ToList();

2

เพียงแค่ก่อนที่คำสั่ง .. ที่เกิดขึ้นจริงจะถูกบันทึกเป็นแบบสอบถามก็ยังไม่ได้ปฏิบัติตาม หลังจากโทรหาคุณกำลังเล่นกับวัตถุจากนั้นคุณสามารถใช้ตัวสร้างที่ไม่ใช่ค่าเริ่มต้นในแบบสอบถามได้ToList()DbSetSelectDbSetToList()

ไม่ใช่วิธีการใช้เวลาที่มีประสิทธิภาพสูงสุด แต่เป็นตัวเลือกสำหรับชุดเล็ก ๆ


1

ลองแบบนี้ดู ....

var naleznosci = (from nalTmp in db.Naleznosci
                              where nalTmp.idDziecko == idDziec
                              select new Payments()
                              {
                                  Dziecko.Imie,
                                  Dziecko.Nazwisko,
                                  Miesiace.Nazwa,
                                  Kwota,
                                  RodzajeOplat.NazwaRodzajuOplaty,
                                  RodzajeOplat.TypyOplat.NazwaTypuOplaty,
                                  DataRozliczenia,
                                  TerminPlatnosci
                              }).ToList();

สิ่งนี้จะสร้างออบเจ็กต์การชำระเงินของคุณใหม่โดยใช้ตัวสร้างแบบไม่มีพารามิเตอร์จากนั้นเริ่มต้นคุณสมบัติที่อยู่ในวงเล็บปีกกา { }


3
FYI ()ไม่จำเป็นต้องใช้ Payemnts ดังนั้นจึงสามารถ `` เลือกการชำระเงินใหม่ {// init values} ได้
PostMan

ตอนนี้ฉันมีข้อผิดพลาด: ไม่สามารถเริ่มต้นประเภท 'การชำระเงิน' ด้วยตัวเริ่มต้นการรวบรวมเนื่องจากไม่ได้ใช้ 'System.Collections.IEnumerable'
netmajor

ถูกต้อง - หากคุณกำลังสร้างประเภท anon (แทนที่จะเป็นอินสแตนซ์ของคลาส Payments) รหัสของ Muad จะใช้ได้เนื่องจากคุณสมบัติที่จะตั้งค่าจะเป็นชื่อของคุณสมบัติที่อ่านโดยปริยาย เนื่องจากเป็นคลาส 'จริง' คุณจึงต้องระบุคุณสมบัติที่จะตั้งค่าเป็นค่าต่างๆ
James Manning

1

นอกเหนือจากวิธีการดังกล่าวแล้วคุณยังสามารถแยกวิเคราะห์เป็นคอลเล็กชัน Enumerable ได้เช่น:

(from x in table
....
).AsEnumerable()
.Select(x => ...)

นอกจากนี้ยังมีประโยชน์เพิ่มเติมในการทำให้ชีวิตง่ายขึ้นเมื่อสร้างวัตถุที่ไม่ระบุตัวตนเช่นนี้:

 (from x in tableName
select x.obj)
.Where(x => x.id != null)
.AsEnumerable()
.Select(x => new {
   objectOne = new ObjectName(x.property1, x.property2),
   parentObj = x
})
.ToList();

อย่างไรก็ตามโปรดจำไว้ว่าการแยกวิเคราะห์คอลเล็กชันแบบ Enumerable จะดึงมันเข้าสู่หน่วยความจำดังนั้นจึงสามารถใช้ทรัพยากรได้มาก! ข้อควรระวังควรใช้ที่นี่


1

นอกจากนี้หากคุณต้องการใช้ตัวสร้างที่มีวัตถุหลายตัวเพื่อเริ่มต้นคุณอาจได้รับข้อผิดพลาดหาก Linq ไม่ส่งคืนค่า

ดังนั้นคุณอาจต้องการทำสิ่งนี้:

(from x in table_1
   join y in table_2
   on x.id equals y.id
   select new {
   val1 = x,
   val2 = y
})
.DefaultIfEmpty()
.ToList()
.Select(a => new Val_Constructor(a.val1 != null ? a.val1 : new Val_1_Constructor(),
                            a.val2 != null ? a.val2 : new Val_2_Constructor()))
.ToList();

1

ขออภัยที่ไปงานปาร์ตี้ช้า แต่หลังจากพบสิ่งนี้ฉันคิดว่าควรแชร์สิ่งนี้เนื่องจากเป็นการใช้งานที่สะอาดที่สุดเร็วที่สุดและประหยัดหน่วยความจำที่ฉันพบ

ปรับให้เข้ากับตัวอย่างของคุณคุณจะเขียนว่า:

public static IQueryable<Payments> ToPayments(this IQueryable<Naleznosci> source)
{
  Expression<Func<Naleznosci, Payments>> createPayments = naleznosci => new Payments
  {
    Imie = source.Dziecko.Imie,
    Nazwisko = source.Dziecko.Nazwisko,
    Nazwa= source.Miesiace.Nazwa,
    Kwota = source.Kwota,
    NazwaRodzajuOplaty = source.RodzajeOplat.NazwaRodzajuOplaty,
    NazwaTypuOplaty = source.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
    DataRozliczenia = source.DataRozliczenia,
    TerminPlatnosci = source.TerminPlatnosci,
  };

  return source.Select(createPayments);
}

ข้อดีใหญ่ ๆ ที่นี่ (ตามที่ Damien Guard ชี้ให้เห็นในความคิดเห็นที่ลิงค์) คือ:

  • ช่วยให้คุณปลอดภัยจากการใช้รูปแบบการเริ่มต้นในแต่ละเหตุการณ์
  • การใช้งานผ่านvar foo = createPayments(bar);เช่นเดียวกับการใช้งานผ่าน myIQueryable ToPayments () ได้

1

วันนี้ฉันมีปัญหาเดียวกันและวิธีแก้ปัญหาของฉันก็คล้ายกับที่ Yoda ระบุไว้ แต่ใช้ได้กับไวยากรณ์ที่คล่องแคล่วเท่านั้น

การปรับโซลูชันของฉันกับโค้ดของคุณ: ฉันเพิ่มวิธีการคงที่ต่อไปนี้ในคลาสอ็อบเจ็กต์

    /// <summary>
    /// use this instead of a parameritized constructor when you need support
    /// for LINQ to entities (fluent syntax only)
    /// </summary>
    /// <returns></returns>
    public static Func<Naleznosci, Payments> Initializer()
    {
        return n => new Payments
        {
             Imie = n.Dziecko.Imie,
             Nazwisko = n.Dziecko.Nazwisko,
             Nazwa = n.Miesiace.Nazwa,
             Kwota = n.Kwota,
             NazwaRodzajuOplaty = n.RodzajeOplat.NazwaRodzajuOplaty,
             NazwaTypuOplaty = n.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
             DataRozliczenia = n.DataRozliczenia,
             TerminPlatnosc = n.TerminPlatnosci
        };
    }

จากนั้นอัปเดตแบบสอบถามพื้นฐานดังต่อไปนี้:

var naleznosci = (from nalTmp in db.Naleznosci
    where nalTmp.idDziecko == idDziec
    select new Payments.Initializer());

สิ่งนี้เทียบเท่ากับวิธีการแก้ปัญหาของ James Manning โดยมีข้อได้เปรียบในการผลักดันการขยายตัวของการเริ่มต้นสมาชิกไปยัง Class / Data Transfer Object

หมายเหตุ: เดิมทีฉันใช้ชื่อที่สื่อความหมายมากกว่านี้ว่า "Initializer" แต่หลังจากตรวจสอบวิธีการใช้แล้วฉันพบว่า "ตัวเริ่มต้น" นั้นเพียงพอแล้ว (อย่างน้อยก็สำหรับวัตถุประสงค์ของฉัน)

หมายเหตุสุดท้าย:
หลังจากหาวิธีแก้ปัญหานี้แล้วตอนแรกฉันคิดว่ามันจะง่ายมากที่จะแบ่งปันรหัสเดียวกันและปรับให้เข้ากับไวยากรณ์ของ Query ด้วย ฉันไม่เชื่อว่าจะเป็นเช่นนั้นอีกต่อไป ฉันคิดว่าถ้าคุณต้องการใช้การสร้างชวเลขประเภทนี้คุณจะต้องมีวิธีการสำหรับแต่ละ (แบบสอบถามคล่องแคล่ว) อย่างคล่องแคล่วตามที่อธิบายไว้ข้างต้นซึ่งสามารถมีอยู่ในคลาสอ็อบเจ็กต์เอง

สำหรับไวยากรณ์การสืบค้นจำเป็นต้องใช้วิธีการขยาย (หรือวิธีการบางอย่างที่อยู่นอกคลาสพื้นฐานที่ใช้) (เนื่องจากไวยากรณ์การสืบค้นต้องการใช้งาน IQueryable แทน T)

นี่คือตัวอย่างของสิ่งที่ฉันใช้ในการทำให้สิ่งนี้ใช้ได้กับไวยากรณ์คิวรีในที่สุด (Yoda ตอกสิ่งนี้ไปแล้ว แต่ฉันคิดว่าการใช้งานน่าจะชัดเจนขึ้นเพราะฉันไม่เข้าใจในตอนแรก)

/// <summary>
/// use this instead of a parameritized constructor when you need support
/// for LINQ to entities (query syntax only)
/// </summary>
/// <returns></returns>
public static IQueryable<Payments> Initializer(this IQueryable<Naleznosci> source)
{
    return source.Select(
        n => new Payments
        {
            Imie = n.Dziecko.Imie,
            Nazwisko = n.Dziecko.Nazwisko,
            Nazwa = n.Miesiace.Nazwa,
            Kwota = n.Kwota,
            NazwaRodzajuOplaty = n.RodzajeOplat.NazwaRodzajuOplaty,
            NazwaTypuOplaty = n.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
            DataRozliczenia = n.DataRozliczenia,
            TerminPlatnosc = n.TerminPlatnosci
    };
}

และการใช้งาน

var naleznosci = (from nalTmp in db.Naleznosci
    where nalTmp.idDziecko == idDziec
    select nalTmp).Initializer().ToList();

เพิ่มส่วนที่เกี่ยวกับไวยากรณ์การสืบค้นเพื่อความสมบูรณ์เมื่อฉันรู้ว่าคำตอบเริ่มต้นของฉันขยายได้ไม่ดี anwer ของ @ yoda น่าจะดีกว่าในเรื่องไวยากรณ์การสืบค้น
wode

0

แม้ว่าจะตอบช้า แต่ก็ยังช่วยคนที่ตกทุกข์ได้ยาก เนื่องจาก LINQ ถึงเอนทิตีไม่สนับสนุนการสร้างอ็อบเจ็กต์ที่ไม่มีพารามิเตอร์ อย่างไรก็ตามวิธีการประมาณการIEnumerable

ดังนั้นก่อนที่จะเลือกเพียงแค่แปลงIQueryableของคุณเป็นIEnumerableโดยใช้รหัสนี้:

var result = myContext.SomeModelClass.AsEnumerable().Select(m => m.ToString());

มันจะทำงานได้ดี อย่างไรก็ตามแน่นอนว่าจะทำให้ประโยชน์ของการสืบค้นแบบเนทีฟขาดหายไป


0
IQueryable<SqlResult> naleznosci = (from nalTmp in db.Naleznosci
                              where nalTmp.idDziecko == idDziec
                              select new Payments
                              {
                                  Imie = nalTmp.Dziecko.Imie,
                                  Nazwisko = nalTmp.Dziecko.Nazwisko,
                                  Nazwa= nalTmp.Miesiace.Nazwa,
                                  Kwota = nalTmp.Kwota,
                                  NazwaRodzajuOplaty =                          nalTmp.RodzajeOplat.NazwaRodzajuOplaty,
                              NazwaTypuOplaty = nalTmp.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
                              DataRozliczenia = nalTmp.DataRozliczenia,
                              TerminPlatnosci = nalTmp.TerminPlatnosci,
                          });
Repeater1.DataSource  = naleznosci.ToList(); 
Repeater1.DataBind();


public class SqlResult
{
        public string Imie { get; set; }
        public string Nazwisko { get; set; }
        ...
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.