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.
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, "220.127.116.11.") 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.