New command in the loop is wasteful. Create it once and loop on the values.
foreach (var softwarecomponent in softwareComponents)
{
using (var cmd = new SqlCommand(query, trans.Connection, trans))
SQL has async methods SqlCommand.BeginExecuteNonQuery;
It is one set of write heads on the other end. My experience is a single insert command will perform best. For sure it has less impact on resources.
I am not even sure this will work. You don't mention enabling MARS.
MARS operations are not thread-safe.