Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert a "Not Any" query for a condition to a "All" query for the inverted condition #31

Closed
moreirayokoyama opened this issue Nov 18, 2014 · 32 comments · Fixed by #370
Closed

Comments

@moreirayokoyama
Copy link

Scenario 1: simple condition

This:

if (!results.Any(r => condition))
    throw new Exception("Error");

Becomes:

if (results.All(r => condition == false))
    throw new Exception("Error");

or

if (results.All(r => !condition))
    throw new Exception("Error");

Scenario 2: special case >, <, >= and <=

This:

if (!results.Any(r => r > 0))
    throw new Exception("Error");

Becomes:

if (results.All(r => r <= 0))
    throw new Exception("Error");

So it became more readable.
This is a refactoring, there is no diagnostic.

This is essentially a refactoring, so we are classifying it as a Hidden diagnostic.
Category is Refactoring.
Diagnostic id is: CC0043

@andersonaap
Copy link

In case of Linq To EF

!Categories.Any(x => x.CategoryID > 5) 

generates the following SQL

SELECT 
CASE WHEN ( EXISTS (SELECT 
    1 AS [C1]
    FROM [dbo].[Categories] AS [Extent1]
    WHERE [Extent1].[CategoryID] > 5
)) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM [dbo].[Categories] AS [Extent2]
    WHERE [Extent2].[CategoryID] > 5
)) THEN cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]

and the equivalent All version

Categories.All(x => x.CategoryID <= 5) 

generates

SELECT 
CASE WHEN ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM [dbo].[Categories] AS [Extent1]
    WHERE ( NOT ([Extent1].[CategoryID] <= 5)) OR (CASE WHEN ([Extent1].[CategoryID] <= 5) THEN cast(1 as bit) WHEN ( NOT ([Extent1].[CategoryID] <= 5)) THEN cast(0 as bit) END IS NULL)
)) THEN cast(1 as bit) WHEN ( EXISTS (SELECT 
    1 AS [C1]
    FROM [dbo].[Categories] AS [Extent2]
    WHERE ( NOT ([Extent2].[CategoryID] <= 5)) OR (CASE WHEN ([Extent2].[CategoryID] <= 5) THEN cast(1 as bit) WHEN ( NOT ([Extent2].[CategoryID] <= 5)) THEN cast(0 as bit) END IS NULL)
)) THEN cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]

maybe the user wants having more control of generated SQL.

@moreirayokoyama
Copy link
Author

You suggest that we favor a dynamically generated query in the EF rather suggest an improvement on code readability? If the dev wants more control over how EF generate query he can only ignore the code-cracker suggestion.

@viniciushana
Copy link
Contributor

Countless times I've sacrificed code readability over performance, and I think it's a rather common scenario. Perhaps this should be presented as an Info.

@andersonaap
Copy link

I do prefer readability where there is no high perfomance reason. Maybe somebody dont.

I could be info over DbSet, and another category over other objects.

@moreirayokoyama
Copy link
Author

I understand that readability may not be a priority according to the scenario. But, then again, I understand that this is only a matter of deliberately ignore the suggestion made by code-cracker, as well as we can do in resharper.

@giggio
Copy link
Member

giggio commented Nov 18, 2014

Agreed, it should be an Information according to our criteria. And it a safe one if the type is only IEnumerable, not IQueryable.

And I love the idea. The problem is inverting the condition would probably result in this:

if (!results.Any(r => r > 0))
    throw new Exception("Error");

becoming:

if (results.All(r => (r > 0) == false))
    throw new Exception("Error");

We could treat specially the <, >, <= and >= operators, though, but all others would end up something like that.

@moreirayokoyama
Copy link
Author

I don't think it should behave different for DbSet than for others.

@giggio
Copy link
Member

giggio commented Nov 18, 2014

IQueryables have special behavior, they are very often generating code, so they should be treated differently.

@moreirayokoyama
Copy link
Author

Ok... agreed with IQueryable.

@giggio
Copy link
Member

giggio commented Nov 18, 2014

So, is this a Warning for IEnumerable and a Information for IQueryable?

@moreirayokoyama
Copy link
Author

I agree with that.

@giggio
Copy link
Member

giggio commented Nov 18, 2014

Ok, I updated the description.
Now, I have another consideration regarding performance, and that will impact even IEnumerables:
When you use Any, at the 1st encounter of something that matches the condition the method returns, while if we use All, every member in the enumerable has to be verified. If I have a 1M sized array, and I am checking if any of them is odd, with All I have to verify if all 1M items are even, while with Any I can do it until I find the first odd.

Summarizing:

  • Running time of Any is O(n)
  • Running time of All is Θ(n)

@moreirayokoyama
Copy link
Author

Not sure if this is true... I believe that All fails on the first occurrence that doesn't match. You're sure.

@moreirayokoyama
Copy link
Author

I did a test... All stops as soon as it finds an occorrence that fails to match the condition.

@andersonaap
Copy link

Not Any will be better in half of cases. All will be better on another half of cases.
Choosing one or other makes sense when have a good idea of values that collection contains.
Any will leave the iteration when condition is true. All will leave when condition is false.

@giggio
Copy link
Member

giggio commented Nov 18, 2014

