The Jade 2025 release focuses on modernising the developer experience and helping you build faster, smarter, and more scalable applications.
the JoobCollection base class doesn't implement the IEnumerable/IQueryable method .Any() . That means it's left to the default implementation, which means we read all the items in the collection from the database to check whether there are any items in the table - a potentially very expensive operation that has a very cheap workaround. We measured that even if we were to only attempt to read the first item in the DB each time, it's still an order of magnitude faster to use the Count property (30ms vs 5ms)
Because using System.Linq is so ubiquitous, it's not practical for us to write an extension method, so we need an implementation that's "closer" in .NET terms.
This is entirely a developer productivity problem:
1) It's not always clear that the particular method we're in is operating on a JadeCollection (i.e. we depend on interfaces like IEnumerable) unless we start attempting to hold a lot more context in our heads.
2) The Linq methods are so common and helpful, they exist in our code even when we're not operating on a JadeCollection - their existence doesn't raise eyebrows for a reviewer.
3) There is a strong preference by the .NET community and all the static checkers built for it (like ReSharper) encourage the use of .Any() over .Count() > 0 because of the performance improvements available - JADE is asking us to go against the flow on all that experience / peer pressure.
4) It doesn't make a noticeable difference when the collection is small and our test data often is.
So what's the impact for our customers?
Performance regressions are often only found when we hit production, purely because it's really difficult to produce test data that is large and varied enough to find that kind issue.
After reviewing the suggestion, we've concluded that we’re unable to implement a solution that reliably prevents developers from inadvertently falling back to System.Linq Enumerable.Any() implementation causing performance degradation.
The only real work arounds for this is:
Use Count() > 0 like Microsoft now recommend as of 2023
Implement bool Any<TSource>(this IEnumerable<TSource> source) in the same file you use Any as it is the only way to have the same signature as Linq's implementation and hide it.
Move to .NET 6+ as the base implementation of Any will check the Count so no iterator is created.
We understand that the reason Resharper recommends using Any is because if the object your enumerating does not implement ICollection then there is no Count property for it to return, so it will instead iterate the entire collection to retrieve this value. Any() by contrast will always instantiate an Enumerator for every enumerable which degrades performance for every object that implements ICollection. But that is less of a performance impact than the few collections that have no Count.
However, it appears Microsoft now do not agree with this and have added a warning in 2023 CA1860 to discourage
developers from using the Any extension method because of Any's performance issues.
If we were to implement the Any() method on your collections or one of its super classes, it will not be called because IEnumerable does not define an Any method. Any is only implemented via extension.
Consider the following Any implementation within JadeSoftware.Joob.dll:
bool Any(this IEnumerable<JoobObject> source);If we had that implementation the only time it will be called is if you had an IEnumerable<JoobObject> variable, not an IEnumerable<Customer>. You will now have performance issues if you have an enumerable variable with any other signature than exactly IEnumerable<JoobObject> as Linq does not adhere to where clauses or class inheritance, just the type of the variable it is being used on. We could then generate a CollectionExtension.cs file in your exposure which automatically defines extension methods for every collection membership exposed However this is not guaranteed to work either. If you have any generic methods or classes which call Any then they will almost always resolve to the default Linq implementation, not any of the concrete implementations we provide.
However, if at any point you use Any from a generic method or class then it could still choose the Linq implementation. An example would be the code snippet below:
public class GenericCollWraper<T>{
public IEnumerable<T> coll;
public bool Any()
{
return coll.Any(); // ignores the Any(this IEnumerable<Customer>); implementation.
// even though the object is a GenericCollWrapper<Customer>.
}
}
public class Program{
public static void Main(string[] args)
{
IEnumerable<Customer> customers;
using (var ctx = new JoobContext())
{
var customerRoot = ctx.FirstInstance<CustomerRoot>();
customers = customerRoot.AllCustomersById;
var s = new GenericCollWraper<Customer> { coll = customers };
s.Any();
}
}
}
Not seeing this with Dotnet 9, J 22 Ansi. Calls to Any and Count both map to RootSchema::Collection::size
I suspect you're using an older version of Dotnet (before dotnet 6?)
LINQs extension methods for Count and Any had the common problem of enumerating to resolve the count if it was over an I/Enumerable, because there's no count property on there.
This was a common with SQL & Dotnet web dev as well.
JoobCollection implements all this:
public abstract class JoobCollection<T> : JoobObject, IJoobCollection<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IJoobCollection where T : JoobObject
ICollection has a Count property.
Later versions of DotNet and LINQ address this - LINQ checks to see if the collection is actually some kind of list or collection first, which then lets it delegate to the Count property, and avoid enumeration. The source is online and publicly available.
I've attached my own code sample, which produces the attached profiler report - no enumeration.
Output:
Hello, World!
True
18846
The problem is likely with how you're using Dotnet and LINQ, what version they're on, and probably not Jade.
If a DotNet version upgrade is too difficult (Though I would definitely recommend, since you are chasing after performance) you can try changing usages of IEnumerable<T> to ICollection<T>, or try using IQueryables instead if your Jade collections are MKDs, as a fix in the meantime.
Can you confirm Dotnet version details and provide code samples with profiler results?
Corrected Count >> Any
Oh dear, it's clearly Monday - I meant
.Any()and kept insisting on calling it `.Count()`