Linq to Entities - ส่วนคำสั่ง SQL“ IN”


230

ใน T-SQL คุณสามารถมีแบบสอบถามเช่น:

SELECT * FROM Users WHERE User_Rights IN ("Admin", "User", "Limited")

คุณจะทำซ้ำสิ่งนั้นในการสืบค้น LINQ ไปยังนิติบุคคล? เป็นไปได้ไหม

คำตอบ:


349

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

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

ไวยากรณ์การสืบค้น:

var selected = from u in users
               where new[] { "Admin", "User", "Limited" }.Contains(u.User_Rights)
               select u

foreach(user u in selected)
{
    //Do your stuff on each selected user;
}

ไวยากรณ์ของวิธีการ:

var selected = users.Where(u => new[] { "Admin", "User", "Limited" }.Contains(u.User_Rights));

foreach(user u in selected)
{
    //Do stuff on each selected user;
}

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

foreach(User u in users.Where(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}

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

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

อีกวิธีหนึ่งที่ไม่ได้ใช้ LINQ คุณสามารถใช้ไวยากรณ์ของวิธีการเดิมแทน "ที่ไหน" กับ "FindAll" และรับผลลัพธ์เดียวกันซึ่งจะทำงานใน NET 2.0:

foreach(User u in users.FindAll(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}

บางทีฉันอาจเร็วเกินไปที่จะทำเครื่องหมายว่าเป็นคำตอบ แต่ฉันไม่ได้รับข้อความ. หลังจาก {"ผู้ดูแลระบบ", "ผู้ใช้", "จำกัด "} VS2008 ไม่ชอบรหัสนั้นเลยแม้แต่นิดเดียว
StevenMcD

1
จริงกับชื่อของฉัน "FailBoy" ฉันคิดออก: PI ใส่ลงในสตริง [] แล้วใช้มันและมันทำงานได้ ขอบคุณ!
StevenMcD

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

28
คำตอบนี้จะถูกต้องหากคำถามเกี่ยวกับ Linq-to-SQL หรือ Linq โดยทั่วไป อย่างไรก็ตามเนื่องจากมันระบุว่า "Linq-to-Entities" โดยเฉพาะคำตอบนี้จึงไม่ถูกต้อง array.Contains ยังไม่รองรับ Linq-to-Entities
KristoferA

6
@KristoferA - ซึ่งอาจเป็นจริงสำหรับ EF รุ่นก่อนหน้า แต่มันก็ดีสำหรับฉันกับ EF4
Drew Noakes

21

สิ่งนี้จะเพียงพอต่อความต้องการของคุณ มันเปรียบเทียบสองคอลเลกชันและตรวจสอบว่าคอลเลกชันหนึ่งมีค่าที่ตรงกับที่อยู่ในคอลเลกชันอื่น ๆ

fea_Features.Where(s => selectedFeatures.Contains(s.feaId))

9

หากคุณใช้ VS2008 / .net 3.5 ดูคำแนะนำของ Alex James # 8: http://blogs.msdn.com/alexj/archive/2009/03/26/tip-8-writing-where-in-style -queries โดยใช้-LINQ เพื่อ entities.aspx

มิฉะนั้นก็ใช้วิธี array.Contains (someEntity.Member)


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


8

ฉันจะไปเข้าร่วมวงในในบริบทนี้ ถ้าฉันจะใช้ประกอบด้วยมันจะย้ำ 6 ครั้งแม้จะมีความจริงที่ว่ามีเพียงหนึ่งการแข่งขัน

var desiredNames = new[] { "Pankaj", "Garg" }; 

var people = new[]  
{  
    new { FirstName="Pankaj", Surname="Garg" },  
    new { FirstName="Marc", Surname="Gravell" },  
    new { FirstName="Jeff", Surname="Atwood" }  
}; 

var records = (from p in people join filtered in desiredNames on p.FirstName equals filtered  select p.FirstName).ToList(); 

ข้อเสียของการมี

สมมติว่าฉันมีวัตถุสองรายการ

List 1      List 2
  1           12
  2            7
  3            8
  4           98
  5            9
  6           10
  7            6

การใช้ประกอบด้วยจะค้นหาแต่ละรายการ 1 รายการในรายการ 2 ซึ่งหมายความว่าการทำซ้ำจะเกิดขึ้น 49 ครั้ง !!!


5
สิ่งนี้ไม่สนใจข้อเท็จจริงที่ว่าคำสั่งนั้นถูกแปลเป็น SQL ดูที่นี่
Gert Arnold

5

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

var result = _db.Companies.Where(c => _db.CurrentSessionVariableDetails.Select(s => s.CompanyId).Contains(c.Id)).ToList();

2

ฉันยังพยายามที่จะทำงานกับ SQL-IN-เหมือนสิ่ง - สอบถามกับข้อมูลนิติบุคคลรุ่น วิธีการของฉันคือเครื่องมือสร้างสตริงเพื่อเขียน OR-expression ขนาดใหญ่ นั่นน่าเกลียดมาก แต่ฉันเกรงว่ามันเป็นวิธีเดียวที่จะไปในตอนนี้

ตอนนี้ดูเหมือนว่า:

Queue<Guid> productIds = new Queue<Guid>(Products.Select(p => p.Key));
if(productIds.Count > 0)
{
    StringBuilder sb = new StringBuilder();
    sb.AppendFormat("{0}.ProductId = Guid\'{1}\'", entities.Products.Name, productIds.Dequeue());
    while(productIds.Count > 0)
    {
        sb.AppendFormat(" OR {0}.ProductId = Guid\'{1}\'",
          entities.Products.Name, productIds.Dequeue());
    }
}

การทำงานกับ GUID ในบริบทนี้ : ตามที่คุณเห็นด้านบนจะมีคำว่า "GUID" อยู่ข้างหน้า GUID ถ้าตัวเองอยู่ในแฟรกเมนต์สตริงการสืบค้น หากคุณไม่เพิ่มสิ่งนี้ให้ObjectQuery<T>.Whereโยนข้อยกเว้นต่อไปนี้:

อาร์กิวเมนต์ชนิด 'Edm.Guid' และ 'Edm.String' ไม่เข้ากันสำหรับการดำเนินการนี้ใกล้กับนิพจน์เท่ากับบรรทัด 6 คอลัมน์ 14

พบสิ่งนี้ในฟอรัม MSDN อาจเป็นประโยชน์หากมีอยู่ในใจ

มัทธีอัส

... รอคอยรุ่นต่อไปของ. NET และ Entity Framework เมื่อทุกอย่างดีขึ้น :)


2

วิธีอื่นในการตอบ BenAlabaster

ก่อนอื่นคุณสามารถเขียนแบบสอบถามแบบนี้ใหม่:

var matches = from Users in people
        where Users.User_Rights == "Admin" ||
              Users.User_Rights == "Users" || 
              Users.User_Rights == "Limited"
        select Users;

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

ดังนั้นถ้าเรามีวิธีอรรถประโยชน์ที่ทำให้มันง่ายในการสร้างการแสดงออกของ LINQ เหล่านี้เราจะอยู่ในธุรกิจ

ด้วยวิธีการใช้งานยูทิลิตี้คุณสามารถเขียนสิ่งนี้:

var matches = ctx.People.Where(
        BuildOrExpression<People, string>(
           p => p.User_Rights, names
        )
);

สิ่งนี้จะสร้างนิพจน์ที่มีผลเหมือนกับ:

var matches = from p in ctx.People
        where names.Contains(p.User_Rights)
        select p;

แต่ที่สำคัญกว่านั้นใช้งานได้กับ. NET 3.5 SP1

นี่คือฟังก์ชั่นระบบประปาที่ทำให้สิ่งนี้เป็นไปได้:

public static Expression<Func<TElement, bool>> BuildOrExpression<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();

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

ฉันจะไม่พยายามอธิบายวิธีการนี้นอกจากจะบอกว่ามันเป็นการสร้างนิพจน์เพรดิเคตสำหรับค่าทั้งหมดโดยใช้ valueSelector (เช่น p => p.User_Rights) และ ORs เพรดิเคตด้วยกันเพื่อสร้างนิพจน์ที่สมบูรณ์ คำกริยา

ที่มา: http://blogs.msdn.com/b/alexj/archive/2009/03/26/tip-8-writing-where-in-style-queries-using-linq-to-entities.aspx


0

ตัวอย่างจริง:

var trackList = Model.TrackingHistory.GroupBy(x => x.ShipmentStatusId).Select(x => x.Last()).Reverse();
List<int> done_step1 = new List<int>() {2,3,4,5,6,7,8,9,10,11,14,18,21,22,23,24,25,26 };
bool isExists = trackList.Where(x => done_step1.Contains(x.ShipmentStatusId.Value)).FirstOrDefault() != null;

-13

อย่างจริงจัง? คนที่คุณไม่เคยใช้

where (t.MyTableId == 1 || t.MyTableId == 2 || t.MyTableId == 3)

9
-1 ลองด้วยค่า 20 หรือมากกว่าในตารางที่มีมากกว่า 1,000 แถวและคุณจะเห็นได้อย่างรวดเร็วถึงข้อดีของวิธีแก้ปัญหาที่ยอมรับ นอกจากนี้มันไม่ใช่เรื่องง่ายที่จะเพิ่มจำนวนเงื่อนไขไปยังคำสั่ง where (เช่นถ้าผู้ใช้เลือกที่จะรวมตัวเลือก 1 และ 2 แต่ไม่ใช่ 3)
ริป

ฉันไม่ต้องการนักวิทยาศาสตร์ที่บ้าอะไรเลยและคำตอบนี้ไปให้ฉันเพราะฉันต้องการ AND และ 2 ORS var SamplePoints = (จาก c ใน _db.tblPWS_WSF_SPID_ISN_Lookup.OrderBy (x => x.WSFStateCode) โดยที่ c PWS == id && ((c.WSFStateCode.Substring (0, 2) == "SR") || (c.WSFStateCode.Substring (0, 2) == "CH")) เลือก c) .ToList () ;
JustJohn

@Trisped - จำนวนแถว (1,000) ไม่เปลี่ยนแปลงอะไร - หรือฉันไม่มีอะไรเลย?
tymtam

@Tymski ใช่จำนวนแถวมีความสำคัญ ยิ่งแถวยิ่งมีการคำนวณมากเท่าไหร่ Checks = NumValues * NumRowsเช่นเดียวกันกับจำนวนของค่าที่เป็นไปได้: เนื่องจากนี่เป็นการคำนวณประเภท M * N หากมีขนาดเล็กดังนั้นเวลาที่ใช้ในการตรวจสอบแต่ละครั้งก็จะมีค่าน้อยเช่นกัน ฉันเพิ่มข้อ จำกัด เพื่อให้ cjm30305 รู้วิธีตั้งค่าสภาพแวดล้อมการทดสอบซึ่งแสดงว่าทำไมโซลูชันของเขาจึงไม่ดี
trisped

@Trisped คุณกำลังบอกว่าwhere new[] { 1, 2, 3 }.Contains(x)มันเปรียบเทียบน้อยกว่าwhere (x == 1 || x == 2 || x == 3)หรือเปล่า?
tymtam
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.