Linq To Entities - Any VS First VS Exists

后端 未结 5 1146
滥情空心
滥情空心 2021-01-07 21:36

I am using Entity Framework and I need to check if a product with name = \"xyz\" exists ...

I think I can use Any(), Exists() or First().

Which one is the b

相关标签:
5条回答
  • 2021-01-07 22:01

    One would think Any() gives better results, because it translates to an EXISTS query... but EF is awfully broken, generating this (edited):

    SELECT 
    CASE WHEN ( EXISTS (SELECT 
        1 AS [C1]
        FROM [MyTable] AS [Extent1]
        WHERE Condition
    )) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
        1 AS [C1]
        FROM [MyTable] AS [Extent2]
        WHERE Condition
    )) THEN cast(0 as bit) END AS [C1]
    FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
    

    Instead of:

    SELECT 
    CASE WHEN ( EXISTS (SELECT 
        1 AS [C1]
        FROM [MyTable] AS [Extent1]
        WHERE Condition
    )) THEN cast(1 as bit)
       ELSE cast(0 as bit) END AS [C1]
    FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
    

    ...basically doubling the query cost (for simple queries; it's even worse for complex ones)

    I've found using .Count(condition) > 0 is faster pretty much always (the cost is exactly the same as a properly-written EXISTS query)

    0 讨论(0)
  • 2021-01-07 22:03

    Any translates into "Exists" at the database level. First translates into Select Top 1 ... Between these, Exists will out perform First because the actual object doesn't need to be fetched, only a Boolean result value.

    At least you didn't ask about .Where(x => x.Count() > 0) which requires the entire match set to be evaluated and iterated over before you can determine that you have one record. Any short-circuits the request and can be significantly faster.

    0 讨论(0)
  • 2021-01-07 22:03

    Any() and First() is used with IEnumerable which gives you the flexibility for evaluating things lazily. However Exists() requires List.

    I hope this clears things out for you and help you in deciding which one to use.

    0 讨论(0)
  • 2021-01-07 22:17

    Okay, I wasn't going to weigh in on this, but Diego's answer complicates things enough that I think some additional explanation is in order.

    In most cases, .Any() will be faster. Here are some examples.

    Workflows.Where(w => w.Activities.Any())
    Workflows.Where(w => w.Activities.Any(a => a.Title == "xyz"))
    

    In the above two examples, Entity Framework produces an optimal query. The .Any() call is part of a predicate, and Entity Framework handles this well. However, if we make the result of .Any() part of the result set like this:

    Workflows.Select(w => w.Activities.Any(a => a.Title == "xyz"))
    

    ... suddenly Entity Framework decides to create two versions of the condition, so the query does as much as twice the work it really needed to. However, the following query isn't any better:

    Workflows.Select(w => w.Activities.Count(a => a.Title == "xyz") > 0)
    

    Given the above query, Entity Framework will still create two versions of the condition, plus it will also require SQL Server to do an actual count, which means it doesn't get to short-circuit as soon as it finds an item.

    But if you're just comparing these two queries:

    1. Activities.Any(a => a.Title == "xyz")
    2. Activities.Count(a => a.Title == "xyz") > 0

    ... which will be faster? It depends.

    The first query produces an inefficient double-condition query, which means it will take up to twice as long as it has to.

    The second query forces the database to check every item in the table without short-circuiting, which means it could take up to N times longer than it has to, depending on how many items need to be evaluated before finding a match. Let's assume the table has 10,000 items:

    • If no item in the table matches the condition, this query will take roughly half the time as the first query.
    • If the first item in the table matches the condition, this query will take roughly 5,000 times longer than the first query.
    • If one item in the table is a match, this query will take an average of 2,500 times longer than the first query.
    • If the query is able to leverage an index on the Title and key columns, this query will take roughly half the time as the first query.

    So in summary, IF you are:

    1. Using Entity Framework 4 (since newer versions might improve the query structure) Entity Framework 6.1 or earlier (since 6.1.1 has a fix to improve the query), AND
    2. Querying directly against the table (as opposed to doing a sub-query), AND
    3. Using the result directly (as opposed to it being part of a predicate), AND
    4. Either:
      1. You have good indexes set up on the table you are querying, OR
      2. You expect the item not to be found the majority of the time

    THEN you can expect .Any() to take as much as twice as long as .Count(). For example, a query might take 100 milliseconds instead of 50. Or 10 instead of 5.

    IN ANY OTHER CIRCUMSTANCE .Any() should be at least as fast, and possibly orders of magnitude faster than .Count().

    Regardless, until you have determined that this is actually the source of poor performance in your product, you should care more about what's easy to understand. .Any() more clearly and concisely states what you are really trying to figure out, so stick with that.

    0 讨论(0)
  • 2021-01-07 22:19

    Ok, I decided to try this out myself. Mind you, I'm using the OracleManagedDataAccess provider with the OracleEntityFramework, but I'm guessing it produces compliant SQL.

    I found that First() was faster than Any() for a simple predicate. I'll show the two queries in EF and the SQL that was generated. Mind you, this is a simplified example, but the question was asking whether any, exists or first was faster for a simple predicate.

     var any = db.Employees.Any(x => x.LAST_NAME.Equals("Davenski"));
    

    So what does this resolve to in the database?

    SELECT 
    CASE WHEN ( EXISTS (SELECT 
    1 AS "C1"
    FROM "MYSCHEMA"."EMPLOYEES" "Extent1"
    WHERE ('Davenski' = "Extent1"."LAST_NAME")
     )) THEN 1 ELSE 0 END AS "C1"
    FROM  ( SELECT 1 FROM DUAL ) "SingleRowTable1"
    

    It's creating a CASE statement. As we know, ANY is merely syntatic sugar. It resolves to an EXISTS query at the database level. This happens if you use ANY at the database level as well. But this doesn't seem to be the most optimized SQL for this query. In the above example, the EF construct Any() isn't needed here and merely complicates the query.

     var first = db.Employees.Where(x => x.LAST_NAME.Equals("Davenski")).Select(x=>x.ID).First();
    

    This resolves to in the database as:

    SELECT 
    "Extent1"."ID" AS "ID"
    FROM "MYSCHEMA"."EMPLOYEES" "Extent1"
    WHERE ('Davenski' = "Extent1"."LAST_NAME") AND (ROWNUM <= (1) )
    

    Now this looks like a more optimized query than the initial query. Why? It's not using a CASE ... THEN statement.

    I ran these trivial examples several times, and in ALMOST every case, (no pun intended), the First() was faster.

    In addition, I ran a raw SQL query, thinking this would be faster:

    var sql = db.Database.SqlQuery<int>("SELECT ID FROM MYSCHEMA.EMPLOYEES WHERE LAST_NAME = 'Davenski' AND ROWNUM <= (1)").First();
    

    The performance was actually the slowest, but similar to the Any EF construct.

    Reflections:

    1. EF Any doesn't exactly map to how you might use Any in the database. I could have written a more optimized query in Oracle with ANY than what EF produced without the CASE THEN statement.
    2. ALWAYS check your generated SQL in a log file or in the debug output window.
    3. If you're going to use ANY, remember it's syntactic sugar for EXISTS. Oracle also uses SOME, which is the same as ANY. You're generally going to use it in the predicate as a replacement for IN. In this case it generates a series of ORs in your WHERE clause. The real power of ANY or EXISTS is when you're using Subqueries and are merely testing for the EXISTENCE of related data.

    Here's an example where ANY really makes sense. I'm testing for the EXISTENCE of related data. I don't want to get all of the records from the related table. Here I want to know if there are Surveys that have Comments.

    var b = db.Survey.Where(x => x.Comments.Any()).ToList();
    

    This is the generated SQL:

    SELECT 
    "Extent1"."SURVEY_ID" AS "SURVEY_ID", 
    "Extent1"."SURVEY_DATE" AS "SURVEY_DATE"
    FROM "MYSCHEMA"."SURVEY" "Extent1"
    WHERE ( EXISTS (SELECT 
    1 AS "C1"
    FROM "MYSCHEMA"."COMMENTS" "Extent2"
    WHERE ("Extent1"."SURVEY_ID" = "Extent2"."SURVEY_ID")
     ))
    

    This is optimized SQL! I believe the EF does a wonderful job generating SQL. But you have to understand how the EF constructs map to DB constructs else you can create some nasty queries.

    And probably the best way to get a count of related data is to do an explicit Load with a Collection Query count. This is far better than the examples provided in prior posts. In this case you're not loading related entities, you're just obtaining a count. Here I'm just trying to find out how many Comments I have for a particular Survey.

      var d = db.Survey.Find(1);
    
      var  e = db.Entry(d).Collection(f => f.Comments)
                                   .Query()
                                   .Count();
    
    0 讨论(0)
提交回复
热议问题