2

Here's the situation: I have a "common" Data Access assembly that contains classes used in all my projects. Some of those are abstract classes that are only implemented by my data access layers for each project.

In my projects I have a layered approach - separated data access, business layer, and UI. My data access classes may inherit from the abstract classes in Common. These abstract classes contain an "execute" method.

In my business layer of the project I only reference the data access layer of the project - I don't reference other projects or the Common assembly. But once I put my abstract classes that are reused all the time into Common, my business layer could no longer call the "Execute" method without having a reference to common.

I hope that's not too confusing.

If I don't want lots of interdependencies between assemblies, I need to move the abstract classes back into each project's data access layer. But then I have repeated code and potentially inconsistent behavior between projects.

But if I keep it as it is, all my business layers need to be able to access this common data access assembly, which seems wrong.

Any thoughts on this architecture?

I know some might try to say "use Entity Framework" or some other ORM. But my projects are not complex enough to warrant that much overhead, especially given the need for fast performance. I have found that a simple framework of my own directly implementing ADO.Net is remarkably faster. So please just advise me on the separation of concerns and inheritance issues and don't try to talk me into adding an ORM.

Example code:

In Common: base class

Public MustInherit Class AbstractDatabaseAction
    Protected Property Factory As DbProviderFactory
    Protected Property Connection As DbConnection
    Protected Property Command As DbCommand
    Protected Property MessageForExceptions As String
    Protected Property ProviderName As String

    Protected Sub New(connString As String, providerName As String, messageForExceptions As String)
        Factory = DbProviderFactories.GetFactory(providerName)

        'set up connection
        Connection = Factory.CreateConnection
        Connection.ConnectionString = connString
        Me.ProviderName = providerName
        'set up command
        Command = Factory.CreateCommand

        Me.MessageForExceptions = messageForExceptions
    End Sub

    Public MustOverride Sub Execute()
    Protected Overridable Sub SetParameters()
        'nothing
    End Sub
    Protected MustOverride Sub SetCommandText()
    Protected Overridable Sub SetCommandType()
        Command.CommandType = CommandType.StoredProcedure
    End Sub

    Protected Sub BuildCommand()
        Command.Connection = Connection
        Me.SetCommandText()
        Me.SetCommandType()
        Me.SetParameters()

        If Me.ProviderName = "Oracle.DataAccess.Client" Then
            OracleSpecificCommandEdits()
        End If
    End Sub

    Protected Overridable Sub OracleSpecificCommandEdits()
        CType(Command, OracleCommand).BindByName = True
    End Sub


End Class

In Common: second base class (I have both a search and a save version, with the save version optionally allowing transactions.)

Public MustInherit Class AbstractSearch
    Inherits AbstractDatabaseAction

    Protected Sub New(connString As String, providerName As String, messageForExceptions As String)
        MyBase.New(connString, providerName, messageForExceptions)
    End Sub

    Public Overrides Sub Execute()
        Try
            Me.BuildCommand()

            Using Connection
                Connection.Open()

                Using Command
                    Try
                        Dim rdr As IDataReader = Command.ExecuteReader
                        Me.fill(rdr)
                        rdr.Close()
                    Catch ex As Exception
                        Throw New Exception(MessageForExceptions & "->Search", ex)
                    End Try
                End Using
            End Using
        Catch ex As Exception
            Throw New Exception(MessageForExceptions & "->Search", ex)
        End Try
    End Sub

    Protected MustOverride Sub fill(ByRef rdr As System.Data.IDataReader)

    Protected Overrides Sub OracleSpecificCommandEdits()
        MyBase.OracleSpecificCommandEdits()

        If TypeOf (Factory) Is OracleClientFactory Then
            Dim p As DbParameter = New OracleParameter
            p.ParameterName = "results"
            p.Direction = ParameterDirection.Output
            CType(p, OracleParameter).OracleDbType = OracleDbType.RefCursor
            Command.Parameters.Add(p)
        End If
    End Sub

Protected Sub AddInParameter(key As String, value As Object)
    Dim p As IDataParameter = Command.CreateParameter
    p.Direction = ParameterDirection.Input
    p.Value = value
    p.ParameterName = key
    Command.Parameters.Add(p)
End Sub

Protected Sub AddOutParameter(key As String, type As System.Data.DbType)
    Dim p As IDataParameter = Command.CreateParameter
    p.Direction = ParameterDirection.Output
    p.DbType = type
    p.ParameterName = key
    Command.Parameters.Add(p)
