ailinmcc666
ailinmcc666

Reputation: 413

Can anyone help me with this StackOverFlow.Exception?

I have a web application, made using c#/mvc/ef/unitofworkPattern. I have database with two tables, PERSON and CASENOTE. A PERSON may have multiple CASENOTES, but a CASENOTE may only have one PERSON. The key in PERSON is PasNo. This is a foreign key in CASENOTE, but it's not defined as a foreign key in DDL for reasons.

So, I have a search page, where someone can input data into a CASENOTE number textbox, and get back all people linked to any casenotes it may find. So they could put in just a partial casenote, and it would get back all casenotes containing that partial string, and then find all of the PEOPLE that have those casenotes using the PasNo in each casenote.

So here is my code:

public IEnumerable<PERSON> GetSearchResults(SearchViewModel viewModel)
{
    var people = unitOfWork.PersonRepository.GetAll();
    if (viewModel.Casenote != null)
    {
        var casenotes = unitOfWork.CasenoteRepository.GetAll()
                            .Where(x => x.CASENOTE1.Trim()
                                   .ToLower()
                                   .Contains(viewModel.Casenote.Trim().ToLower()))
                            .Take(1000)
                            .ToList();

        if (casenotes.Count > 0)
        {
            foreach (var casenote in casenotes)
            {
                var pasint = casenote.PAS_INT_NO;
                people = people.Where(w => w.PAS_INT_NO == pasint);
                //it gives me the stackoverflow.exception at execution of the above line
            } 
        }
    }
    return people.Take(1000).ToList().OrderBy(x => x.SURNAME).ThenBy(x => x.FORENAMES);
}

Can anybody help me and tell me what I'm doing wrong? I can provide more info if needed.

EDIT! I forgot to mention, when I put in a single letter into the CASENOTE textbox, I get the stackoverflow exception, but when I type more than 1 letter, I don't and the search works.

Upvotes: 0

Views: 106

Answers (2)

Renat
Renat

Reputation: 8962

If you need to select from people using or condition over w.PAS_INT_NO == pasint, this would work:

public IEnumerable<PERSON> GetSearchResults(SearchViewModel viewModel)
{
    var people = unitOfWork.PersonRepository.GetAll();
    if (viewModel.Casenote != null)
    {
        var casenoteNos = unitOfWork.CasenoteRepository.GetAll()
                            .Where(x => x.CASENOTE1.Trim()
                                   .ToLower()
                                   .Contains(viewModel.Casenote.Trim().ToLower()))
                            .Select(casenote => casenote.PAS_INT_NO)
                            .Take(1000)
                            .ToList();

        if (casenoteNos.Any())
        {
            people = people.Where(w => casenoteNos.Contains(w.PAS_INT_NO));
        }
    }
    return people.Take(1000).ToList().OrderBy(x => x.SURNAME).ThenBy(x => x.FORENAMES);
}

Description

The culprit in the original code was:

people = people.Where(w => w.PAS_INT_NO == pasint)

because IEnumerable produced by Where is not materialized, here we creating up to 1000 chained Where, so eventually people becomes people.Where(...).Where(...).Where...1000 times.

If check the sources of Where extension here, first Where produces WhereEnumerableIterator object then its Where and consequent Wheres generate nested lambda calls here by calling CombinePredicates, which is:

static Func<TSource, bool> CombinePredicates<TSource>(Func<TSource, bool> predicate1, Func<TSource, bool> predicate2) {
    return x => predicate1(x) && predicate2(x);
}

Which being called 1000 times becomes:

(x=> ...1000 times... (x => predicate1(x) && predicate2(x)) && predicate2(x)) ...1000 times... ) && predicate2(x)

Then 1000 nested calls produced a stack overflow exception.

Conclusion: don't chain a ton of Where, it was not designed for that. Anyway chained Where (a ton of AND filters) here is not what was intended, but IN / Contains is needed instead.

Upvotes: 2

deruitda
deruitda

Reputation: 580

Is there a reason you are getting a list of all the people at the top of the function? I'm not that familiar with EF so this syntax could be wrong, but I'm assuming that this would be much more efficient than getting every person in your database and then filtering that list:

        foreach (var casenote in casenotes)
        {
            var pasint = casenote.PAS_INT_NO;
            people = unitOfWork.PersonRepository.Where(w => w.PAS_INT_NO == pasint);
            //it gives me the stackoverflow.exception at execution of the above line
        } 

Not sure if this will solve the problem, but if there are a lot of people and a lot of casenotes in your database that could definitely cause a SO exception.

Upvotes: 0

Related Questions