Fill: SelectCommand.Connection error on derived iDBCommand - c#

Before you dowonvote this, please have a look first. I know there numerous threads here with this errormessage, but I haven't found one with this particular problem. I've created a custom command class implementing the IDbCommand interface like this:
internal class Prozedur : IDbCommand
{
private static SqlCommand command = new SqlCommand() ;
private SqlConnection connection;
string commandtext;
CommandType commandType;
public Prozedur(string abfrage)
{
commandtext = abfrage;
commandType = CommandType.StoredProcedure;
connection = (SqlConnection)new Verbindung();
SqlCommand command = new SqlCommand(commandtext, connection);
}
public static explicit operator SqlCommand(Prozedur v)
{
return command;
}
...
I'am using the object like this:
internal class Tabelle : DataTable
{
DataTable tabelle = new DataTable();
internal Tabelle(string abfrage)
{
Prozedur p = new Prozedur(abfrage);
SqlDataAdapter adapter = new SqlDataAdapter((SqlCommand)p);
adapter.Fill(tabelle);
}
}
However, on the adapter.Fill(tabelle) i get the Error “Fill: SelectCommand.Connection property has not been initialized.”. But if i look at the object p, the connection is there:
Update
If i change the code like this
internal class Tabelle : DataTable
{
DataTable tabelle = new DataTable();
internal Tabelle(string abfrage)
{
Prozedur p = new Prozedur(abfrage);
SqlCommand c = (SqlCommand)p;
//added this line
c.Connection = p.Connection;
SqlDataAdapter adapter = new SqlDataAdapter((SqlCommand)p);
adapter.Fill(tabelle);
}
}
I get a compiler error for p.Connection; C# Cannot implicitly convert type to. An explicit conversion exists (are you missing a cast?).
The code for the connection is this:
public IDbConnection Connection
{
get
{
return connection;
}
set
{
connection = (SqlConnection)value;
}
}

The problem was in the convert method. When I change the code as below it works. Basically I had to make all variables static and return a new SqlCommandObject in the convert method.
internal class Prozedur : IDbCommand
{
private static SqlCommand command = new SqlCommand();
private static SqlConnection connection;
static string commandtext;
CommandType commandType;
public Prozedur(string abfrage)
{
commandtext = abfrage;
commandType = CommandType.StoredProcedure;
connection = (SqlConnection)new Verbindung();
}
public static explicit operator SqlCommand(Prozedur v)
{
return new SqlCommand() { CommandText = commandtext, Connection = connection, CommandType = command.CommandType };
}
...
And it works like this without static variables:
internal class Prozedur : IDbCommand
{
private SqlCommand command = new SqlCommand();
private SqlConnection connection;
string commandtext;
CommandType commandType;
public Prozedur(string abfrage)
{
commandtext = abfrage;
commandType = CommandType.StoredProcedure;
connection = (SqlConnection)new Verbindung();
}
public static explicit operator SqlCommand(Prozedur v)
{
return new SqlCommand() { CommandText = v.CommandText, Connection = (SqlConnection)v.Connection, CommandType = v.CommandType };
}
...

Related

Truncated incorrect DOUBLE value: 'hello.world'

In my DAO, I have:
public void UpdateProfile(string newName, string newBio, long profileId)
{
using var dbConnection = _databaseProvider.GetConnection();
dbConnection.SetQuery($"UPDATE `profile_data` SET `name` = #newName AND `bio` = #newBio AND `fixed_unicode` = 1 WHERE `profile_id` = #profileId");
dbConnection.AddParameter("newName", newName);
dbConnection.AddParameter("newBio", newBio);
dbConnection.AddParameter("profileId", profileId);
dbConnection.ExecuteQuery();
}
It results in:
Unhandled exception. MySql.Data.MySqlClient.MySqlException (0x80004005): Truncated incorrect DOUBLE value: 'hello.world'
I don't understand why, because the name column is a varchar(255), verified via:
>> SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'profile_data' AND COLUMN_NAME = 'name'
>> varchar
Below I will place code references in my main example, for anyone to check out if it helps debug the issue.
_databaseProvider:
public class DatabaseProvider : IDatabaseProvider
{
private readonly string _connectionString;
public DatabaseProvider(string connectionString)
{
_connectionString = connectionString;
}
public DatabaseConnection GetConnection()
{
var connection = new MySqlConnection(_connectionString);
var command = connection.CreateCommand();
return new DatabaseConnection(connection, command);
}
}
DatabaseConnection:
public class DatabaseConnection : IDisposable
{
private readonly MySqlConnection _connection;
private readonly MySqlCommand _command;
public DatabaseConnection(MySqlConnection connection, MySqlCommand command)
{
_connection = connection;
_command = command;
_connection.Open();
}
public void SetQuery(string commandText)
{
_command.Parameters.Clear();
_command.CommandText = commandText;
}
public int ExecuteQuery()
{
return _command.ExecuteNonQuery();
}
public Task ExecuteQueryAsync()
{
return _command.ExecuteNonQueryAsync();
}
public MySqlDataReader ExecuteReader()
{
return _command.ExecuteReader();
}
public object ExecuteScalar()
{
return _command.ExecuteScalar();
}
public int GetLastId()
{
SetQuery("SELECT LAST_INSERT_ID();");
return int.Parse(ExecuteScalar().ToString());
}
public void AddParameter(string name, object value)
{
_command.Parameters.AddWithValue(name, value);
}
public void Dispose()
{
_connection.Close();
_command.Dispose();
}
}
You UPDATE query is incorrect. Replace AND keyword with a comma.
The query should look like this:
$"UPDATE `profile_data` SET `name` = #newName,
`bio` = #newBio,
`fixed_unicode` = 1
WHERE `profile_id` = #profileId"
Without executing your code, I can notice that your UPDATE command is not correct.
Try this
UPDATE `profile_data` SET `name` = #newName ,`bio` = #newBio ,`fixed_unicode` = 1 WHERE `profile_id` = #profileId");

Why Connection not being closed in this code

I am maintaining a previous developer work.
Here is the db layer class, it has .......
public class Database
{
private string mConnString;
private SqlConnection mConn;
private SqlDataAdapter mAdapter;
private SqlCommand mCmd;
private SqlTransaction mTransaction;
private bool disposed = false;
public Database() : this(Web.GetWebConfigValue("ConnectionString"))
{
}
public Database(string connString)
{
mConnString = connString;
mConn = new SqlConnection(mConnString);
mConn.Open();
mAdapter = new SqlDataAdapter();
mCmd = new SqlCommand();
mCmd.CommandType = CommandType.StoredProcedure;
mCmd.Connection = mConn;
}
public void CloseConnection()
{
mConn.Close();
}
public void BeginTransaction()
{
mTransaction = mConn.BeginTransaction();
mCmd.Transaction = mTransaction;
}
public void CommitTransaction()
{
mTransaction.Commit();
}
public void RollbackTransaction()
{
mTransaction.Rollback();
}
public void AddParam(string name, SqlDbType type, object value)
{
SqlParameter parameter = new SqlParameter('#' + name, type);
parameter.Value = value;
mCmd.Parameters.Add(parameter);
}
public void ChangeParam(string name, object value)
{
mCmd.Parameters['#' + name].Value = value;
}
public void DeleteParam(string name)
{
mCmd.Parameters.RemoveAt('#' + name);
}
public void AddReturnParam()
{
SqlParameter parameter = new SqlParameter();
parameter.ParameterName = "return";
parameter.Direction = ParameterDirection.ReturnValue;
mCmd.Parameters.Add(parameter);
}
public void AddOutputParam(string name, SqlDbType type, int size)
{
SqlParameter parameter = new SqlParameter('#' + name, type);
parameter.Direction = ParameterDirection.Output;
parameter.Size = size;
mCmd.Parameters.Add(parameter);
}
public int GetReturnParam()
{
return (int)mCmd.Parameters["return"].Value;
}
public object GetOutputParam(string name)
{
return mCmd.Parameters['#' + name].Value;
}
public void ClearParams()
{
mCmd.Parameters.Clear();
}
public void ExecNonQuery(string cmdText)
{
if(mConn.State==ConnectionState.Closed)
mConn.Open();
mCmd.CommandText = cmdText;
mCmd.ExecuteNonQuery();
}
public DataSet GetDataSet(string cmdText)
{
mCmd.CommandText = cmdText;
mAdapter.SelectCommand = mCmd;
DataSet ds = new DataSet();
mAdapter.Fill(ds);
return ds;
}
public IDataReader GetDataReader(string cmdText)
{
mCmd.CommandText = cmdText;
if(mConn.State==ConnectionState.Closed)
mConn.Open();
return mCmd.ExecuteReader(CommandBehavior.CloseConnection);
}
public DataTable GetDataTable(string cmdText)
{
return GetDataSet(cmdText).Tables[0];
}
public DataTable GetDataTable(string cmdText,string SQL)
{
SqlCommand cmd = new SqlCommand();
cmd.CommandText = cmdText;
mAdapter.SelectCommand = cmd;
cmd.Connection = mConn;
DataSet ds = new DataSet();
mAdapter.Fill(ds);
return ds.Tables[0];
}
public DataRow GetDataRow(string cmdText)
{
DataTable dt = GetDataTable(cmdText);
DataRow dr;
if(dt.Rows.Count > 0)
dr = dt.Rows[0];
else
dr = null;
return dr;
}
public object GetScalar(string cmdText)
{
mCmd.CommandText = cmdText;
return mCmd.ExecuteScalar();
}
public void SetCommandType(CommandType type)
{
mCmd.CommandType = type;
}
~Database()
{
this.Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if(!this.disposed)
{
if(disposing)
{
if(mConn.State == ConnectionState.Open)
mConn.Close();
this.mCmd.Dispose();
this.mAdapter.Dispose();
this.mTransaction.Dispose();
}
}
disposed = true;
}
}
Can you help me in figuring out where connection might not be closed in all the cases where this class is being used.
The connection only ever gets closed when disposing the instance of the DataBase class.
However, while this class is implementing the disposable pattern, it doesn't implement the IDisposable interface - so you can't use it in a using statement.
Moreover, you have to rely on whoever is using this class to dispose it.
If they don't, the connection will not get closed until the Finalizer gets called, and that is completely out of the developer's control. It might even not get called at all - as the garbage collector might not need to clear memory during the runtime of whatever application is using this code.
This is why the proper way of handling connections to the database is as a local variable inside a using statement.
What you want to do is create the connection and open it as late as possible, and dispose it as soon as possible.
A proper method for handling calls to the database looks like this:
int ExecuteNonQuery(string sql)
{
using(var con = new SqlConnection(connectionString))
{
using(var cmd = new SqlCommand(sql, con))
{
con.Open();
return cmd.ExecueNonQuery();
}
}
}
Of course, you would want to add arguments to hold whatever parameters you'll need to pass to the database, and an argument to hold command type, but that should be built on the basis of this structure.
I have a project on GitHub called ADONETHelper (that I've been neglecting for the past year or so due to lack of spare time) that was written in order to reduce the code repetition when using ADO.Net directly.
I've written it a few years back so of course now I have improvements in mind but as I said, I don't have the spare time to work on it - but the general idea is still valid and useful.
Basically, it has a single Execute method that looks like this:
public T Execute<T>(string sql, CommandType commandType, Func<IDbCommand, T> function, params IDbDataParameter[] parameters)
{
using (var con = new TConnection())
{
con.ConnectionString = _ConnectionString;
using (var cmd = new TCommand())
{
cmd.CommandText = sql;
cmd.Connection = con;
cmd.CommandType = commandType;
if (parameters.Length > 0)
{
cmd.Parameters.AddRange(parameters);
}
con.Open();
return function(cmd);
}
}
}
Than I've added a few methods that use this method:
public int ExecuteNonQuery(string sql, CommandType commandType, params IDbDataParameter[] parameters)
{
return Execute<int>(sql, commandType, c => c.ExecuteNonQuery(), parameters);
}
public bool ExecuteReader(string sql, CommandType commandType, Func<IDataReader, bool> populate, params IDbDataParameter[] parameters)
{
return Execute<bool>(sql, commandType, c => populate(c.ExecuteReader()), parameters);
}
and so on.
Feel free to borrow ideas from that project - or even use it as is - I have a few applications using this and they run very well for quite some time now.
You aren't implementing the disposable pattern via the IDisposable interface, you just have a Dispose method, in turn you would not be able to call this in a using statement.
public class Database : IDisposable { ... }
This is all a bit suspect: I mean, if you are already using it you aren't using this in a using statement and trying to cache the connection seemingly. I would shy away from this altogether.
Also you have a Destructor, however its usage is wrong 99% of times.
While a persistence layer makes perfectly sense, you have to design it differently. What you do is pack some complexity into methods which still do the same, such as ChangeParam() or GetDataReader().
Normally, you have repositories which have knowledge about the underlying technicalities and remove that kind of complexity, such as GetAllCustomers() (with emphasis on the domain term customer).
When you have say 4 or 5 such repositories, then you start refactoring by abstracting complexity into a parent class. By doing so, you're packing the complexity such as in GetDataReader() and promote it from the repositories into an abstract repository that sits on top of it.
By starting from where you started you'll get just another layer which does not abstract nearly as much and has too much, often unnecessary, functionality.
If it were that easy, the ADO.NET API would already have it removed in the first place.
Another thing you should do is look at this simple but ever-recurring code snippet. It distillates some core concepts about IDisposable and using(){}. You'll always encounter it in correct code:
string sql = "SELECT * FROM t";
using (SqlConnection con = new SqlConnection(connectionString))
using (SqlCommand cmd = new SqlCommand(sql, con))
{
SqlDataReader reader;
con.Open();
reader = cmd.ExecuteReader();
while (reader.Read())
{
// TODO: consume data
}
reader.Close();
}
This is the stuff I'd expect to see in a persistence layer, and the consume data part is actually the most important, because it is domain-dependent. The rest is just boilerplate code with little interest.

ADO.NET DAL and BLL

I have two classes SqlHelper and DishesTypes there are used in a DAL project
public class SqlHelper
{
public static SqlDataReader ExecuteReader(string procedure,
params SqlParameter[] commandParameters)
{
using (var connection = new SqlConnection(
ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
using (var command = new SqlCommand(procedure, _connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(commandParameters);
return command.ExecuteReader();
}
}
public class DishesTypes
{
public static SqlDataReader DishesTypesSelectAll()
{
return SqlHelper.ExecuteReader("DishesTypesSelectAllRows"); //name of procedure
}
}
And I have class DishedTypes that used in a BLL project like this
public class DishesTypes
{
public int DishTypeId { get; set; }
public string DishType { get; set; }
public static List<DishesTypes> DishesTypesSelectAll()
{
IDataReader dr = DataAccessLayer.DishesTypes.DishesTypesSelectAll();
List<DishesTypes> dishesTypesList = new List<DishesTypes>();
while (dr.Read())
{
DishesTypes myDishesTypes = new DishesTypes
{
DishTypeId = (int)dr["DishTypeId"],
DishType = (string)dr["DishType"]
};
dishesTypesList.Add(myDishesTypes);
}
return dishesTypesList;
}
}
Problems starts here while (dr.Read()),The reason, the connection to this point has already closed and it is necessary to reconnect how best to change the implementation of classes adhering layers DAL and BLL, to work?
If you want to roll your own, something like this is better:
public class DataQuery
{
private readonly string _connectionString;
public DataQuery(string connectionString)
{
_connectionString = connectionString;
}
public IEnumerable<T> GetList<T>(string procedure,
Func<IDataRecord, T> entityCreator,
params SqlParameter[] commandParameters
)
{
var result = new List<T>();
using (var connection = CreateConnection())
using (var command = CreateCommand(procedure, connection, commandParameters))
using (var reader = command.ExecuteReader())
{
result.Add(entityCreator(reader));
}
return result;
}
private SqlConnection CreateConnection()
{
var connection = new SqlConnection(_connectionString);
connection.Open();
return connection;
}
private static DbCommand CreateCommand(string procedure,
SqlConnection connection, SqlParameter[] commandParameters)
{
var command = new SqlCommand(procedure, connection)
{
CommandType = CommandType.StoredProcedure
};
command.Parameters.AddRange(commandParameters);
return command;
}
}
Which you would call like this:
var connectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"]
.ConnectionString;
var query = new DataQuery(connectionString);
Func<IDataRecord, DishesTypes> creator = dr =>
new DishesTypes
{
DishTypeId = (int)dr["DishTypeId"],
DishType = (string)dr["DishType"]
};
var results = query.GetList("DishesTypesSelectAllRows", creator);
Otherwise, which I recommend, have a look at Dapper.
Dapper would allow you to simply do:
var results = connection.Query<DishesTypes>("DishesTypesSelectAllRows",
commandType: CommandType.StoredProcedure);
First of all, your using statement is closing your connection, so you cannot expect to return a useable IDataReader. Second, your connection is never opened, so you would not get a result, anyway. Having said that, if your dataset will always be small enough to fit in memory, you could use something like what I have done below. This should have minimal impact on your code.
public class SqlHelper
{
public static IDataReader ExecuteReader(string procedure, params SqlParameter[] commandParameters)
{
using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
connection.Open();
using (var command = new SqlCommand(procedure, connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(commandParameters);
DataTable dt = new DataTable();
using (SqlDataAdapter da = new SqlDataAdapter(command))
da.Fill(dt);
return dt.CreateDataReader();
}
}
}
}
public class DishesTypes
{
public static IDataReader DishesTypesSelectAll()
{
return SqlHelper.ExecuteReader("DishesTypesSelectAllRows");//name of procedure
}
}

Create separate class for connection string

i am trying to create separate class for connection named Connection.cs, why am i having this error while executing the query
ExecuteNonQuery requires an open and available Connection. The connection's current state is closed
what am i doing wrong ?
in my Dal.cs class
public void Insert(string f1,string f2)
{
string query;
Connection c = new Connection();
c.OpenCnn();
try {
query = "inset into ..."
cmd = new SqlCommand(query, con);
cmd.ExecuteNonQuery();
}
catch (Exception ex){
throw ex;
}
finally {
c.CloseCnn();
}
}
and in my Connection.cs class
public class Connection
{
SqlCommand cmd = new SqlCommand();
SqlConnection con = new SqlConnection();
public void OpenCnn()
{
string cnnStr = ConfigurationManager.ConnectionStrings["myconnstrng"].ToString();
con = new SqlConnection(cnnStr);
con.Open();
}
public void CloseCnn()
{
con.Close();
}
}
Currently the con field is private and can not be accessible from outside of Connection class.
You can add a property to your connection class:
public SqlConnection Connection { get { return con; } }
Then access it via your instance:
Connection c = new Connection();
c.OpenCnn();
try
{
query = "inset into ..."
cmd = new SqlCommand(query, c.Connection);
cmd.ExecuteNonQuery();
}
Btw, I'm not sure what are you referring by con in your Insert method (probably a local variable instead of the con field, because otherwise it wouldn't even compile)
public class Connection
{
SqlConnection con;
public SqlConnection Conn { get { return con; } }
public void OpenCnn()
{
string cnnStr = ConfigurationManager.ConnectionStrings["myconnstrng"].ToString();
con = new SqlConnection(cnnStr);
con.Open();
}
public void CloseCnn()
{
con.Close();
}
}
Method Insert:
Note SqlCommand initialization and usage of public property conn.
public void Insert(string f1, string f2)
{
string query;
Connection c = new Connection();
c.OpenCnn();
try
{
query = "insert into ..."
SqlCommand cmd = new SqlCommand(query, c.Conn);
cmd.ExecuteNonQuery();
}
catch (Exception ex){
throw ex;
}
finally {
c.CloseCnn();
}
}

Convert delegate to Action<T> (Action<T1,T2>)

I have a DB class that makes all DB calls like below:
public delegate void Part1_Callback(string message);
public delegate void Part2_Callback(DataTable dt);
public delegate void Part3_Callback(DataTable dt, int x, int y);
public delegate void ErrorHandler(string message);
public class CommandAndCallback<TCallback>
{
public SqlCommand Sql { get; set; }
public TCallback Callback { get; set; }
public ErrorHandler Error { get; set; }
}
class DB : SingletonBase<DB>
{
public static readonly string SqlConnectionString = #"Data Source=MyDB;Initial Catalog=Stats;Integrated Security=True;Asynchronous Processing=true;";
private DB()
{
}
public void Part2(Part2_Callback callback, ErrorHandler error)
{
SqlConnection conn = new SqlConnection(SqlConnectionString);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "Part2";
try
{
conn.Open();
}
catch (Exception ex)
{
error(ex.ToString());
return;
}
CommandAndCallback<Part2_Callback> ar = new CommandAndCallback<Part2_Callback>() { Callback = callback, Error = error, Sql = cmd };
IAsyncResult result = cmd.BeginExecuteReader(new AsyncCallback(Part2_Handler), ar, CommandBehavior.CloseConnection);
}
private void Part2_Handler(IAsyncResult result)
{
DataTable dt = new DataTable();
CommandAndCallback<Part2_Callback> ar = (CommandAndCallback<Part2_Callback>)result.AsyncState;
SqlDataReader dr;
if (result.IsCompleted)
{
dr = ar.Sql.EndExecuteReader(result);
}
else
dr = null;
dt.Load(dr);
dr.Close();
dt.Columns[3].ReadOnly = false;
ar.Callback(dt);
}
}
And in my main class I'm using it like so:
private void Form1_Enter(object sender, EventArgs e)
{
showStatus("Loading");
DB.Instance.Part2(Part2_OK, ErrorHandler);
}
private void ErrorHandler(string msg)
{
hideStatus();
viewStack1.InvokeIfRequired(c => { c.moveToFirst(); });
//MessageBox.Show(msg, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
private void Part2_OK(DataTable dt)
{
dataGridView1.InvokeIfRequired(c =>
{
c.DataSource = dt;
});
}
Right now I have 3 methods in my DB class that return 3 different sets of data, for every type I must declare delegate.
If in future I'll add more methods then I'll have to add more delegates.
Can I remove usage of delegates? I would like to simplify build of my class so that it will be easy to add new methods.
What I need is to be able to call my DB Class like so:
DB.Instance.PartX(PartX_OK, ErrorHandler);
PartX is declared as shown
private void PartX_OK(DataTable dt, int x, int y, ...)
{
//logic here
}
Can Action<T> be used to to that, so I can call my handlers with multiple parameters? If yes then how?
Yes, I meant to get back to your earlier question, so your function would become
public void Part2(Action<DataTable> callback, ErrorHandler error)
{
SqlConnection conn = new SqlConnection(SqlConnectionString);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "Part2";
try
{
conn.Open();
}
catch (Exception ex)
{
error(ex.ToString());
return;
}
CommandAndCallback<Action<DataTable>> ar = new CommandAndCallback<Action<DataTable>>() { Callback = callback, Error = error, Sql = cmd };
IAsyncResult result = cmd.BeginExecuteReader(new AsyncCallback(Part2_Handler), ar, CommandBehavior.CloseConnection);
}
private void Part2_Handler(IAsyncResult result)
{
DataTable dt = new DataTable();
CommandAndCallback<Action<DataTable>> ar = (CommandAndCallback<Action<DataTable>>)result.AsyncState;
SqlDataReader dr;
if (result.IsCompleted)
{
dr = ar.Sql.EndExecuteReader(result);
}
else
dr = null;
dt.Load(dr);
dr.Close();
dt.Columns[3].ReadOnly = false;
ar.Callback(dt);
}
There are Action classes with upto 16 generic parameters, you will probably find the one for your needs;). See MSDN page.
And the invocation
DB.Instance.PartX((p1, p2, p3, p4) => { ... }, ErrorHandler);
Part1_Callback will be equivilant to Action<string>
Part2_Callback will be equivilant to Action<DataTable>
Part3_Callback will be equivilant to Action<DataTable, int, int>
ErrorHandler will be equivilant to Action<string>
Just use those types in place of each of your existing delegates.
There are almost no cases anymore where you need to define your own delegates. If you have >16 parameters, ref/out parameters, params parameters, or optional arguments then there may not be an Action/Func overload for you, but that's not particularly common.

Categories