Results 1 to 6 of 6

Thread: General approach to databases

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    General approach to databases

    Hi,

    I am often asked to create a very simple database front-end for friends or family, and the structure of the database (and application) is usually very similar.

    Right now I am always creating a class that handles all database interactions with some shared functions. For example, there's a function to run a general query and return a table, a function to save/load various items (depending on the application of course). The point is: the code is nearly always the same except for some slight variations.

    For example, I was asked to create an application that manages a load of pictures. So, I wrote the class and made a function to load an image (which in turn calls the function to execute a query with the correct SQL query). I made a function to save an image to the database, etc.
    A short while later, I was asked to create an application that manages a load of documents. The principle was exactly the same except instead of images there was now documents...

    To cut a long story short; I am getting tired of writing (or copy/pasting) the same class every time, I'm sure there must be a way to get this 'automated'.



    So I've been trying this for some time, but haven't even gotten close.

    Right now, I have a class (Database) that I simply pass the location of an Access database file to. The important part is that it maintains an OleDbConnection object, and has some functions like ExecuteScalar, ExecuteNonQuery, etc, which basically accept a string and run the query.


    My goal now is that, for each similar application, I can create a class that is simply a 'container' of all attributes of the corresponding database table, which it exposes through properties.
    For example, suppose I have a simple table 'Person' with attributes ID, NAME and AGE, the class would look like this:
    vb.net Code:
    1. Public Class PersonEntity
    2.  
    3.     Private _Id As Integer
    4.     Public Property Id() As Integer
    5.         Get
    6.             Return _Id
    7.         End Get
    8.         Set(ByVal value As Integer)
    9.             _Id = value
    10.         End Set
    11.     End Property
    12.  
    13.     Private _Name As String
    14.     Public Property Name() As String
    15.         Get
    16.             Return _Name
    17.         End Get
    18.         Set(ByVal value As String)
    19.             _Name = value
    20.         End Set
    21.     End Property
    22.  
    23.     Private _Age As Integer
    24.     Public Property Age() As Integer
    25.         Get
    26.             Return _Age
    27.         End Get
    28.         Set(ByVal value As Integer)
    29.             _Age = value
    30.         End Set
    31.     End Property
    32.  
    33. End Class
    Each property corresponds to a table attribute. I suppose I need some way to connect the properties with the attributes; perhaps via a custom property Attribute like so?
    vb.net Code:
    1. Private _Name As String
    2.     <DatabaseField("NAME")> _
    3.     Public Property Name() As String
    4.         Get
    5.             Return _Name
    6.         End Get
    7.         Set(ByVal value As String)
    8.             _Name = value
    9.         End Set
    10.     End Property

    I created an interface IEntity that requires the class to implement at least an ID property (more might be added later).


    Anyway, besides this class (there might be many of these classes of course), I will also need a 'manager' class that manages loading and saving of instances of these classes.

    My 'vision' is that I could do this:
    vb.net Code:
    1. Dim db As New Database("file here")   'my Database class
    2. Dim pm As New PersonManager(db)
    3.  
    4. Dim pe As PersonEntity = pm.LoadById(3)
    5. pe.Name = "John"
    6. pe.Age = 35
    7. pm.Save(pe)
    what this should do is create a new PersonManager, which manages PersonEntities. Then the LoadById function returns a new PersonEntity object. This object will have its properties populated by running a database query ("select * from Persons where ID = 3" or whatever). Then, the Name and Age property are modified, and the new object is saved back to the database.


    Obviously this should work without me having to write the LoadById and Save functions manually for each and every Manager. The goal is that I have a base manager (possibly a generic class?) which would automatically implement the load and save functions.
    The base manager would look something like this
    vb.net Code:
    1. Public MustInherit Class TableManager(Of T As IEntity)
    2.  
    3.     Public Sub New(ByVal db As Database)
    4.         _Database = db
    5.     End Sub
    6.  
    7.     Private _Database As Database
    8.     Public ReadOnly Property Database() As Database
    9.         Get
    10.             Return _Database
    11.         End Get
    12.     End Property
    13.  
    14.     Public Function Load(ByVal id As Integer) As T
    15.         '???
    16.     End Function
    17.  
    18.     Public Sub Save(ByVal entity As T)
    19.         '???
    20.     End Sub
    21.  
    22. End Class
    (Note that T is constrained, it must implement IEntity)

    And the PersonManager class would then simply be
    vb.net Code:
    1. Public Class PersonManager
    2.     Inherits TableManager(Of PersonEntity)
    3.  
    4.     Public Sub New(ByVal db As Database)
    5.         MyBase.New(db)
    6.     End Sub
    7.  
    8. End Class


    The obvious problem: what to do with the Load and Save methods?

    I am running into a few 'major' problems when trying to implement them and starting to think it may not even be possible*...

    This is how I imagined it, but it's obviously not going to work that easy:
    vb.net Code:
    1. Public Function Load(ByVal id As Integer) As T
    2.         Dim t As New T
    3.  
    4.         Dim reader As IDataReader = Me.Database.ExecuteReader("select * from ??? where ID = " & id)
    5.  
    6.         While reader.Read
    7.             t.Id = id
    8.             t.??? = ???
    9.             t.??? = ???
    10.         End While
    11.  
    12.         Return t
    13.     End Function

    Problems:
    1. I don't know the table name. This could be solved by supplying the table name in the constructor I suppose.
    2. a) I don't know which attributes the reader will be reading except the ID
    2. b) I don't know which properties T has.
    (3. I cannot create an instance of T, but I am sure I did this before; I just need to constrain T so that it must have a constructor I believe?)




    Now, for problem 2... The Entity classes (such as PersonEntity) have a list of properties which correspond to database fields. The name of those fields is listed in the Attribute above the property. I am pretty sure I could use reflection or something to retrieve a list of the properties and their attributes. Then, I would have a list of the properties and theoretically I should be able to set their values. I can load the values from the database because I also know the field names.

    But how to do this?


    I suppose I could also do it without generics, where the Load function returns an IEntity object (and the save function accepts one), but the problem then is even worse: I cannot create an instance of IEntity, but I cannot know which type I should create a new instance of.
    I could supply the type in the constructor though, so that would yield something like this
    vb.net Code:
    1. Public MustInherit Class TableManager
    2.  
    3.     Private _t As Type
    4.  
    5.     Public Sub New(ByVal db As Database, ByVal t As Type)
    6.         _Database = db
    7.         _t = t
    8.     End Sub
    9.  
    10.     Private _Database As Database
    11.     Public ReadOnly Property Database() As Database
    12.         Get
    13.             Return _Database
    14.         End Get
    15.     End Property
    16.  
    17.     Public Function Load(ByVal id As Integer) As IEntity
    18.         Dim t As New 'instance of type _t   how???
    19.  
    20.         Dim reader As IDataReader = Me.Database.ExecuteReader("select * from ??? where ID = " & id)
    21.  
    22.         While reader.Read
    23.             t.Id = id
    24.             t.??? = ???
    25.             t.??? = ???
    26.         End While
    27.  
    28.         Return t
    29.     End Function
    30.  
    31.     Public Sub Save(ByVal entity As IEntity)
    32.         '???
    33.     End Sub
    34.  
    35. End Class
    and the PersonManager would be
    vb.net Code:
    1. Public Class PersonManager
    2.     Inherits TableManager
    3.  
    4.     Public Sub New(ByVal db As Database)
    5.         MyBase.New(db, GetType(PersonEntity))
    6.     End Sub
    7.  
    8. End Class


    I think that's enough for a single post... I'm sure many more problems will arise, but I hope someone can at least give me some ideas.



    *However, I am sure it is possible in some way. I used to do a little work for some company and they used this exact 'structure' (that's where I got the idea in the first place). I could ask them how they did it I suppose, but I doubt they would tell me. I could use Reflector to take a peek myself but somehow that feels much more like stealing then asking advice on a forum. If you disagree please let me know. I wouldn't feel comfortable stealing their code and using it as my own. I don't have this problem though if I merely explain the idea and ask others for help, right?

    They used the last approach I mentioned though, passing the type to the constructor, without generics. Not sure if that matters.

  2. #2
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    40,106

    Re: General approach to databases

    This is a problem that people have wrassled with for a long time: Interactions with databases tend to fall into repetitive patterns of boringly similar code.

    This realization has prompted numerous efforts to automate the functionality. I am currently working with a template based system written with a guy I work with. It's a pretty impressive piece of work, to be sure, but it has strengths and weaknesses in its design. I have felt for quite some time that the DAL and BO layers (overkill for a simple front end, but with this system you can build them in a matter of only a few minutes) were solid and functional. They did have a tendency to be overwrought, and not as efficient as I would like, but since they got the job done with no noticeable delay, does it really matter that I could have written the few hundred lines of code so that they performed 50-80&#37; faster? It would have taken me hours to write those lines, whereas I generated them in less than fifteen minutes (and maybe less than 2 minutes) of effort, and if I got the business objects (essentially a wrapper around a datatable, in this case) wrong, I could change and re-generate in seconds. Can't do that by hand.

    The drawback is that he went a bit too far. The program generates the entire program including the interface. Whenever you generate an interface, you constrain the design and layout in some way, and that was the greatest weakness of this program. Fortunately, it could handle custom code, and I wrote lots of it to make the interface work in the most useful way. The other problem was more nebulous...and irrelevant, so I think I get off this tangent by summing up with: The program generated a good database interaction layer, but it took a fair amount of effort to build that generator.

    Recently, I have started into the Entity Framework, and that is something you should really look into before you go too far down this road. The EF is something MS has put out to solve pretty much the same problem. Building an entity model using the EF is quick to learn, and quick to accomplish. Doing something with that can take a bit more effort. However, it appears (I haven't gotten into it much, and keep getting drawn away to other things) that for simple apps it might give you a nice, relatively generic, means of interacting with the database through the entity objects.
    My usual boring signature: Nothing

  3. #3
    Frenzied Member brin351's Avatar
    Join Date
    Mar 2007
    Location
    Land Down Under
    Posts
    1,293

    Re: General approach to databases

    It sounds to me that because you want to be able to do this

    Code:
    My 'vision' is that I could do this:
    
    Dim db As New Database("file here")   'my Database class
    Dim pm As New PersonManager(db) 
    Dim pe As PersonEntity = pm.LoadById(3)
    pe.Name = "John"
    pe.Age = 35
    pm.Save(pe)
    You need to create a class for every table, storedProc etc in the database which seams ideal for a code generator to do for you even if you setup the generation templates yourself.

    OR it's sort of asking for intelligence / intellisence about your database from your development environment which would be a great addin to VS.

    On the other hand I sometimes prefer not to go down the "typed" data classes path because with simple DAL's like you use and a simple database like you often have it's not difficult to remember the table name and queries etc to pass to your DAL and I like the flexibility when the client says can you change that to this.

    This is just my limited opinion. Here's a link by jmcilhinney about the DbProviderFactory class but you probably already know about that

    http://jmcilhinney.blogspot.com/2009...ess-layer.html

  4. #4

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Re: General approach to databases

    Quote Originally Posted by brin351 View Post
    It sounds to me that because you want to be able to do this

    Code:
    My 'vision' is that I could do this:
    
    Dim db As New Database("file here")   'my Database class
    Dim pm As New PersonManager(db) 
    Dim pe As PersonEntity = pm.LoadById(3)
    pe.Name = "John"
    pe.Age = 35
    pm.Save(pe)
    You need to create a class for every table, storedProc etc in the database which seams ideal for a code generator to do for you even if you setup the generation templates yourself.

    OR it's sort of asking for intelligence / intellisence about your database from your development environment which would be a great addin to VS.

    On the other hand I sometimes prefer not to go down the "typed" data classes path because with simple DAL's like you use and a simple database like you often have it's not difficult to remember the table name and queries etc to pass to your DAL and I like the flexibility when the client says can you change that to this.

    This is just my limited opinion. Here's a link by jmcilhinney about the DbProviderFactory class but you probably already know about that

    http://jmcilhinney.blogspot.com/2009...ess-layer.html
    Yes, I would need a class for every table in the database, and that class is basically an 'enumeration' of the attributes. It could be generated entirely automatically by looking at the attribute names and types, but that is a different story. Writing the entity classes is not so much work (and probably, writing a generator would mean more work).

    I did not know about the DbProviderFactory class, I'll look into it, thanks.

    Quote Originally Posted by Shaggy Hiker View Post
    This is a problem that people have wrassled with for a long time: Interactions with databases tend to fall into repetitive patterns of boringly similar code.

    This realization has prompted numerous efforts to automate the functionality. I am currently working with a template based system written with a guy I work with. It's a pretty impressive piece of work, to be sure, but it has strengths and weaknesses in its design. I have felt for quite some time that the DAL and BO layers (overkill for a simple front end, but with this system you can build them in a matter of only a few minutes) were solid and functional. They did have a tendency to be overwrought, and not as efficient as I would like, but since they got the job done with no noticeable delay, does it really matter that I could have written the few hundred lines of code so that they performed 50-80&#37; faster? It would have taken me hours to write those lines, whereas I generated them in less than fifteen minutes (and maybe less than 2 minutes) of effort, and if I got the business objects (essentially a wrapper around a datatable, in this case) wrong, I could change and re-generate in seconds. Can't do that by hand.

    The drawback is that he went a bit too far. The program generates the entire program including the interface. Whenever you generate an interface, you constrain the design and layout in some way, and that was the greatest weakness of this program. Fortunately, it could handle custom code, and I wrote lots of it to make the interface work in the most useful way. The other problem was more nebulous...and irrelevant, so I think I get off this tangent by summing up with: The program generated a good database interaction layer, but it took a fair amount of effort to build that generator.
    That sounds a lot like what I am trying to do (except the interface, I want to do that manually). As said above, the classes could be generated entirely by simply looking at all attributes.

    I do realize that performance may suffer slightly, because I'd probably need reflection to pull apart the classes and iterate through their properties or whatever. But performance is no issue in my case, the applications and databases are so simple that it simply doesn't matter. Since the applications are not meant to be complicated, it is much more important to me that I can spent all my time on designing the interface (to make it as clear and easy as possible), instead of having to spent 80% on writing the same code over and over except with 'picture' replaced by 'document'.

    Quote Originally Posted by Shaggy Hiker View Post
    Recently, I have started into the Entity Framework, and that is something you should really look into before you go too far down this road. The EF is something MS has put out to solve pretty much the same problem. Building an entity model using the EF is quick to learn, and quick to accomplish. Doing something with that can take a bit more effort. However, it appears (I haven't gotten into it much, and keep getting drawn away to other things) that for simple apps it might give you a nice, relatively generic, means of interacting with the database through the entity objects.
    Looks promising, I'll look into this more when I get some more time.



    I don't want to make this too complicated, if possible. As said, the applications are just for friends and family, and while I enjoy helping them out, I'm getting tired of it being so much 'dull work'. If my 'vision' would work, then it would be much, MUCH faster. All I need to do is design the database (it being so simple usually, that will take 2 minutes), then write the entity classes (another 2 minutes) and finally the manager class (3 seconds, as it's just the same every time only a different generic type). Then I could start concentrating on the interface already, and even that would be simplified. Loading and saving records would be a few lines of code, instead of writing queries and sending them to my functions, etc.

    For example, when I want to let the user edit a record, I would show a dialog form and pass the ID to its constructor. In that constructor, i write a query that retrieves the data and then populate the form. When OK is pressed, I write a new query to save the new data.

    Now, I could just do this
    Code:
    Dim id As Integer = GetSelectedId()  'returns some id
    Dim pe As PersonEntity = personManager.LoadById(id)
    Using frm As New EditForm(pe)
       If frm.ShowDialog = DialogResult.OK Then
          personManager.Save(frm.PersonEntity)
       End If
    End Using
    Now, the constructor takes a PersonEntity object, and it can simply populate its textboxes etc using the properties from that object. When OK is pressed it saves the new data back to the PersonEntity object and exposes it via a property, so I can save it using the same Manager object I used to load it.

    I can do all this already, obviously, but then I would have to write the PersonManager and PersonEntity classes completely by scratch every single time, for every application, and that kinda defeats the purpose. What I'm looking for is a way to create base classes I can inherit which already implement most of the functionality, like loading and saving. A base class for the PersonEntity class wouldn't really do much good (right now I think an interface with a property ID and Delete or something would be enough), but a base class for the PersonManager would have to do all the loading and saving, and that's where I'm having trouble, as this basically goes against OOP design I guess. The base class would have to know what kind of Entity object it is dealing with. Right now I can only tell it that it implements IEntity, but that isn't any help. I can pass the type it needs via the constructor or via generics, but then I still have problems. For example, I would need to be able to create an instance of that type, but I will only know it at run-time...

    EDIT
    I just found I can create an instance at run-time by using
    Code:
    Dim inst As T = CType(Activator.CreateInstance(GetType(T)), T)
    but then I still have further problems, because all I know is that T implements IEntity, while I need to know all of its properties...

  5. #5

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Re: General approach to databases

    EDIT: Updated.

    Well I've gotten a tiny bit further I think. I could remember from a very different project that I once had to get a property by its name to set its value. So I looked it up and this is what I came up with:
    vb.net Code:
    1. Public Function Load(ByVal id As Integer) As T
    2.         Dim type As Type = GetType(T)
    3.         Dim inst As T = Nothing
    4.  
    5.         'Get name of table from type attributes
    6.         Dim tableName As String = Me.GetTableName(type)
    7.         If tableName <> String.Empty Then
    8.  
    9.             'Create instance
    10.             inst = CType(Activator.CreateInstance(type), T)
    11.  
    12.             'Get Data Reader with data from correct table
    13.             Dim params As New Dictionary(Of String, Object)
    14.             params.Add("@ID", id)
    15.             Dim reader As IDataReader = Me.Database.ExecuteReader("select * from " & tableName & " where ID = @ID", params)
    16.  
    17.             If reader.Read Then
    18.                 inst.Id = id
    19.                 inst.Deleted = (Me.Database.ToInt(reader("DELETED_CD")) = 1)
    20.  
    21.                 'Loop through all properties of T
    22.                 Dim fieldName As String
    23.                 For Each prop As PropertyInfo In type.GetProperties(BindingFlags.DeclaredOnly) '
    24.                     'Get field name from property attributes
    25.                     fieldName = Me.GetFieldName(prop)
    26.                     If fieldName IsNot Nothing Then
    27.                         'Set its value to the value in the data reader
    28.                         prop.SetValue(inst, reader(fieldName), Nothing)
    29.                     Else
    30.                         Me.Database.LogError("Missing FieldNameAttribute on property '" & prop.Name & "'.")
    31.                     End If
    32.                 Next
    33.             End If
    34.         Else
    35.             Me.Database.LogError("Missing TableNameAttribute on type '" & type.Name & "'.")
    36.         End If
    37.  
    38.         Return inst
    39.     End Function
    40.  
    41.     Private Function GetTableName(ByVal type As Type) As String
    42.         Dim tableName As String = String.Empty
    43.         Dim tableNameAttr As TableNameAttribute
    44.         For Each attr As Attribute In type.GetCustomAttributes(False)
    45.             tableNameAttr = TryCast(attr, TableNameAttribute)
    46.             If tableNameAttr IsNot Nothing Then
    47.                 tableName = tableNameAttr.TableName
    48.             End If
    49.         Next
    50.         Return tableName
    51.     End Function
    52.  
    53.     Private Function GetFieldName(ByVal prop As PropertyInfo) As String
    54.         Dim fieldName As String = String.Empty
    55.         Dim fieldNameAttr As FieldNameAttribute
    56.         For Each attr As Attribute In prop.GetCustomAttributes(False)
    57.             fieldNameAttr = TryCast(attr, FieldNameAttribute)
    58.             If fieldNameAttr IsNot Nothing Then
    59.                 fieldName = fieldNameAttr.FieldName
    60.             End If
    61.         Next
    62.         Return fieldName
    63.     End Function

    It creates an instance of T by using the Activator.CreateInstance method. Then it reads the data (still need to find table name) into a data reader.

    Then, I use the Type.GetProperties method to get a collection of all the properties of T. I found that the PropertyInfo objects have a SetValue method, which I suppose would set the value...? That's what I am assuming anyway. I am retrieving the field names for each property from their custom attributes, and I'm also retrieving the table name from the class attributes.
    Now the Entity class looks like so
    vb.net Code:
    1. <TableName("PERSONS")> _
    2. Public Class PersonEntity
    3.     Implements IEntity
    4.  
    5.     Private _Id As Integer
    6.     Public Property Id() As Integer Implements IEntity.Id
    7.         Get
    8.             Return _Id
    9.         End Get
    10.         Set(ByVal value As Integer)
    11.             _Id = value
    12.         End Set
    13.     End Property
    14.  
    15.     Private _Deleted As Boolean
    16.     Public Property Deleted() As Boolean Implements IEntity.Deleted
    17.         Get
    18.             Return _Deleted
    19.         End Get
    20.         Set(ByVal value As Boolean)
    21.             _Deleted = value
    22.         End Set
    23.     End Property
    24.  
    25.     Private _Name As String
    26.     <FieldName("NAME")> _
    27.     Public Property Name() As String
    28.         Get
    29.             Return _Name
    30.         End Get
    31.         Set(ByVal value As String)
    32.             _Name = value
    33.         End Set
    34.     End Property
    35.  
    36.     Private _Age As Integer
    37.     <FieldName("AGE")> _
    38.     Public Property Age() As Integer
    39.         Get
    40.             Return _Age
    41.         End Get
    42.         Set(ByVal value As Integer)
    43.             _Age = value
    44.         End Set
    45.     End Property
    46.  
    47. End Class

    I am guessing that I'm getting closer, but since I don't really have any way to test this yet I really have no idea whether it works or not.

  6. #6

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Re: General approach to databases

    Well, I just managed to test it quickly, and, after some minor modifications, to my utter amazement it actually works!
    Well, just the LoadById function. The save function would probably bring a whole new load of problems, because I'd have to generate an sql string out of nothing, but let's deal with that once the time comes...

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width