1

The Problem

I have some code which I've coded in such a way to enable high maintainability and code re-usability. I am concerned about a particular piece of code and I would like a professional opinion on if this code will crumble under high stress.

The Codez ##

public abstract class PlexxisDataTransferObjects : PlexxisDatabaseRow
    {
        //static methods
        public static List<PlexxisDatabaseRow> GetAll();

        //Constructors
        public PlexxisDataTransferObjects(){ }

        //Methods
        public abstract bool Insert(OracleConnection Conn);
        public abstract bool Update(OracleConnection Conn);
        public abstract bool Delete(OracleConnection Conn);

        public bool Insert()
        {
            using (var Conn = new OracleConnection(ConnectionString))
            {
                Conn.Open();
                return Insert(Conn);
            }
        }
        public bool Update()
        {
            using (var Conn = new OracleConnection(ConnectionString))
            {
                Conn.Open();
                return Update(Conn);
            }
        }
        public  bool Delete()
        {
            using (var Conn = new OracleConnection(ConnectionString))
            {
                Conn.Open();
                return Delete(Conn);
            }
        }
    }


    //Data Transfer Objects
    public sealed class Apps : PlexxisDataTransferObjects
    {
        //Static Methods
        public override static List<PlexxisDatabaseRow> GetAll()
        {
            List<PlexxisDatabaseRow> collection = new List<PlexxisDatabaseRow>();
            using (var Conn = new OracleConnection(ConnectionString))
            {
                using (var Command = new OracleCommand("select * from APPS", Conn))
                {
                    Conn.Open();
                    using (var reader = Command.ExecuteReader(CommandBehavior.CloseConnection))
                        while (reader.Read())
                            collection.Add(new Apps(reader));
                }
            }
            return collection;
        }

        //Fields
        public int AppId;
        public string AuthKey;
        public string Title;
        public string Description;
        public bool isClientCustomApp;

        //Constructors
        public Apps() : base () { }
        public Apps(OracleDataReader reader) : base ()
        {
            if (reader["APP_ID"] != DBNull.Value)
                this.AppId = Convert.ToInt32(reader["APP_ID"]);

            if (reader["AUTH_KEY"] != DBNull.Value)
                this.AuthKey = Convert.ToString(reader["AUTH_KEY"]);

            if (reader["TITLE"] != DBNull.Value)
                this.Title = Convert.ToString(reader["TITLE"]);

            if (reader["DESCRIPTION"] != DBNull.Value)
                this.Description = Convert.ToString(reader["DESCRIPTION"]);

            if (reader["IS_CLIENT_CUSTOM_APP"] != DBNull.Value)
                this.isClientCustomApp = Convert.ToBoolean(reader["IS_CLIENT_CUSTOM_APP"]);
        }

        //Methods
        public override bool Insert(OracleConnection Conn)
        {

            string sql = string.Empty;
            sql += "INSERT INTO APPS (APP_ID, AUTH_KEY, TITLE, DESCRIPTION, IS_CLIENT_CUSTOM_APP)";
            sql += "VALUES(:appid, :authkey, :title, :description, :iscust)";

            using (var Command = new OracleCommand(sql, Conn))
            {
                AppId = GetId();
                Command.Parameters.Add(":appid", OracleDbType.Int32).Value = AppId;
                Command.Parameters.Add(":authkey", OracleDbType.Varchar2).Value = AuthKey;
                Command.Parameters.Add(":title", OracleDbType.Varchar2).Value = Title;
                Command.Parameters.Add(":description", OracleDbType.Varchar2).Value = Description;
                Command.Parameters.Add(":iscust", OracleDbType.Int32).Value = Convert.ToInt32(isClientCustomApp);

                return Convert.ToBoolean(Command.ExecuteNonQuery());
            }
        }
        public override bool Update(OracleConnection Conn)
        {
            string sql = string.Empty;
            sql += "UPDATE APPS SET ";
            sql += "AUTH_KEY = :authkey, TITLE = :title, DESCRIPTION = :description, IS_CLIENT_CUSTOM_APP = :iscust ";
            sql += "WHERE APP_ID = :appid";

            using (var Command = new OracleCommand(sql, Conn))
            {
                Command.Parameters.Add(":authkey", OracleDbType.Varchar2).Value = AuthKey;
                Command.Parameters.Add(":title", OracleDbType.Varchar2).Value = Title;
                Command.Parameters.Add(":description", OracleDbType.Varchar2).Value = Description;
                Command.Parameters.Add(":iscust", OracleDbType.Int32).Value = Convert.ToInt32(isClientCustomApp);
                Command.Parameters.Add(":appid", OracleDbType.Int32).Value = AppId;

                return Convert.ToBoolean(Command.ExecuteNonQuery());
            }
        }
        public override bool Delete(OracleConnection Conn)
        {
            string sql = string.Empty;
            sql += "DELETE FROM APPS ";
            sql += "WHERE APP_ID = :appid";

            using (var Command = new OracleCommand(sql, Conn))
            {
                Command.Parameters.Add(":appid", OracleDbType.Int32).Value = AppId;
                return Convert.ToBoolean(Command.ExecuteNonQuery());
            }
        }
    }

