Alternative method to save data than my.settings for VB.Net - database

I need to save approximately 1600 different pieces of information for a windows forms application.
What is the best way to go about this? For example some of the user settings would look like this measuringItem1Name, measuringItem1Equation, measuringItem1Enabled, measuringItem1Offset, measuringItem2Name...
I have looked into my.settings, however, I found that it would be very tedious and error prone to type out every user setting.
It seems like the best solution would be to have a type of table that can access each individual cell and edit that information.
Does something like this exist? If so, how would it be implemented with VB.Net?

I'd recommend creating a class with properties to hold your settings. Mark the class as serializable. Then serialize and deserialize an instance of this class as application startup / shutdown to the user's profile directory. This will allow you to have strongly typed settings that save separately per user if you ever need that option. It will also allow you to easily set default values for the settings.
You can make the "settings" class as deep as you want, with properties made up of other sub setting classes or lists of settings by index. It's a very powerful pattern.
For example:
settings.MeasuringItem(1).Enabled = True
settings.MeasuringItem(1).Equation = "1+1"
settings.MeasuringItem(1).Offset = 15
settings.MeasuringItem(2).Enabled = True
settings.MeasuringItem(2).Equation = "1+1"
settings.MeasuringItem(2).Offset = 15
Settings is the main settings class with a Generic.List(of MeasuringItem) property on it. MeasuingItem is another class with .Enabled As Boolean, .Equation as string , etc properties on it.
I haven't really checked this for errors. You will want to do a lot more safety checking, but this should get you started.
<Serializable>
Public Class Settings
Public Sub New()
_measuringItems = New List(Of MeasuringItem)
End Sub
Private _measuringItems As List(Of MeasuringItem)
Public ReadOnly Property MeasuringItems As List(Of MeasuringItem)
Get
Return _measuringItems
End Get
End Property
Public Sub Save()
Using s As New IO.FileStream("Path to save", IO.FileMode.Create)
Dim formatter As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
formatter.Serialize(s, Me)
End Using
End Sub
Public Shared Function Load() As Settings
Dim settings As Settings = Nothing
Using s As New IO.FileStream("Path to load", IO.FileMode.Open)
Dim formatter As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
settings = CType(formatter.Deserialize(s), ServerHost.Settings)
End Using
Return settings
End Function
End Class
<Serializable>
Public Class MeasuringItem
Public Property Enabled As Boolean = True 'These are the default values
Public Property Equation As String = "1+1"
Public Property Offset As Integer = 15
End Class
A quick example of usage:
'Create a completely new settings instance with 100 measuring items
Dim settings1 As Settings = New Settings
For i As Integer = 1 To 100
settings1.MeasuringItems.Add(New MeasuringItem)
Next
settings1.MeasuringItems(0).Enabled = False
settings1.MeasuringItems(5).Equation = "testing"
'Save it
settings1.Save()
'load the saved settings
Dim settings2 As Settings = Settings.Load

I would recommend using a database to store the values in. I don't know your operating environment (for example, I have several enterprise SQL servers available for my use.) You could go with SQL Express Edition, which is free. SQL Lite is an option as well. You could even use Access, but that requires MS Office be installed.
If you don't want to go the database route, you can create a settings class, and make the class serializable . This will serialize the object to either a binary file or a XML file (depending on how you set it up.) If you want to go this approach, I'll post the code for you.

Related

VB.NET ADODB MS SQL Server Open Recordset

