'มี ()' วิธีแก้ปัญหาโดยใช้ Linq ไปยังเอนทิตี?


86

ฉันกำลังพยายามสร้างแบบสอบถามที่ใช้รายการรหัสในส่วนคำสั่ง where โดยใช้ API ไคลเอนต์ Silverlight ADO.Net Data Services (และดังนั้น Linq To Entities) ไม่มีใครรู้วิธีแก้ปัญหาสำหรับการประกอบด้วยที่ไม่รองรับ?

ฉันต้องการทำสิ่งนี้:

List<long?> txnIds = new List<long?>();
// Fill list 

var q = from t in svc.OpenTransaction
        where txnIds.Contains(t.OpenTransactionId)
        select t;

ลองสิ่งนี้:

var q = from t in svc.OpenTransaction
where txnIds.Any<long>(tt => tt == t.OpenTransactionId)
select t;

แต่ได้รับ "ไม่รองรับวิธีการ" ใด ๆ "


36
หมายเหตุ: Entity Framework 4 (ใน. NET 4) มีเมธอด "มี" ในกรณีที่มีคนอ่านสิ่งนี้ซึ่งไม่รู้เกี่ยวกับมัน ฉันรู้ว่า OP ใช้ EF1 (.NET 3.5)
DarrellNorton

7
@Darrell ฉันเสียเวลาไปครึ่งชั่วโมงเพราะข้ามความคิดเห็นของคุณไป ฉันหวังว่าฉันจะทำให้ความคิดเห็นของคุณกระพริบและกระโจมผ่านหน้าจอ
Chris Dwyer

คำตอบ:


97

อัปเดต: EF ≥ 4 รองรับContainsโดยตรง (ชำระเงินAny) ดังนั้นคุณไม่จำเป็นต้องมีวิธีแก้ปัญหาใด ๆ

public static IQueryable<TEntity> WhereIn<TEntity, TValue>
  (
    this ObjectQuery<TEntity> query,
    Expression<Func<TEntity, TValue>> selector,
    IEnumerable<TValue> collection
  )
{
  if (selector == null) throw new ArgumentNullException("selector");
  if (collection == null) throw new ArgumentNullException("collection");
  if (!collection.Any()) 
    return query.Where(t => false);

  ParameterExpression p = selector.Parameters.Single();

  IEnumerable<Expression> equals = collection.Select(value =>
     (Expression)Expression.Equal(selector.Body,
          Expression.Constant(value, typeof(TValue))));

  Expression body = equals.Aggregate((accumulate, equal) =>
      Expression.Or(accumulate, equal));

  return query.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
}

//Optional - to allow static collection:
public static IQueryable<TEntity> WhereIn<TEntity, TValue>
  (
    this ObjectQuery<TEntity> query,
    Expression<Func<TEntity, TValue>> selector,
    params TValue[] collection
  )
{
  return WhereIn(query, selector, (IEnumerable<TValue>)collection);
}

การใช้งาน:

public static void Main()
{
  using (MyObjectContext context = new MyObjectContext())
  {
    //Using method 1 - collection provided as collection
    var contacts1 =
      context.Contacts.WhereIn(c => c.Name, GetContactNames());

    //Using method 2 - collection provided statically
    var contacts2 = context.Contacts.WhereIn(c => c.Name,
      "Contact1",
      "Contact2",
      "Contact3",
      "Contact4"
      );
  }
}

6
คำเตือน; เมื่อ arg เป็นคอลเลกชันขนาดใหญ่ (ของฉันคือ 8500 item int list) stack overflow คุณอาจคิดว่ามันเป็นเรื่องบ้าที่จะผ่านรายการดังกล่าว แต่ฉันคิดว่าสิ่งนี้ทำให้เกิดข้อบกพร่องในแนวทางนี้อย่างไรก็ตาม
dudeNumber4

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

1
หากคุณหมายความว่าเมื่อคอลเล็กชันการตรวจสอบว่างเปล่าก็ไม่ควรส่งคืนผลลัพธ์ใด ๆ ในตัวอย่างด้านบนจะแทนที่การif (!collection.Any()) //action;ดำเนินการ - แทนที่ด้วยการส่งคืนแบบสอบถามว่างของประเภทที่ร้องขอเพื่อประสิทธิภาพที่ดีที่สุด - หรือเพียงแค่ลบบรรทัดนี้
Shimmy Weitzhandler