What Am I looking at?

What concerns me the most is the Insert, Update and Delete method in the abstract class calling the Insert, Update and Delete in the concrete class.

I've done it this way so that I could enable transactions if necessary by opening a connection and explicitly starting a transaction, send the transaction and still have the objects do what they need to do; furthermore, if I had to explicitly rewrite the 3 methods for 40 or so classes, it could become quite cumbersome.

However, I'm concerned that by opening the connection early that I might be holding up the database. I don't know how much input data that might be being updated at any given time. In this situation I have two major thoughts, I can either make the insert, update and delete abstract in the abstract class and implement them while explicitly opening the connection immediately before the Command.ExecuteNonQuery() or I can leave it how it is now.

What would I like from you?

First and foremost, your opinion on the situation. Secondly pointing out any pitfalls behind the logic or any bad coding that you happen to spot would also be very helpful.

Aelphaeis
  • 2,593
  • 3
  • 24
  • 42

2 Answers2

3

I think this might be worthwhile to investigate the unit-of-work pattern.

To me it looks like you're already using the active-record pattern, however I find it problematic (from a separation of concerns and dependency perspective) that your class definitions are hard-coded to be dependent on oracle, which means the code that uses your DTOs must also be dependent on oracle. I'm not suggesting this is a problem in case you want to switch your database, I am saying that it is best practice to have a very decoupled system, both for understanding and unit-testing.

Database agnostic code

class Application
{
    public int ID { get; set; }
    public string AuthKey { get; set; }
    // and so on
}

interface IApplicationRepository
{
    IEnumerable<Application> GetAll();
    void Update(Application app);
    void Delete(Application app);
    void Insert(Application app);
}

interface IUnitOfWork : IDisposable
{
    IApplicationRepository Applications { get; }
    void Commit();
}

Consuming code

void SaveButton_Click(object sender, EventArgs e)
{
    // this could be resolved by dependency injection, this would know about Oracle
    using (var uow = UnitOfWorkFactory.Create()) 
    {
        uow.Applications.Insert(new Application { AuthKey = "1234" });

        // you may have other repo that have work done in the same transaction / connection

        uow.Commit();
    }
}

If you look at all the code that is written above, there is no mention of Oracle, or even of a concept of connections or transactions. You have this abstraction called UnitOfWork which behind the scenes manages state for your application. The repository works with plain classes. This type of code is easy to mock and write tests for. This is huge for maintainability.

Database specific code

class OracleApplicationRepository : IApplicationRepository
{
    public readonly OracleDbConnection _dbConnection;

    public OracleApplicationRepository(OracleDbConnection dbConnection)
    {
        _dbConnection = dbConnection;
    }

    IEnumerable<Application> GetAll()
    {
        // up to you, the viewer
        throw new NotImplementedException();
    }

    void Update(Application app)
    {
        // up to the viewer
    }

    void Delete(Application app)
    {
        // up to the viewer
    }

