Я согласен с комментариями, там есть некоторые запахи кода, и, вероятно, написание запроса более эффективным способом ускорит весь процесс в большей степени, чем любая оптимизация, полученная с помощью параллелизма.
Сказав это, я думаю, что вы ищете что-то вроде этого:
var contactIds = new List<int>();
contactIds.AddRange(res.RelatedContacts.Select(c => c.IdVal).ToList());
contacts = await _dbContext.Contacts.Where(co => contactIds.Contains(co.ContactId)).ProjectTo<ContactDTO>(_mapper.ConfigurationProvider).AsNoTracking().ToListAsync();
Parallel.Foreach(contacts, contact => Update(contact));
//or: Parallel.Foreach(contacts, Update);
void Update(Contact contact){
contact.address = addresses.FirstOrDefault(a => a.AddressId.Equals(contact.AddressId));
contact.ImagePath = contact.ImageId > 0 ? images.FirstOrDefault(im => im.ImageId.Equals(contact.ImageId)).FilePath : "NA";
}
Некоторые примечания:
- нет необходимости повторять через идентификаторы, а затем получить первый элемент с этим идентификатором. Просто итерируйте элементы.
- не должно быть проблем с безопасностью потока, операции в
Update
доступны только для чтения.