Simply as a demonstration that you need to take this little detail into account even when abstracting, if you look at Microsoft's Data Access Block which for years has provided ADO abstraction using the System.Data.Common base, you'll see that they address this very issue by including virtual method in the abstract base class Database
that is then overridden by the provider specific derived classes.
So the base class Database.cs has this method:
/// <summary>Builds a value parameter name for the current database.</summary>
/// <param name="name">The name of the parameter.</param>
/// <returns>A correctly formated parameter name.</returns>
public virtual string BuildParameterName(string name){ return name; }
(if the provider uses positional parameters or has no need of a prefix, there is nothing more to override)
and then the SqlClient specific provider implementation SqlDatabase.cs overrides it as such:
/// <summary>Gets the parameter token used to delimit parameters for the SQL Server database.</summary>
protected char ParameterToken{ get { return '@'; } }
public override string BuildParameterName(string name)
{
if (name == null) throw new ArgumentNullException("name");
if (name[0] != ParameterToken)
return name.Insert(0, new string(ParameterToken, 1));
return name;
}
Notice that this implementation allows the calling code to use sql parameter names with the '@' prefix or not, thus freeing the devs from having to know/remember what the api actually does to the name under the covers.
I don't use the DAAB directly, but their overall approach to abstracting behind the System.Data
and System.Data.Common
interfaces and classes is a great guideline for small data access api's.