    void Insert(Application app)
    {       
        using (var command = _dbConnection.CreateCommand())
        {
            // or whatever the syntax is
            command.Parameters["AuthKey"] = app.AuthKey;

            command.ExecuteNonQuery();
        }
    }
}

class OracleUnitOfWork : IUnitOfWork
{
    private readonly OracleDbConnection _dbConnection;

    public OracleUnitOfWork(string connectionString)
    {
        _dbConnection = new OracleDbConnection(connectionString);
    }

    public IApplicationRepository Applications 
    {
        get
        {
            // this could be lazy loaded instead of making new instances all over the place
            return new OracleApplicationRepository(_dbConnection); 
        }
    }

    public Dispose()
    {
        // close the connection and any transactions
        _dbConnection.Dispose();
    }
}
Matthew
  • 24,703
  • 9
  • 76
  • 110
  • Okay, I understand the IApplicationRepo but I'm not understand how the unit of work ties into this. I checked out http://www.codeproject.com/Articles/581487/Unit-of-Work-Design-Pattern but its not quite clicking yet. Different people can be making changes to the database simultaneously, so in some cases I may want to rollback some peoples changes and keep others. Can this pattern accomplishment this? – Aelphaeis Sep 10 '13 at 19:10
  • The Unit-Of-Work is a concept for a task, if your task is to create a new `Application`, then you might have to use multiple repositories (multiple inserts, updates, and deletes) in one transaction to make this change atomic. You wouldn't share a UoW between people, a UnitOfWork is typically one person doing one task (although one task may do many database operations). – Matthew Sep 10 '13 at 19:13
  • Is it sensible to put an IApplicationRepo in the instance is being output so that the instance could write itself? I completely see how the Repo. It is meaningful for me to be able to say ApplicationInstance.Insert() – Aelphaeis Sep 10 '13 at 19:18
  • This is where I find the active-record pattern is problematic. Suppose you were to write code to save a person, if you were telling a person in real life, you wouldn't tell them to Save themselves. In my opinion, the person should not have the responsibility of persisting itself. If a person were to have a Save method, it would be locked into one way of saving them, which in itself forces you to couple it to another system. – Matthew Sep 10 '13 at 19:35
  • These objects map 1:1 with a particular database, The fields inside these objects are the columns in the database. Every column in the database is within these objects and these objects are all within another class Called Data, so in order to access App for example, you need to say Data.App a = new Data.App(). These objects are only intended for internal use for a particular application but the internal functionality of always changing. I am not able to use an ORM (Because of politics) and this sort of replaces that functionality. Just to put some context to things. – Aelphaeis Sep 10 '13 at 19:46
  • Is this a question? I don't understand your statement. As for not using an ORM, while it's ridiculous to not be able to use one because of political reasons, I don't think it's a reason you shouldn't write any object oriented code. The example I was giving you, however was to help with separation of concerns. In your example, the code to save a person was so tightly coupled to the database that it was even creating a database connection. Why should any sort of domain object need to know how to connect to the database? – Matthew Sep 10 '13 at 19:59
  • I was just adding additional information. I'll spend some more time thinking about it cause I'm a little murky on it. I've actually never even heard of that pattern before now; however, I feel that the objects themselves represent the database more so than the entities. There are other entities which present this data which are more OO structured. – Aelphaeis Sep 10 '13 at 20:04
  • 1
    Looking back at this, Thank you very much. You really helped me out with this! – Aelphaeis May 29 '14 at 18:54
0

Opening and closing a database connection for each CRUD operation will hurt if you are looping over a large set of data. Also, you should use try,catch,and finally. You can't guarantee the database will be up and it will cause an exception to be thrown.

Justin
  • 6,373
  • 9
  • 46
  • 72
  • I disagree here but I'd like you to elaborate. I think that exception should as this would not be the place for error handling as its intended to be rather generic. – Aelphaeis Sep 10 '13 at 18:58
  • @Aelphaeis, yes I agree. I just wanted to make sure that exception handling was used somewhere. – Justin Sep 10 '13 at 19:00