Perfect, @andersonaap. Now we have a problem. This cannot be a warning then even for IEnumerables.
It is either a refactoring of we mark it as Information. What do you think?

@moreirayokoyama
Copy link
Author

That should be the case, alright.

@andersonaap
Copy link

BCL could have a None extension where it would be equivalent to Not Any.

@giggio
Copy link
Member

giggio commented Nov 18, 2014

So, a refactoring or a Information diagnostic + code fix?

@moreirayokoyama
Copy link
Author

Seems to be a refactoring... it is as useful as invert an if (idk if anyone suggest that also) but now it is clear to me that is definitely not a code issue.

@giggio
Copy link
Member

giggio commented Nov 18, 2014

Ok, it is marked as ready, anyone can start working on it, it is up for grabs, just let us know here in the comments so we don't have two people working at the same time.

@moreirayokoyama
Copy link
Author

I can work on it. Excited to contribute. :)

@viniciushana
Copy link
Contributor

Go ahead, @moreirayokoyama! :goberserk:

@giggio
Copy link
Member

giggio commented Nov 18, 2014

@moreirayokoyama, it is yours. I am changing the status to Working right now, and removing up-for-grabs.

@giggio
Copy link
Member

giggio commented Nov 28, 2014

@moreirayokoyama How is this coming? Any idea when it will be done?

@giggio
Copy link
Member

giggio commented Dec 1, 2014

@moreirayokoyama We just changed refactorings to be diagnostics+code fixes (see #66).
BTW, how is this coming?

@moreirayokoyama
Copy link
Author

I plan to finish it between today and tomorrow. I had unexpected issues last week that made me unable to finish it then. I'll make it diagnostic+code fix as well. Sorry.

@moreirayokoyama
Copy link
Author

Ok, I'm trying to make more then just get any arbitrary "Any" method invocation and make sure that is the Any for an IEnumerable or IQueryable... but it is being hard to find out how to query the types in Roslyn. I searched in other analyzer but as it seems more assertive (by looking for methods like Single, First, SingleOrDefault etc) it seems pretty safe to assume.
Anyone can guide me in this issue (or give some hint of the proper way to goolgle it? 'coz so far I found nothing).

@giggio
Copy link
Member

giggio commented Dec 4, 2014

Yes, I think I can help you tomorrow. Let's try to talk, you have my email address.

@giggio
Copy link
Member

giggio commented Feb 4, 2015

@moreirayokoyama Are you still planing to work on this one?

@giggio
Copy link
Member

giggio commented Apr 14, 2015

@moreirayokoyama I am assuming you are not working on this one and returning it to the backlog. If you would like to complete let us know!

@giggio giggio self-assigned this May 22, 2015
@giggio giggio added this to the 1.0.0-alpha6 milestone May 22, 2015
@giggio giggio modified the milestones: 1.0.0-alpha7, 1.0.0-alpha6 May 30, 2015
giggio added a commit to giggio/code-cracker that referenced this issue Jun 6, 2015
@giggio
Copy link
Member

giggio commented Jun 6, 2015

I finished and added Any to All, and All to Any. Also added a lot of special cases. Check out:

[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => i > 1);", @"
var ints = new [] {1, 2};
var query = !ints.All(i => i <= 1);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => i >= 1);", @"
var ints = new [] {1, 2};
var query = !ints.All(i => i < 1);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => i < 1);", @"
var ints = new [] {1, 2};
var query = !ints.All(i => i >= 1);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => i <= 1);", @"
var ints = new [] {1, 2};
var query = !ints.All(i => i > 1);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => i == 1);", @"
var ints = new [] {1, 2};
var query = !ints.All(i => i != 1);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => i != 1);", @"
var ints = new [] {1, 2};
var query = !ints.All(i => i == 1);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => !true);", @"
var ints = new [] {1, 2};
var query = !ints.All(i => true);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => !(i == 1));", @"
var ints = new [] {1, 2};
var query = !ints.All(i => i == 1);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => true);", @"
var ints = new [] {1, 2};
var query = !ints.All(i => false);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => false);", @"
var ints = new [] {1, 2};
var query = !ints.All(i => true);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = !ints.Any(i => false);", @"
var ints = new [] {1, 2};
var query = ints.All(i => true);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => (i == 1) == true);", @"
var ints = new [] {1, 2};
var query = !ints.All(i => (i == 1) == false);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => true == (i == 1));", @"
var ints = new [] {1, 2};
var query = !ints.All(i => false == (i == 1));")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => (i == 1) == false);", @"
var ints = new [] {1, 2};
var query = !ints.All(i => i == 1);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => false == (i == 1));", @"
var ints = new [] {1, 2};
var query = !ints.All(i => i == 1);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => (i == 1) != true);", @"
var ints = new [] {1, 2};
var query = !ints.All(i => i == 1);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => true != (i == 1));", @"
var ints = new [] {1, 2};
var query = !ints.All(i => i == 1);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => (i == 1) != false);", @"
var ints = new [] {1, 2};
var query = !ints.All(i => (i == 1) == false);")]
[InlineData(@"
var ints = new [] {1, 2};
var query = ints.Any(i => false != (i == 1));", @"
var ints = new [] {1, 2};
var query = !ints.All(i => false == (i == 1));")]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants