Ecco la situazione: Ho un assembly "comune" di accesso ai dati che contiene classi utilizzate in tutti i miei progetti. Alcuni di questi sono classi astratte che vengono implementate solo dai miei livelli di accesso ai dati per ciascun progetto.
Nei miei progetti ho un approccio a più livelli: accesso ai dati separato, livello aziendale e interfaccia utente. Le mie classi di accesso ai dati possono ereditare dalle classi astratte in Comune. Queste classi astratte contengono un metodo "execute".
Nel mio livello aziendale del progetto faccio riferimento solo al livello di accesso ai dati del progetto - Non faccio riferimento ad altri progetti o all'assemblea comune. Ma una volta che ho messo le mie classi astratte che sono sempre riutilizzate in Common, il mio livello aziendale non può più chiamare il metodo "Execute" senza avere un riferimento al comune.
Spero che non sia troppo confuso.
Se non voglio molte interdipendenze tra gli assembly, ho bisogno di spostare le classi astratte nel livello di accesso ai dati di ogni progetto. Ma poi ho ripetuto il codice e il comportamento potenzialmente incoerente tra i progetti.
Ma se continuo così com'è, tutti i miei livelli aziendali devono essere in grado di accedere a questo assembly di accesso ai dati comune, che sembra sbagliato.
Qualche idea su questa architettura?
So che alcuni potrebbero provare a dire "usa Entity Framework" o qualche altro ORM. Ma i miei progetti non sono abbastanza complessi da garantire un sovraccarico, soprattutto considerando la necessità di prestazioni veloci. Ho scoperto che un semplice framework di mia proprietà che implementa direttamente ADO.Net è notevolmente più veloce. Quindi, per favore, basta avvisarmi sulla separazione dei problemi di preoccupazione e ereditarietà e non cercare di convincermi ad aggiungere un ORM.
Codice di esempio:
In Common: classe base
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: seconda classe di base (ho sia una ricerca che una versione di salvataggio, con la versione di salvataggio che consente facoltativamente le transazioni.)
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
Un esempio di implementazione molto semplice di un'implementazione del livello di accesso ai dati:
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
Il problema sarebbe venuto quando nel livello aziendale del mio progetto avrei fatto qualcosa di simile:
Dim srch as new IpBlackListSearch(connstring, providername, "12.12.12.12.")
srch.execute
srch.Execute può essere compilato solo se il livello aziendale fa riferimento all'assembly di accesso ai dati comune.
Sembra dai commenti che non c'è niente di sbagliato nel mio livello aziendale contenente quel riferimento.