After I open a SqlConnection, I try to open a recordset like I did it thousands times before in VBA. Yes, I know that there are differences between VBA and VB.NET, but maybe it's to simple to see it.
Public Class Form1
Const m_cstrCnnString As String = "MyCorrectCnnString"
Dim cnn As SqlConnection
Dim rcs As New ADODB.Recordset
Private Sub MyConnection()
cnn = New SqlConnection(m_cstrCnnString)
cnn.Open()
'so far it works, my cnn.State is 1
rcs.Open("SELECT * FROM dbo.myTable", cnn) 'this line doesn´t work
End Sub
I tried the rcs.Open with and without CursorTypeEnum, LockTypeEnum, but I always get the same error:
System.Runtime.InteropServices.COMException: The arguments are of the wrong type, are out of scope, or are inconsistent with each other.
Background: Win10, Connection to a MS SQL Server Express by VB.NET.
First I import System.Data.SqlClient
Microsoft ActiveX Data Objects 6.1 Library is activated.
Also I tried it with Microsoft ActiveX Data Objects Recordset 6.0 Library.
And I'm new in VB.NET
There's some high-level stuff I need to cover before we can get into the details of what this code should look like.
First, if you're using VB.Net, you should not be opening a RecordSet object. Recordset is from ADO, which is not the same as ADO.Net. You definitely can't use an ADO.Net SqlConnection to open a classic ADO RecordSet. Again: these are two completely different libraries. The classic library exists in .Net only for backwards compatibility and to aid in porting forward old code. It should not be used for new development.
Additionally, when you move forward to ADO.Net you need to be aware of a feature called Connection Pooling. This feature takes care of caching connection objects for you, such that it's counter-productive (uses more RAM and makes things slower) to try to keep a single connection option ready for use throughout the application. Instead, it really is better to create a new connection object for most queries.
Finally, classic ADO had a problem with properly closing resources, such that some of those older applications could occasionally even lock out the database. .Net provides a way to handle this such that you are sure everything is closed and disposed properly and promptly: a Using block.
The weakness of this mechanism is DataReader objects (which replace RecordSet) need the connection to remain open for the life of the reader. This makes it hard to build a good data abstraction to hide all the boiler plate code that goes with data access.
The best pattern I've seen for offering the best of both worlds requires some moderate and advanced VB.Net language features (lambda methods, iterator blocks, and IEnumerable) that are unfamiliar to many traditional VB coders. It's going to require me to go a lot deeper than normal in this answer. That said, once you wrap your head around it, the result is really nice.
To show what this might look like, I need an example that's a little more concrete. We'll pretend you have an Employee table with columns for ID, FirstName, and LastName, and a class Employee with properties of the same name:
Public Class Employee
Public Property ID As Integer
Public Property FirstName As String
Public Property LastName As String
Public Shared Function FromDataRecord(record As IDataRecord) As Employee
Return New Employee() With {
ID = record("ID"),
FirstName = record("FirstName"),
LastName = record("LastName")
}
End Function
End Class
That's right: VB sometimes uses braces now. The Shared method at the end is optional, but it will help make some of what comes next easier to follow. Here is what modern ADO.Net code might look like. Pay special attention to the use of Private and Public
Public Module DB
Private Shared Property ConnectionString As String = "MyCorrectCnnString"
Private Iterator Function GetData(Of T)(SQL As String, transform As Func(Of IDataRecord, T), addParameters As Action(Of SqlParameterCollection)) As IEnumerable(Of T)
Using cn As New SqlConnection(ConnectionString)
Using cmd As New SqlCommand(SQL, cn)
If addParameters IsNot Nothing Then addParameters(cmd.Parameters)
cn.Open()
Using rdr As SqlDataReader = cmd.ExecuteReader()
While rdr.Read()
Yield transform(rdr)
End While
End Using
End Using
End Using
End Function
Public Function GetEmployees() As IEnumerable(Of Employee)
Dim SQL As String = "SELECT * FROM dbo.Employee"
Return GetData(SQL, Employee.FromDataRecord, Nothing)
End Function
Public Function GetEmployeeById(ID As Integer) As Employee
Dim SQL As String = "SELECT * FROM dbo.Employee WHERE ID= #ID"
Return GetData(SQL, Employee.FromDataRecord,
Sub(pc) pc.Add("#ID", SqlDbType.Integer).Value = ID
).FirstOrDefault()
End Function
Public Function GetEmployeesByLastName(LastName As String) As IEnumerable(Of Employee)
Dim SQL As String = "SELECT * FROM dbo.Employee WHERE LastName= #LastName"
Return GetData(SQL, Employee.FromDataRecord,
Sub(pc) pc.Add("#LastName", SqlDbType.NVarChar, 25).Value = LastName
)
End Function
End Module
Now the main part of the application NEVER deals in SQL directly, or even has to know there's a relational database involved. It's just calling methods on a regular module, such that the code in your form for reading data looks more like this:
Dim employees = DB.GetEmployeesByLastName(TextBox1.Text)
DataGridView1.DataSource = employees
You make similar methods in the new Module for writing INSERT/UPDATE/DELETE queries to save changes. These methods will end up calling the cmd.ExecuteNonQuery() method, but they should NEVER accept an SQL statement string as input.
As an application grows you might have a number of classes with FromDataRecord() methods, such that you instead refactor these methods to their own Module. You might also end up with enough Public methods in the DB Module to divide them up into a number of related Modules. At this point, these Modules would all go into a separate class library project (using Friend in some places instead of Private).

Winforms Data from database across multiple forms

