Home > database >  Why EF core memory leak exception is solved by static method
Why EF core memory leak exception is solved by static method

Time:12-20

I'm not sure if the title of this question is good enough, please pardon me if it is not.

I wrote a LINQ query

var result =
from person in _dbContext.Persons
where person.OrganizationId == organisationId

select new PersonInfo
{
    Id = person.Id,
    Username = person.User.Name,
    Gender = person.User.Gender,
    MembershipStatus = GetMembershipStatus(person)
};

MembershipStatus GetMembershipStatus(Person person)
{
    if (activeCondition)
        return MembershipStatus.Active;
    if (!activeCondition)
        return MembershipStatus.Inactive;
    if (viewerCondition)
        return MembershipStatus.Viewer;
    if (readerCondition)
        return MembershipStatus.Reader;

    return MembershipStatus.None;
}

This throws an exception

System.InvalidOperationException: The client projection contains a reference to a constant expression of 'MyQuery' through the instance method 'GetMembershipStatus'. This could potentially cause a memory leak; consider making the method static so that it does not capture constant in the instance. See https://go.microsoft.com/fwlink/?linkid=2103067 for more information.

This is exactly same process and behaviour this question has. The first comment there suggested the asker changes a method, in my case GetMembershipStatus to a static method. I did that and it works.

I asked if the commenter could explain exactly why that works for me but I guess he's not active, so I'm throwing the question here, could someone kindly explain what the exception means and why changing that method to a static one made the exception go away? Thank you.

CodePudding user response:

The issue here is that the projection includes a call to the GetMembershipStatus method, which is an instance method. This means that the method is defined on an instance of a class and requires an instance of that class to be called.

Because the Person object is being processed as part of the query, it cannot be garbage collected until the query has completed. This means that the expression tree created by LINQ to Entities, which includes a reference to the Person object, also cannot be garbage collected. This can potentially lead to a memory leak if the query is executed repeatedly, because the expression tree and the Person objects it references will not be released from memory.

By making the GetMembershipStatus method static, you can avoid this issue because the method does not require an instance of the class to be called. This allows the Person object to be garbage collected when the query completes, avoiding the potential for a memory leak.

If you didn't' want to use a static method one way would be to finish the evaluation by doing a ToList

var result = (from person in _dbContext.Persons
where person.OrganizationId == organisationId select person).ToList()

CodePudding user response:

Lets simplify your example a bit;

Expression<Func<Person,PersonInfo>> expr = 
    person => new PersonInfo {
        Id = person.Id,
        MembershipStatus = GetMembershipStatus(person)
    };

The C# compiler needs to capture the implicit [this.]GetMembershipStatus. If you look at the disassembly, you'll see;

ConstantExpression instance = Expression.Constant(this, typeof(<your class>));

Now the expr variable includes a reference to this, preventing garbage collection until the expression tree variable is collected.

So why is this a problem with EF Core specifically? Because the query compiler takes too long to execute for every query; the expression tree, resulting sql and materialisation function will be cached.

  • Related