1
กลับ WhereIn (แบบสอบถามตัวเลือกคอลเลกชัน); ควรถูกแทนที่ด้วย return WhereIn (query, selector, (IEnumerable <TValue>) collection); เพื่อหลีกเลี่ยงการเรียกซ้ำที่ไม่ต้องการ
Antoine Aubry

1
ฉันเชื่อว่ามีข้อบกพร่องในโค้ด หากรายการค่าที่ให้มาว่างเปล่าลักษณะการทำงานที่ถูกต้องควรจะไม่ส่งคืนผลลัพธ์นั่นคือ / ไม่มีอ็อบเจ็กต์ในคิวรีอยู่ในคอลเล็กชัน อย่างไรก็ตามโค้ดจะตรงกันข้าม - ค่าทั้งหมดจะถูกส่งกลับไม่ใช่เลย ฉันเชื่อว่าคุณต้องการ "if (! collection.Any ()) return query.Where (e => false)"
ShadowChaser

18

คุณสามารถถอยกลับในการเขียนโค้ด e-sql ได้ (สังเกตคำหลัก "it"):

return CurrentDataSource.Product.Where("it.ID IN {4,5,6}"); 

นี่คือรหัสที่ฉันใช้สร้าง e-sql จากคอลเล็กชัน YMMV:

string[] ids = orders.Select(x=>x.ProductID.ToString()).ToArray();
return CurrentDataSource.Products.Where("it.ID IN {" + string.Join(",", ids) + "}");

1
คุณมีข้อมูลเพิ่มเติมเกี่ยวกับ "it" หรือไม่? คำนำหน้า "it" ปรากฏในตัวอย่าง MSDN แต่ไม่สามารถหาคำอธิบายได้ว่าเมื่อใด / ทำไมถึงต้องการ "it"
Robert Claypool

1
ใช้ในแบบสอบถามไดนามิกของ Entity Framework ดูที่geekswithblogs.net/thanigai/archive/2009/04/29/… , Thanigainathan Siranjeevi อธิบายไว้ที่นั่น
Shimmy Weitzhandler

13

จากMSDN :

static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
    Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
{
    if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
    if (null == values) { throw new ArgumentNullException("values"); }
    ParameterExpression p = valueSelector.Parameters.Single();

    // p => valueSelector(p) == values[0] || valueSelector(p) == ...
    if (!values.Any())
    {
        return e => false;
    }

    var equals = values.Select(
             value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));

    var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));

    return Expression.Lambda<Func<TElement, bool>>(body, p);
} 

และข้อความค้นหาจะกลายเป็น:

var query2 = context.Entities.Where(BuildContainsExpression<Entity, int>(e => e.ID, ids));

3
หากคุณต้องการทำ 'ไม่มี' ให้ทำการแก้ไขต่อไปนี้ในเมธอด BuildContainsExpression: - Expression.Equal กลายเป็น Expression.NotEqual - Expression.Or หรือกลายเป็น Expression และ
Merritt

2

ฉันไม่แน่ใจเกี่ยวกับ Silverligth แต่ใน linq กับวัตถุฉันมักจะใช้ any () สำหรับคำค้นหาเหล่านี้

var q = from t in svc.OpenTranaction
        where txnIds.Any(t.OpenTransactionId)
        select t;

5
Any ไม่ใช้วัตถุประเภทลำดับ - ไม่มีพารามิเตอร์ (ซึ่งในกรณีนี้จะเป็นเพียง "ว่างเปล่าหรือไม่") หรือใช้เพรดิเคต
Jon Skeet

ฉันดีใจมากที่ได้พบคำตอบนี้ :) +1 ขอบคุณ AndreasN
SDReyes

1

ในการบันทึกให้เสร็จสมบูรณ์นี่คือรหัสที่ฉันใช้ในที่สุด (การตรวจสอบข้อผิดพลาดละไว้เพื่อความชัดเจน) ...