End Sub

End Class

A very simple implementation example of a data access layer implementation:

Public Class IpBlackListSearch
        Inherits Common.DataAccess.AbstractSearch

        Private Property IPToSearch As String
        Public Property Results As List(Of String) = Nothing

        Public Sub New(connString As String, providerName As String, ipAddressToSearch As String)
            MyBase.New(connString, providerName, "IpAddressSearch")
            Me.IPToSearch = IPToSearch
        End Sub

        Protected Overrides Sub fill(ByRef rdr As System.Data.IDataReader)
            Results = New List(Of String)

            While rdr.Read
                Results.Add(HelperFunctions.NullScrubber(Of String)("ip"))
            End While

        End Sub

        Protected Overrides Sub SetCommandText()
            Command.CommandText = "Get_IPBlacklist"
        End Sub

        Protected Overrides Sub SetParameters()
            MyBase.AddInParameter("in_ip", Me.IPToSearch)
        End Sub
    End Class

The problem would come when in the business layer of my project that would do something like:

Dim srch as new IpBlackListSearch(connstring, providername, "12.12.12.12.")
srch.execute

srch.Execute can only compile if the business layer references the common data access assembly.

It sounds like from the comments that there is nothing wrong with my business layer containing that reference.

2
  • 2
    It seems like your Data Access classes were accessing the common assembly and then your Business layer accessed your Data Access Layer. Now you want to put your "Common" Data Access layer classes into the "Common" Assembly but somehow think it is no longer OK for the business layer to access the DAL. The business layer was already accessing the DAL. So what is different? If you only want to expose your abstract classes then it sounds like they need to go in their own "Common" assembly.
    – Dunk
    Apr 7, 2014 at 15:49
  • that makes perfect sense. Thank you. Posting some example code in a moment per Doval's request, but your argument absolutely is logical to me.
    – user158017
    Apr 7, 2014 at 15:50

3 Answers 3

1

From what I understand your question to be, it seems like your Data Access Layer classes were accessing the Common Assembly and then your Business Layer accessed your Project Specific Data Access Layer.

Now you want to put some of your Common Data Access layer classes into the Common Assembly. You think it is no longer OK for the Business Layer to access the DAL. However, the business layer was already accessing the DAL. So in reality, what is different?

If you only want to expose your abstract classes then it sounds like they need to go in their own Common DAL assembly.

3

Consider using delegates instead of abstract classes. The Template Method pattern is mostly a hack to work around a language's lack of first-class functions. So instead of overriding "execute", pass it in as an argument. That aside, why don't you want dependencies between assemblies? What good is your Common assembly if you're not going to use it?

6
  • my thought was that data access layers should reference a "common" data access assembly but that the business layers should NOT reference a common data access assembly. Is that a misguided thought?
    – user158017
    Apr 7, 2014 at 15:31
  • @user158017 Could you post some example code? It's hard to make such calls in a vacuum.
    – Doval
    Apr 7, 2014 at 15:39
  • 1
    @user:I think the goal is to have uni-directional dependencies (In your case Business Layer depends on Common). Thus, as long as the "Common" assembly doesn't depend on your business layer, which from your description, I don't believe it does then I think you are in good shape.
    – Dunk
    Apr 7, 2014 at 15:54
  • 1
    If you really wanted to separate the Common and Business Layers from depending on each other entirely then you'd need a third assembly to make the connections and use delegates or C# like events to provide the communication interfaces.
    – Dunk
    Apr 7, 2014 at 15:58
  • I have added example code. What you are saying makes sense, though. My option would be to make the reference OR to use delegates. Making a reference is arguably the simple solution.
    – user158017
    Apr 7, 2014 at 16:03
2

This exact problem is why Dependency Inversion Principle exists. This principle says.

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.

In your case, the business layer depends on detail that is data access. To fix this you should create abstraction that is part of business layer that says what kind of data operations the business layer needs to function. The data access layer then realizes this abstraction while inheriting from data access common/base class. This concrete implementation is then created using some kind of factory mechanism, like Abstract Factory pattern or using Dependency Injection.

1
  • very useful information. I'm going to look into it. For now, I'm just making the reference, but this will be very useful for improvements long term.
    – user158017
    Apr 7, 2014 at 17:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.