The use of parameters is handy, but it is not the only way to protect oneself against SQL injection, nor is it foolproof.
If the stored procedure internally dynamically creates and performs an EXEC() on SQL code, you lose all of that protection. You also lose that protection if you have a single query in your application that includes those fields without using parameters or escaping.
Parameters aren't magic, and you can protect yourself by simply using an escape function when building your SQL:
Public Shared Function StringToSql(ByVal s As String) As String
If s Is Nothing Then Return "NULL"
Return "N'" & Replace(s, "'", "''") & "'"
End Function
Usage:
Sql = "INSERT INTO mytable(name) VALUES(" & StringToSql(username) & ")"
Boom. Easy as pie. Even takes care of the nvarchar quoting.
You can convert dates, numbers, GUIDs, etc to strings and pass them to the function above, but you're better off creating separate functions for each data type you use.
There is one caveat: you have to use it every single time you build a query-- every CRUD that includes this data, whether produced at the application layer or generated dynamically inside a stored procedure.
But you have to do the same thing to benefit from parameters! So either way, you have to change your habits and review your code. There is no escaping that (lame pun intended).