// How the function is called
var q = (from t in svc.OpenTransaction.Expand("Currency,LineItem")
         select t)
         .Where(BuildContainsExpression<OpenTransaction, long>(tt => tt.OpenTransactionId, txnIds));



 // The function to build the contains expression
   static System.Linq.Expressions.Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
                System.Linq.Expressions.Expression<Func<TElement, TValue>> valueSelector, 
                IEnumerable<TValue> values)
        {
            if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
            if (null == values) { throw new ArgumentNullException("values"); }
            System.Linq.Expressions.ParameterExpression p = valueSelector.Parameters.Single();

            // p => valueSelector(p) == values[0] || valueSelector(p) == ...
            if (!values.Any())
            {
                return e => false;
            }

            var equals = values.Select(value => (System.Linq.Expressions.Expression)System.Linq.Expressions.Expression.Equal(valueSelector.Body, System.Linq.Expressions.Expression.Constant(value, typeof(TValue))));
            var body = equals.Aggregate<System.Linq.Expressions.Expression>((accumulate, equal) => System.Linq.Expressions.Expression.Or(accumulate, equal));
            return System.Linq.Expressions.Expression.Lambda<Func<TElement, bool>>(body, p);
        }


0

ขอบคุณมาก ๆ. WhereIn วิธีการขยายก็เพียงพอสำหรับฉัน ฉันทำโปรไฟล์และสร้างคำสั่ง SQL เดียวกันกับ DataBase เป็น e-sql

public Estado[] GetSomeOtherMore(int[] values)
{
    var result = _context.Estados.WhereIn(args => args.Id, values) ;
    return result.ToArray();
}

สร้างสิ่งนี้:

SELECT 
[Extent1].[intIdFRLEstado] AS [intIdFRLEstado], 
[Extent1].[varDescripcion] AS [varDescripcion]
FROM [dbo].[PVN_FRLEstados] AS [Extent1]
WHERE (2 = [Extent1].[intIdFRLEstado]) OR (4 = [Extent1].[intIdFRLEstado]) OR (8 = [Extent1].[intIdFRLEstado])

0

ฉันคิดว่าการเข้าร่วมใน LINQ อาจเป็นเรื่องปกติ

ฉันยังไม่ได้ทดสอบโค้ด หวังว่าจะช่วยได้ ไชโย :-)

List<long?> txnIds = new List<long?>();
// Fill list 

var q = from t in svc.OpenTransaction
        join tID in txtIds on t equals tID
        select t;

เข้าร่วม LINQ:

http://weblogs.asp.net/salimfayad/archive/2008/07/09/linq-to-entities-join-queries.aspx


0

ขออภัยผู้ใช้ใหม่ฉันจะแสดงความคิดเห็นในคำตอบจริง แต่ดูเหมือนว่าฉันยังทำไม่ได้?

อย่างไรก็ตามเกี่ยวกับคำตอบด้วยโค้ดตัวอย่างสำหรับ BuildContainsExpression () โปรดทราบว่าหากคุณใช้วิธีการดังกล่าวกับเอนทิตีฐานข้อมูล (เช่นไม่ใช่วัตถุในหน่วยความจำ) และคุณใช้ IQueryable จริง ๆ แล้วจะต้องออกไปที่ฐานข้อมูล เนื่องจากโดยทั่วไปแล้วจะมีเงื่อนไข SQL "หรือ" จำนวนมากเพื่อตรวจสอบส่วนคำสั่ง "where in" (เรียกใช้ด้วย SQL Profiler เพื่อดู)

ซึ่งอาจหมายความว่าหากคุณกำลังปรับแต่ง IQueryable ด้วย BuildContainsExpression () หลายตัวมันจะไม่เปลี่ยนเป็นคำสั่ง SQL เดียวที่รันในตอนท้ายอย่างที่คุณคาดหวัง

วิธีแก้ปัญหาสำหรับเราคือใช้การรวม LINQ หลายตัวเพื่อเก็บไว้ในการเรียก SQL ครั้งเดียว


0

นอกเหนือจากคำตอบที่เลือก

แทนที่Expression.Orด้วยExpression.OrElseเพื่อใช้กับ Nhibernate และแก้ไขUnable to cast object of type 'NHibernate.Hql.Ast.HqlBitwiseOr' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'ข้อยกเว้น

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