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.