KJanek
KJanek

Reputation: 315

Foreach loop async-await

I have a question about good practice with async-await in foreach loop.

My method looks like this

 public async Task<List<Category>> GetCategoriesAsync(string partOfName,
        CancellationToken ct = default(CancellationToken))
    {
        string lowerCasePartOfName = partOfName.ToLower();

        var categories = await _context.ECATEGORIES
            .Where(a => a.NAME.ToLower().Contains(lowerCasePartOfName))
            .ProjectTo<Category>()
            .ToListAsync(ct);

        //version1 #Beginning 
        var i = 0;
        foreach (var parentId in categories)
        {
            var categoryParent = await _context.ECATEGORIES
                .Where(a => a.ID == parentId.ParentId)
                .Select(s => s.NAME)
                .FirstOrDefaultAsync(ct);

            categories[i].CategoryParent = categoryParent;
            i++;
        }
        //version1 #End

        //version2 #Beginning 
        categories.ForEach(async x => x.CategoryParent = await _context.ECATEGORIES
            .Where(a => a.ID == x.ParentId)
            .Select(s => s.NAME).FirstOrDefaultAsync(ct));
        //version2 #End

        return categories;
    }

Version1 and version2 gives same result but I would like to ask which is better practice for async tasks or maybe none of them.

Thanks in advance.

Upvotes: 0

Views: 276

Answers (1)

Igor
Igor

Reputation: 62213

Both are bad in that the code could be rewritten to make use of a proper join. That would make 1 db call instead of 1 call per category + 1 (for the initial call). But strictly answering your question, it does not matter: pick the one you feel most comfortable with

Problem I had is that, parentId is often duplicated and I couldn't use .Contains

You can use a left join to do the same thing but all in 1 DB call which is cheaper than 1 call per result.

string lowerCasePartOfName = partOfName.ToLower();

var categories = await (from category in _context.ECATEGORIES.Where(a => a.NAME.ToLower().Contains(lowerCasePartOfName))
    from parent in _context.ECATEGORIES.Where(parent => parent.ID == category.ParentId).DefaultIfEmpty()
    select new Category
    {
        Id = category.ID,
        CategoryParent = parent.NAME,
    }).ToListAsync();

If your schema is setup to be case insensitive then you can omit the ToLower calls as well. You can check this by looking at the COLLATION.

.Where(a => a.NAME.Contains(lowerCasePartOfName))

Upvotes: 1

Related Questions