Search code examples
c#listcodefluent

Slow collections when using List in CodeFluent


I have had memoryleak issues in CodeFluent because eventhandlers of ListCollections of objects maintained references to objects I did not need anymore. The solution was to change the collection type of the entity to List in stead of ListCollection. That solved the memory leak problems.

However, now I noticed that the List is MUCH slower than the ListCollection. Every time Codefluent adds an object to the List if checks whether the object is already in the list. This will fire the BaseContains method in the object. 91% of CPU is spent here (profiling using ANTS). I have marked the Hot path with CPU percentage.

The function LoadByMainCwEntity contains the following code block:

for (readerRead = reader.Read(); ((readerRead == true) 
            && ((count < this.MaxCount) 
            && (count < pageSize))); readerRead = reader.Read())
{
    readCount = (readCount + 1);
    if ((CodeFluent.Runtime.CodeFluentPersistence.CanAddEntity(pageIndex, pageSize, pageOptions, readCount) == true))
    {
        Runtime.CwObject cwObject = new Runtime.CwObject();
        ((CodeFluent.Runtime.ICodeFluentEntity)(cwObject)).ReadRecord(reader);
        91% CPU >> if ((this.BaseContains(cwObject) == false))
        {
            this.BaseAdd(cwObject);
            count = (count + 1);
        }
        cwObject.EntityState = CodeFluent.Runtime.CodeFluentEntityState.Unchanged;
    }
}

Which calls this:

protected virtual bool BaseContains(Runtime.CwObject cwObject)
        {
            if ((cwObject == null))
            {
                return false;
            }
            91% CPU >> bool localContains = this.BaseList.Contains(cwObject);
            return localContains;
        }

Which calls this:

public virtual bool Equals(Runtime.CwObject cwObject)
    {
        if ((cwObject == null))
        {
            return false;
        }
        29% CPU >> if ((this.Guid.Equals(CodeFluentPersistence.DefaultGuidValue) == true))
        {
            return base.Equals(cwObject);
        }
        45% CPU >> return (this.Guid.Equals(cwObject.Guid) == true);
    }

All methods seem light. I think the problem lies in the hitcount. If I have a list of 100.000 objects and Codefluent adds number 100.001 it will check all 100.000 others to find a match. A growing collection will exponentially slow down the .Add method.

It seems a bit odd to check whether the object is already in the collection in a normal 'load' operation of Codefluent. Is there any workaround or should I just live with the fact large Lists are really slow in Codefluent?


Solution

  • SoftFluent has created an aspect that improves the performance of the generated code by removing some checks and genericity: blog post, code on GitHub. In the benchmark provided by Frans Bouma, using this aspect, CodeFluent Entities is ranked at the 2nd place just after the hand coded query.

    If you have identified just a little number of load methods that are slow in the context of your application, you can create custom C# methods that use the generated code to return a custom collection. For instance you can create a method which returns an IEnumerable<T> instead of the generated collection:

    static IEnumerable<Order> LoadOrders()
    {
        using (IDataReader reader = OrderCollection.PageDataLoadAll(null))
        {
            while (reader.Read())
            {
                Order o = new Order();
                o.RaisePropertyChangedEvents = false;
                ((ICodeFluentEntity)o).ReadRecord(reader);
                yield return o;
            }
            CodeFluentPersistence.CompleteCommand(Constants.NorthwindStoreName);
        }
    }
    

    Update

    The fastReader attribute should be visible in the property grid when you select a property on the surface. You must select the "Aspect and Producers Properties" tab:

    Aspects and producers Properties

    The property is added by the aspect:

    <cf:descriptor name="fastReader" targets="Property" defaultValue="false" displayName="Enable Fast Reader" typeName="boolean" description="Determines if the fast reder is enabled for collection loading." category="Faster Reader Aspect" />
    

    The aspect remove automatic conversions (CodeFluentPersistence.GetReaderValue) and expect the stored procedures to return all the columns. In the some cases, it may not work.