I have a winforms applications that has an ms sql server backend. In my database i have lookup tables for like status, and other tables where the data rarely changes. In my application, several forms might use the same lookup tables (Some have a lot of data in them). Instead of loading/filling the data each time the form is open, is there a way to cache the data from the database that can be accessed from multiple forms. I did some searching, but couldnt find the best solution. There is caching, dictionaries, etc. What is the best solution and can you point me to the documentation that discusses it and may even have an example.
Edit:
In my original post I failed to mention that I have a strongly typed dataset and use tableadapters. I want to preload my lookup tables when my application starts, and then have these dataset tables be used throughout the application, on multiple forms without having to fill them on every form.
I have tried creating a class:
Public Class dsglobal
Public Shared EML_StaffingDataSet As EML_StaffingDataSet
Public Shared Sub populateDS()
EML_StaffingDataSet = New EML_StaffingDataSet
End Sub
Public Shared Sub loadskills()
Dim ta As New EML_StaffingDataSetTableAdapters.TSTAFFSKILLTableAdapter
ta.Fill(EML_StaffingDataSet.TSTAFFSKILL)
End Sub
End Class
I run this on a background worker when my application is starting up. So it loads the dataset table. On fill, I can see the datatable has data in it. When I open a form, i want to use the dataset table, but it seems to clear the data out. Not sure if my approach is correct or where my error is.
Edit2:
I have also tried this per comments, but not sure I am doing it correctly. If I am doing it correctly, then how do I use that as a datasource at design time, can i only do that programmatically?
Public Module lookupdata
Private EML_StaffingDataSet As EML_StaffingDataSet
Private skillvalues As List(Of skill)
Public ReadOnly Property skill As List(Of skill)
Get
If skillvalues Is Nothing Then
getskillvalues()
End If
Return skillvalues
End Get
End Property
Private Sub getskillvalues()
skillvalues = New List(Of skill)
EML_StaffingDataSet = New EML_StaffingDataSet
Dim ta As New EML_StaffingDataSetTableAdapters.TSTAFFSKILLTableAdapter
ta.Fill(EML_StaffingDataSet.TSTAFFSKILL)
For Each row As DataRow In EML_StaffingDataSet.TSTAFFSKILL
Dim skill As New skill
skill.skill_id = row("skill_id")
skill.skill_desc = row("skill_desc")
skill.skill_open_ind = row("skill_open_ind")
skillvalues.Add(skill)
Next
End Sub
End Module
Public Class skill
Public Property skill_id As Integer
Public Property skill_desc As String
Public Property skill_open_ind As Boolean
End Class
You might want to consider a lazy loading pattern, like this:
Public Module LookupData
Private statusValues As List(Of LookupValue)
Public Readonly Property Statuses As List(Of LookupValue)
Get
If statusValues Is Nothing Then
GetStatusValues()
End If
Return statusValues
End Get
End Property
Private Sub GetStatusValues()
statusValues = New List(Of LookupValue)
Dim sql = "select key, value from StatusTable"
'TODO: Read the items from the database here, adding them to the list.
End Sub
End Module
Public Class LookupValue
Public Property Key As String
Public Property Value As String
End Class
The idea is that you've got a single instance of LookupData (a Module in VB, there can be only one). Lookup data has a series of Properties, each of which returns a list of values from the database. If the data has already been loaded, it just returns what it has cached. If the data has not been loaded, then the first time it is referenced it retrieves it from the database.
You can consume it elsewhere in your code as follows:
Dim myStatuses = LookupData.Statuses

Change connection EF at runtime in WPF app

I have a WPF app that I'd like to change the connection string programmatically when the app loads. I use the Database-First approach for EF.
I spent a lot of time implementing various solutions found online including stack overflow and can't seem to get it to work.
The most common way seems to be to modify the Entity partial class. When I do this I get the following error at runtime:
Additional information: The context is being used in Code First mode with code that was generated from an EDMX file for either Database First or Model First development. This will not work correctly. To fix this problem do not remove the line of code that throws this exception. If you wish to use Database First or Model First, then make sure that the Entity Framework connection string is included in the app.config or web.config of the start-up project. If you are creating your own DbConnection, then make sure that it is an EntityConnection and not some other type of DbConnection, and that you pass it to one of the base DbContext constructors that take a DbConnection. To learn more about Code First, Database First, and Model First see the Entity Framework documentation here: http://go.microsoft.com/fwlink/?LinkId=394715
I got the same error implementing various other ways as well. If someone could please help me implement a way to change the connection string at runtime I'd greatly appreciate it.
My current implementation is taken from this example solution:
Changing Databases at Run-time using Entity Framework
Implementation:
App.config containts the connection string and name
Partial class added with same name as Entity class:
Imports System.Data.Entity
Imports System.Data.EntityClient
Imports System.Data.SqlClient
Partial Public Class MyEntityName
Inherits DbContext
Public Sub New(ByVal connString As String)
MyBase.New(connString)
End Sub
End Class
In my Application.xaml code file I set a global string variable by calling a method that builds the EntityConnectionStringBuilder. This global string variable is then passed into an entity constructor.
Imports System.Reflection
Imports DevExpress.Xpf.Core
Imports System.Data.EntityClient
Class Application
Public Sub New()
entityConnStr = BuildConnectionString("[MyDataSource]", "[MyDatabase]")
End Sub
Private Function BuildConnectionString(ByVal DataSource As String, ByVal Database As String) As String
' Build the connection string from the provided datasource and database
Dim connString As String = "data source=" & DataSource & ";initial catalog=" & Database & ";persist security info=True;user id=[user];password=[password];trustservercertificate=True;MultipleActiveResultSets=True;App=EntityFramework""
' Build the MetaData... feel free to copy/paste it from the connection string in the config file.
Dim esb As New EntityConnectionStringBuilder()
esb.Metadata = "res://*/DB.[MyEntityName].csdl|res://*/DB.[MyEntityName].ssdl|res://*/DB.[MyEntityName].msl"
esb.Provider = "System.Data.SqlClient"
esb.ProviderConnectionString = connString
' Generate the full string and return it
Return esb.ToString()
End Function
Usage:
Using context = New MyEntity("entityConnStr")
Public connection variable string:
Public entityConnStr As String
I think you should remove the quotes when you pass the connection string to the constructor. You want to use the variable contents, not the variable name.
Use this:
Using context = New MyEntity(entityConnStr)
Instead of this:
Using context = New MyEntity("entityConnStr")

Querying Access database vb.net

I am trying to figure out how to query an access database to take info from the database and put it into a class array. I know how to do some querying using basic LINQ, but not sure how to do it the way I want it to. I have a character class I am using, I am storing all the character traits in a database. I want to query the database and add that info to a class array. Any help would be greatly appreciated.
Here at the class private variables which are the same traits in the database:
Public Class Character
Private _strName As String
Private _intLevel As Integer
Private _intHealth As Integer
Private _intDamage As Integer
Private _intScore As Integer
First of all. I have to say this is not a good question.
Actually what you are saying is...: Please build me the dataconnections for this acces database and a visual studio form.
But because I'm in a good mood, That's what I did for you.
I created a little database file in acces.I saved it into a folder called database. Which is located in the project directory.
I made 2 classes. 1 CharactersForm, and 2 Gateway
the gateway is to make an connection to your Acces database.
The CharactersForm is the form what you see when you debug
Here is the CharactersForm class.
Make sure you drag and drop a listbox and a button
Call the listbox ListBoxCharacters.
Call the button InladenButton. Or call it whatever you want, But make sure it will fit the code given.
Option Explicit On
Option Strict On
Option Infer On
Public Class CharactersForm
Dim _CharactersGateway As New Gateway
Private Sub InladenButton_Click(sender As Object, e As EventArgs) Handles InladenButton.Click
_CharactersGateway.LoadDataTableCharacters()
ListBoxCharacters.DataSource = _CharactersGateway.CharactersDataTable
ListBoxCharacters.DisplayMember = "Name"
End Sub
End Class
The Gateway Class ( to make connection to your acces file)
If somehow visual studio cannot reach the database then it means the connectionstring is not correct. Make sure the connection string is exactly as where your accesfile.accdb is located
Option Explicit On
Option Strict On
Option Infer On
Imports System.IO
Imports System.Data.OleDb
Public Class Gateway
Public Shared AppPath As String = Application.StartupPath()
'\Debug
Public Shared DirectoryUp1 As String = Path.GetDirectoryName(AppPath)
'\bin
Public Shared DirectoryUp2 As String = Path.GetDirectoryName(DirectoryUp1)
'\CharactersStackOverFlow
Public Shared DirectoryUp3 As String = Path.GetDirectoryName(DirectoryUp2)
'\CharactersStackOverFlow
Public Shared AccesDatabaseFilePath As String = DirectoryUp3 & "\DataBase\Characters.accdb"
Dim ConnectionString As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source= " & AccesDatabaseFilePath)
Public Property DataSetCharacters As New DataSet
Public ReadOnly Property CharactersDataTable As DataTable
Get
CharactersDataTable = DataSetCharacters.Tables("Characters")
End Get
End Property
Public CharactersDataAdapter As OleDbDataAdapter
Public Sub LoadDataTableCharacters()
CharactersDataAdapter = New OleDbDataAdapter("SELECT * FROM Characters", ConnectionString)
CharactersDataAdapter.Fill(DataSetCharacters, "Characters")
Dim kamersCommandBuilder As New OleDbCommandBuilder(CharactersDataAdapter)
End Sub
End Class
This is as Yuriy Galanter said. You need the OleDbConnection.
If you are using office 2010 and you get thsi warning ::
'Microsoft.ACE.OLEDB.12.0' provider is not registered on the local machine
You need to download the 2007 Office System Driver. It works also for 2010 don't ask me why.
Type '2007 Office System Driver' in google. And from microsoft you can download and install it.
ALL DONE!
if you run the program and press the load button. The names of your characters will come in the listbox.
If you want to put values of the fields of the database in variables. Then it's just about making a for loop and put them in dimmed variables. That is something I'm sure you can do yourself. The connection to the database and the loading in the program is already sorted out now!
Good luck
Jonathan

How to Implement Entity Framework on this example?

I have been working thru this WPF example and am trying to hook it up to my database using Entity Framework but am confused on how to do this. Can someone please offer some guidance on how this would be done?
The code has the following in CustomerRepository.cs
static List<Customer> LoadCustomers(string customerDataFile)
{
// In a real application, the data would come from an external source,
// but for this demo let's keep things simple and use a resource file.
using (Stream stream = GetResourceStream(customerDataFile))
using (XmlReader xmlRdr = new XmlTextReader(stream))
return
(from customerElem in XDocument.Load(xmlRdr).Element("customers").Elements("customer")
select Customer.CreateCustomer(
(double)customerElem.Attribute("totalSales"),
(string)customerElem.Attribute("firstName"),
(string)customerElem.Attribute("lastName"),
(bool)customerElem.Attribute("isCompany"),
(string)customerElem.Attribute("email")
)).ToList();
}
which is where I assume the hook to the database would happen but not sure how. I can create the Model.edmx file to connect to the database but not sure how to physically get the list of customers from the database.
Also, this example uses a List of Customers but most examples I have gone through use ObservableCollection for this type of data. Is one preferable over the other and why?
TIA,
Brian Enderle
My MVVM/EF projects typically load entities directly into the ViewModels or Into light collections in the view models. I don't create any kind of custom repository to sit between them.
Generally my ViewModels do one of two things,
Retrieves data on instancing
Takes an entity as a constructor argument.
When I retrieve data on instance, I generally use a background worker class, which queries the context, puts the results in a list, and passes the list out. The Work Completed method then puts the entities into viewmodels and puts the ViewModels in a ObservableCollection.
Similar to this:
Private WithEvents GetProjectsBackgroundWorker As BackgroundWorker
Private _Projects As New ObservableCollection(Of ProjectViewModel)
Public Sub New()
GetProjectsBackgroundWorker = New BackgroundWorker
GetProjectsBackgroundWorker.RunWorkerAsync()
End Sub
Public Property Projects() As ObservableCollection(Of ProjectViewModel)
Get
Return _Projects
End Get
Set(ByVal value As ObservableCollection(Of ProjectViewModel))
_Projects = value
End Set
End Property
#Region " GetProjects"
Private Sub GetProjectsBackgroundWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles GetProjectsBackgroundWorker.DoWork
Dim results = From proj As Project In Context.Projects Where proj.flgActive = True And proj.flgReview = True Select proj
Dim ProjList As New List(Of Project)(results)
e.Result = ProjList
End Sub
Private Sub GetProjectsBackgroundWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles GetProjectsBackgroundWorker.RunWorkerCompleted
If e.Error Is Nothing Then
For Each p As Project In e.Result
Projects.Add(New ProjectViewModel(p, Context))
Next
Else
End If
End Sub
#End Region
In ViewModels that take an entity as an argument, they often take a context as argument, especially if something is a short time-span operation. Otherwise I detach entities from the context in the even something goes hay-wire with the database or the connection is lost or something.
To answer your second question, ObservableCollections are Enumerable collections that have collection change notification implemented. The collection will notify the UI when a new item is added/removed/moved. Typically any time I have entities that are going to be viewed or displayed in the UI, I host them in an Observable Collection. Otherwise I use something simpler, a List normally.

Resources