String.IsNullOrWhiteSpace ใน LINQ Expression


151

ฉันมีรหัสต่อไปนี้:

return this.ObjectContext.BranchCostDetails.Where(
    b => b.TarrifId == tariffId && b.Diameter == diameter
        || (b.TarrifId==tariffId && !string.IsNullOrWhiteSpace(b.Diameter))
        || (!b.TarrifId.HasValue) && b.Diameter==diameter);

และฉันได้รับข้อผิดพลาดนี้เมื่อฉันพยายามเรียกใช้รหัส:

LINQ to Entities ไม่รู้จักวิธีการ 'Boolean IsNullOrWhiteSpace (System.String)' วิธีการและวิธีนี้ไม่สามารถแปลเป็นนิพจน์ร้านค้าได้ "

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

คำตอบ:


263

คุณต้องเปลี่ยน

!string.IsNullOrWhiteSpace(b.Diameter)

กับ

!(b.Diameter == null || b.Diameter.Trim() == string.Empty)

สำหรับ Linq to Entities สิ่งนี้จะถูกแปลเป็น:

DECLARE @p0 VarChar(1000) = ''
...
WHERE NOT (([t0].[Diameter] IS NULL) OR (LTRIM(RTRIM([t0].[Diameter])) = @p0))

และสำหรับ Linq ถึง SQL แทบจะไม่เหมือนกันเลย

DECLARE @p0 NVarChar(1000) = ''
...
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = @p0)

3
ทำไม? รหัสนี้รวบรวม:List<string> my = new List<string>(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;
Eric J.

37
มันอาจรวบรวม แต่มันจะไม่ถูกแปลเป็น SQL โดย Linq ไปยังเอนทิตี วิธีการ 'บูลีน IsNullOrWhiteSpace (System.String)' ไม่มีการสนับสนุนการแปลเป็น SQL เช่นเดียวกับ IsNullOrEmpty
Phil

1
เช่นเดียวกับ Linq ถึง SQL
Phil

3
คำเตือน: มันมีความสำคัญยิ่งที่จะใช้ 'string.Empty' มากกว่า "" (หรือที่รู้จักว่าเป็นสตริงว่าง) งานก่อนหน้าหลังไม่ได้ (อย่างน้อยก็เท่าที่เกี่ยวข้องกับไดรเวอร์ EF ของ Oracle) อาคาถ้าคุณใช้: b.Diameter.Trim () == "" <- มันจะไม่ทำงานตามที่ตั้งใจ (บ้าฉันรู้ว่า ... )
XDS

ดูเหมือนว่า Trim () ไม่ได้รับการสนับสนุนอย่างน้อยสำหรับการสืบค้นที่ใช้ MongoDB.Driver
Leandro hereñu

20

ในกรณีนี้มันเป็นสิ่งสำคัญที่จะแยกแยะระหว่างและIQueryable<T> IEnumerable<T>ในระยะสั้นIQueryable<T>มีการประมวลผลโดยผู้ให้บริการ LINQ เพื่อส่งแบบสอบถามที่ดีที่สุด ในระหว่างการแปลงนี้ไม่สนับสนุนคำสั่ง C # ทั้งหมดเนื่องจากไม่สามารถแปลข้อความเหล่านั้นเป็นข้อความค้นหาเฉพาะด้านหลัง (เช่น SQL) หรือเนื่องจากผู้ดำเนินการไม่คาดการณ์ความต้องการคำสั่ง

ในทางตรงกันข้ามIEnumerable<T>จะดำเนินการกับวัตถุที่เป็นรูปธรรมและดังนั้นจะไม่ถูกแปลง ดังนั้นจึงเป็นเรื่องธรรมดาที่โครงสร้างที่ใช้IEnumerable<T>งานได้ไม่สามารถใช้กับIQueryable<T>และIQueryables<T>ได้รับการสนับสนุนจากผู้ให้บริการ LINQ ที่แตกต่างกันไม่สนับสนุนฟังก์ชั่นชุดเดียวกัน

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

ดังนั้นโค้ดด้านบนอาจถูกเขียนใหม่เช่นนี้:

return this.ObjectContext.BranchCostDetails
    .AsEnumerable()
    .Where(
        b => b.TarrifId == tariffId && b.Diameter == diameter
        || (b.TarrifId==tariffId && !string.IsNullOrWhiteSpace(b.Diameter))
        ||(!b.TarrifId.HasValue) && b.Diameter==diameter
    );

หมายเหตุ:รหัส Ths จะมีผลกระทบต่อประสิทธิภาพการทำงานที่สูงกว่าคำตอบของฟิล อย่างไรก็ตามมันแสดงให้เห็นถึงหลักการ


10

ใช้เข้าชมการแสดงออกในการตรวจสอบการอ้างอิงถึง string.IsNullOrWhiteSpace (x == null || x.Trim() == string.Empty)และทำลายพวกเขาลงไปในการแสดงออกที่เรียบง่าย

ดังนั้นด้านล่างคือผู้เข้าชมเพิ่มเติมและวิธีการต่อเติมเพื่อใช้ประโยชน์จากมัน มันไม่จำเป็นต้องใช้การตั้งค่าพิเศษใด ๆ เพียงแค่เรียก WhereEx แทนที่ไหน

public class QueryVisitor: ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.IsStatic && node.Method.Name == "IsNullOrWhiteSpace" && node.Method.DeclaringType.IsAssignableFrom(typeof(string)))
        {
            //!(b.Diameter == null || b.Diameter.Trim() == string.Empty)
            var arg = node.Arguments[0];
            var argTrim = Expression.Call(arg, typeof (string).GetMethod("Trim", Type.EmptyTypes));

            var exp = Expression.MakeBinary(ExpressionType.Or,
                    Expression.MakeBinary(ExpressionType.Equal, arg, Expression.Constant(null, arg.Type)),
                    Expression.MakeBinary(ExpressionType.Equal, argTrim, Expression.Constant(string.Empty, arg.Type))
                );

            return exp;
        }

        return base.VisitMethodCall(node);
    }
}

public static class EfQueryableExtensions
{
    public static IQueryable<T> WhereEx<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> where)
    {
        var visitor = new QueryVisitor();
        return queryable.Where(visitor.VisitAndConvert(where, "WhereEx"));
    }
}

ดังนั้นหากคุณเรียกใช้myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace())มันจะถูกแปลงเป็น!(c.Name == null || x.Trim() == "")ก่อนที่จะถูกส่งผ่านไปยังอะไรก็ตาม (linq เป็น sql / เอนทิตี) และแปลงเป็น sql


มีความซับซ้อนมากกว่าคำตอบของฟิลสำหรับความต้องการง่ายๆเช่นนี้ แต่น่าสนใจมากสำหรับวัตถุประสงค์ด้านการศึกษาเกี่ยวกับ ExpressionVisitor ขอบคุณ
AFract

2

คุณยังสามารถใช้สิ่งนี้เพื่อตรวจสอบช่องว่าง:

b.Diameter!=null && !String.IsNullOrEmpty(b.Diameter.Trim())

6
สิ่งนี้จะทำให้เกิดข้อยกเว้นถ้าเส้นผ่านศูนย์กลางเป็นโมฆะ
Okan Kocyigit

@OkanKocyigit คุณพูดถูก ฉันได้แก้ไขคำตอบแล้ว :)
Majid

0
!String.IsNullOrEmpty(b.Diameter.Trim()) 

จะโยนยกเว้นถ้าเป็นb.Diameter หากคุณยังต้องการใช้คำสั่งของคุณให้ใช้การตรวจสอบนี้ดีกว่าnull

!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace

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