0

I want to inherit from NHibernate's SqlClientBatchingBatcher class exactly like this (code taken from TooManyRowsAffectedException with encrypted triggers):

public class NonBatchingBatcherWithoutVerification : SqlClientBatchingBatcher
{
    public NonBatchingBatcherWithoutVerification(ConnectionManager connectionManager, IInterceptor interceptor) : base(connectionManager, interceptor)
    {}

    protected override void DoExecuteBatch(IDbCommand ps)
    {
        log.DebugFormat("Executing batch");
        CheckReaders();
        Prepare(currentBatch.BatchCommand);
        if (Factory.Settings.SqlStatementLogger.IsDebugEnabled)
        {
            Factory.Settings.SqlStatementLogger.LogBatchCommand(currentBatchCommandsLog.ToString());
            currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:");
        }

        int rowsAffected = currentBatch.ExecuteNonQuery();

        // Removed the following line
        //Expectations.VerifyOutcomeBatched(totalExpectedRowsAffected, rowsAffected);

        currentBatch.Dispose();
        totalExpectedRowsAffected = 0;
        currentBatch = new SqlClientSqlCommandSet();
    }
}

Just notice some of the members accessed in the method here (like currentBatch or totalExpectedRowsAffected).

Well, it turns out these members are actually private in the superclass of the current NHibernate 3.3 source. So how do I effectively inherit the class without copying the whole thing? This is the unmodified NHibernate code of the class by the way:

 public class SqlClientBatchingBatcher : AbstractBatcher
{
    private int _batchSize;
    private int _totalExpectedRowsAffected;
    private SqlClientSqlCommandSet _currentBatch;
    private StringBuilder _currentBatchCommandsLog;
    private readonly int _defaultTimeout;

    public SqlClientBatchingBatcher(ConnectionManager connectionManager, IInterceptor interceptor)
        : base(connectionManager, interceptor)
    {
        _batchSize = Factory.Settings.AdoBatchSize;
        _defaultTimeout = PropertiesHelper.GetInt32(Cfg.Environment.CommandTimeout, Cfg.Environment.Properties, -1);

        _currentBatch = CreateConfiguredBatch();
        //we always create this, because we need to deal with a scenario in which
        //the user change the logging configuration at runtime. Trying to put this
        //behind an if(log.IsDebugEnabled) will cause a null reference exception 
        //at that point.
        _currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:");
    }

    public override int BatchSize
    {
        get { return _batchSize; }
        set { _batchSize = value; }
    }

    protected override int CountOfStatementsInCurrentBatch
    {
        get { return _currentBatch.CountOfCommands; }
    }

    public override void AddToBatch(IExpectation expectation)
    {
        _totalExpectedRowsAffected += expectation.ExpectedRowCount;
        IDbCommand batchUpdate = CurrentCommand;
        Driver.AdjustCommand(batchUpdate);
        string lineWithParameters = null;
        var sqlStatementLogger = Factory.Settings.SqlStatementLogger;
        if (sqlStatementLogger.IsDebugEnabled || Log.IsDebugEnabled)
        {
            lineWithParameters = sqlStatementLogger.GetCommandLineWithParameters(batchUpdate);
            var formatStyle = sqlStatementLogger.DetermineActualStyle(FormatStyle.Basic);
            lineWithParameters = formatStyle.Formatter.Format(lineWithParameters);
            _currentBatchCommandsLog.Append("command ")
                .Append(_currentBatch.CountOfCommands)
                .Append(":")
                .AppendLine(lineWithParameters);
        }
        if (Log.IsDebugEnabled)
        {
            Log.Debug("Adding to batch:" + lineWithParameters);
        }
        _currentBatch.Append((System.Data.SqlClient.SqlCommand) batchUpdate);

        if (_currentBatch.CountOfCommands >= _batchSize)
        {
            ExecuteBatchWithTiming(batchUpdate);
        }
    }

    protected override void DoExecuteBatch(IDbCommand ps)
    {
        Log.DebugFormat("Executing batch");
        CheckReaders();
        Prepare(_currentBatch.BatchCommand);
        if (Factory.Settings.SqlStatementLogger.IsDebugEnabled)
        {
            Factory.Settings.SqlStatementLogger.LogBatchCommand(_currentBatchCommandsLog.ToString());
            _currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:");
        }

        int rowsAffected;
        try
        {
            rowsAffected = _currentBatch.ExecuteNonQuery();
        }
        catch (DbException e)
        {
            throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "could not execute batch command.");
        }

        Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, rowsAffected);

        _currentBatch.Dispose();
        _totalExpectedRowsAffected = 0;
        _currentBatch = CreateConfiguredBatch();
    }

    private SqlClientSqlCommandSet CreateConfiguredBatch()
    {
        var result = new SqlClientSqlCommandSet();
        if (_defaultTimeout > 0)
        {
            try
            {
                result.CommandTimeout = _defaultTimeout;
            }
            catch (Exception e)
            {
                if (Log.IsWarnEnabled)
                {
                    Log.Warn(e.ToString());
                }
            }
        }

        return result;
    }
}

Did I overlook something? Seems to a rather bad approach to copy the whole thing just to override all access to any private members. I just want to override one method!

Community
  • 1
  • 1
cdbeelala89
  • 2,066
  • 3
  • 28
  • 39

4 Answers4

11

There is only one way to legally access private members of your base class: put the derived class inside the base class:

class Base
{
    private int x;
    private class Derived : Base
    {
        private void M()
        {
            Console.WriteLine(this.x); // legal!
        }
    }
}

Of course, if you could put the class inside the base class then you could also rewrite the base class so that the members were protected.

That the original author made the members private is a hint to you that the class was not designed for you to muck around with that data.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
4

If they're set as private, there's really nothing (short of using Reflection, which is ugly and certainly not always safe) that you can do.

Justin Niessner
  • 242,243
  • 40
  • 408
  • 536
1

Private members of a superclass cannot be accessed, bcause they are private. Encapsulation in OOP is there to prohibit this direct access and so ensure that objects function properly.
There might be properties to access the private members, These are the ones you can use to read from/write to private members. The properties will ensure that no harm to the object will be done.

bash.d
  • 13,029
  • 3
  • 29
  • 42
1

You can access private fields, properties and methods of a parent class using reflection (for example, accessing a field as described here: Reflecting a private field from a base class)

This is not safe, however as the idea of private is that the library implementation could change and those private methods, fields and properties could change or disappear. If they change the implementation, an update could break your code.

That said, I've done it a few times myself. You just need to weigh the risk.

Community
  • 1
  • 1
Pete
  • 6,585
  • 5
  • 43
  • 69