PersonDB - SQLite Example Using Entity Framework 5
PersonDB - SQLite Example Using Entity Framework 5
P Kruger
Revised on: April 5, 2022
Software Development II PersonDB - SQLite Example using Entity Framework Code First
Contents
1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1 Business Layer - Business Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1 Person . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1.1 UML Diagram - Person . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1.2 Person.cs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.2 Persons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.2.1 UML Diagram - Persons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.2.2 Persons.cs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Data Access Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.1 ProviderBase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.1.1 UML Diagram - ProviderBase . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.1.2 ProviderBase.cs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.2 PersonContext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.2.1 UML Diagram - PersonContext . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.2.2 PersonContext.cs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.2.2.1 Connection string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2.3 SQLiteProvider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2.3.1 UML Diagram - SQLiteProvider . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.3.2 SQLiteProvider.cs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.3.2.1 SelectAll() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.3.2.2 SelectPerson() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.2.3.2.3 Insert() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2.3.2.4 Update() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.2.3.2.5 Delete() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.3 Business Layer - Business Logic Class . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3.1 PersonBL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3.1.1 UML Diagram - PersonBL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3.1.2 ProviderBase.cs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3.1.2.1 private ProviderBase providerBase; . . . . . . . . . . . . . . . . . . . . . . . . 16
1.3.1.2.2 public PersonBL(string Provider) . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.3.1.2.3 public Persons SelectAll() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.3.1.2.4 public int SelectPerson(string ID, ref Person person) . . . . . . . . . . . . . . . 16
1.3.1.2.5 public int Insert(Person newPerson) . . . . . . . . . . . . . . . . . . . . . . . . 16
1.3.1.2.6 public int Update(Person existingPerson) . . . . . . . . . . . . . . . . . . . . . 16
1.3.1.2.7 public int Delete(string ID) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.3.1.2.8 private void setupProviderBase(string Provider) . . . . . . . . . . . . . . . . . . 16
1.4 Presentation Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.4.1 class Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.4.1.1 Global variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
1.4.1.2 Initialise() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
1.4.1.3 Main(string[] args) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
1 Introduction
The purpose of this document is to show how to apply the three layer pattern on a SQLite database
to develop a simple .NET application using Visual Studio and Entity Framework.
Please watch the accompanying video series on how to create the Visual Studio solution, add the
required NuGet packages, and run the migration and update to created the actual SQLite database.
1.1.1.2 Person.cs
The UML diagram can now be converted to C# code:
public Person()
{
} // end constructor
1.1.2 Persons
A strongly typed custom CollectionBase class called Persons was created to store a collection of
Person objects.
1.1.2.2 Persons.cs
The UML diagram can now be converted to C# code:
} // end get
set
{
List[personIndex] = value;
} // end set
} // end indexer
} // end class
1.2.1.2 ProviderBase.cs
The UML diagram can now be converted to C# code:
/// <summary>
/// This method gets a single Person object from the Person datastore.
/// It returns 0 to indicate the Person was loaded from datastore, or
/// -1 to indicate that no Person was loaded from the datastore (not found).
/// </summary>
/// <param name="ID">The ID of the Person to load from the datastore.</param>
/// <param name="person">The Person object loaded from the datastore.</param>
public abstract int SelectPerson(string ID, ref Person person);
/// <summary>
/// <summary>
/// This method updates a record in the Person datastore.
/// It returns 0 to indicate the Person was found and updated successfully, or
/// -1 to indicate the Person was not updated because the record was not found
/// </summary>
/// <param name="existingPerson">The new Person data for the record in the Person
/// datastore.</param>
public abstract int Update(Person existingPerson);
/// <summary>
/// This method deletes a record in the Person datastore.
/// It returns 0 to indicate the Person was found and deleted successfully, or
/// -1 to indicate the Person was not deleted because the record was not found
/// </summary>
/// <param name="ID">The Person ID of the Person to delete in the Person datastore.</param>
public abstract int Delete(string ID);
} // end class
1.2.2 PersonContext
The class PersonContext inherits from the Entity Framework’s DbContext (Database Context) class
for creating, updating, and deleting the Person objects as needed in the database.
The public automatic property Persons must not be confused with the Persons custom collection on
page 2. This automatic property is the collection of all the Person entries in the SQLite database.
By manipulating this collection, and then saving it, the contents of the database will also be changed.
All the CRUD methods will make use of an instance of the PersonContext class to manipulate the
database.
1.2.2.2 PersonContext.cs
The UML diagram can now be converted to C# code.
//
@"Data Source=c:\Data\Persons.db;Version=3;"
Version indicates which version of SQLite is being used, which in this example is 3.
1.2.3 SQLiteProvider
The class SQLiteProvider will implement the ProviderBase class and provide the CRUD functionality
using SQLite.
1.2.3.2 SQLiteProvider.cs
The UML diagram can now be converted to C# code. The class definition is shown below:
} // end class
1.2.3.2.1 SelectAll()
The SelectAll() method is now implemented:
try
{
list = new Persons();
using (PersonContext db = new PersonContext())
{
//
// The next block of code can be used
// to select all the items in db.Persons
// and to sort the result on p.ID
//
// Then a foreach can be used to add the
The purpose of this method is to select all the records in the Persons table, and return them as a
Persons list.
1.2.3.2.2 SelectPerson()
The SelectPerson() method is now implemented:
try
{
using (PersonContext db = new PersonContext())
{
person = db.Persons.FirstOrDefault(p => p.ID.Equals(ID));
if (person == null) // not found
{
rc = -1;
} //end if
else
{
rc = 0;
} // end else
} // end using
} // end try
catch (Exception ex)
{
throw ex;
} // end catch
return rc;
} // end method
The purpose of this method is to select a single record in the Persons table. The method parameter
string ID contains the ID of the Person record to search for in the table. If the record is found, it
will be made available via the second method parameter ref Person person as a fully instantiated
Person object.
The integer rc can contain one of the following two valid values:
• 0 : the Person record was found, and is available as a Person object via the method parameter
ref Person person
• -1: the Person record was not found
From here, the basic pattern is as follows (notice what is the same as with SelectAll() on page 6):
• start a single try/catch
1.2.3.2.3 Insert()
The Insert() method is now implemented:
try
{
using (PersonContext db = new PersonContext())
{
person = db.Persons.FirstOrDefault(b => b.ID.Equals(newPerson.ID));
if (person == null) // not found
{
db.Persons.Add(newPerson);
db.SaveChanges();
rc = 0;
} // end if
else
{
rc = -1;
} // end else
} // end using
} // end try
catch (Exception ex)
{
throw ex;
} // end catch
return rc;
} // end method
The purpose of this method is to insert a single record in the Persons table. The method parameter
Person newPerson contains the fully instantiated Person object that must be inserted into the table.
The integer rc can contain one of the following two valid values:
• 0 : the Person object in the method parameter newPerson was successfully inserted into the
table
• -1: the Person object was not inserted since an existing record with the same primary key
already exists in the table
From here, the basic pattern is as follows (notice what is the same as with SelectAll() on page 6
and SelectPerson() on page 8):
• start a single try/catch
• instantiate a PersonContext object with a using() clause:
– As with SelectAll() on page 6 and SelectPerson() on page 8 the PersonContext is used
to gain access to the records in the database
– As with SelectPerson() on page 8, a LINQ query is used to determine if the Person
record identified with the ID property of the Person method parameter is in the database.
FirstOrDefault is used since it will not throw an exception if the object was not found,
and instead will return a null.
– person is evaluated:
∗ if null, newPerson is added to db.Persons and the SaveChanges() method is invoked
to save all the changes to the database. Variable rc is set to 0 to indicate success
∗ if not null, rc is set to -1
• the catch is simply a general catch that will throw any caught exception back to the calling
method of Insert()
• the last thing to do is to return rc
1.2.3.2.4 Update()
The Update() method is now implemented:
try
{
using (PersonContext db = new PersonContext())
{
person = db.Persons.FirstOrDefault(p => p.ID.Equals(existingPerson.ID));
if (person == null) // not found
{
rc = -1;
} // end if
else
{
person.Age= existingPerson.Age;
person.FirstName = existingPerson.FirstName;
person.LastName = existingPerson.LastName;
db.SaveChanges();
rc = 0;
} // end else
} // end using
} // end try
catch (Exception ex)
{
throw ex;
} // end catch
return rc;
} // end method
The purpose of this method is to update a single record in the Persons table. The method parameter
Person existingPerson contains the fully instantiated Person object of which ID will be used to
search for the record to update, and the properties of existingPerson will be used to update the
corresponding fields/columns for that record in the table. This method does NOT support updating
the primary key as well.
The integer rc can contain one of the following two valid values:
• 0 : the Person object in the method parameter person was successfully updated in the table
• -1: the Person object was not updated since an existing record with the same primary key was
not found in the table
From here, the basic pattern is as follows (notice what is the same as with SelectAll() on page 6,
SelectPerson() on page 8, and Insert() on page 9):
• start a single try/catch
• instantiate a PersonContext object with a using() clause:
– As with SelectAll() on page 6, SelectPerson() on page 8 and Insert() on page 9, the
PersonContext is used to gain access to the records in the database
– As with SelectPerson() on page 8 and Insert() on page 9, a LINQ query is used to
determine if the Person record identified with the ID property of the Person method
parameter is in the database. FirstOrDefault is used since it will not throw an exception
if the object was not found, and instead will return a null.
– person is evaluated:
∗ if null, the record was not found and rc is set to -1
∗ if not null, the record was found and the properties of existingPerson is used to update
the corresponding properties of person. Note: person.ID is NOT updated, since
that would change the primary key which is not supported by this method. Next the
SaveChanges() method is invoked to save all the changes to the database. Variable
rc Variable rc is set to 0 to indicate success
• the catch is simply a general catch that will throw any caught exception back to the calling
method of Insert()
• the last thing to do is to return rc
1.2.3.2.5 Delete()
The Delete() method is now implemented:
try
{
using (PersonContext db = new PersonContext())
{
person = db.Persons.FirstOrDefault(b => b.ID.Equals(ID));
if (person == null) // not found
{
rc = -1;
} // end if
else
{
db.Persons.Remove(person);
db.SaveChanges();
rc = 0;
} // end else
} // end using
} // end try
catch (Exception ex)
{
throw ex;
} // end catch
return rc;
} // end method
The purpose of this method is to delete a single record from the Persons table. The method parameter
string ID contains the value of the primary key of the record to remove from the table.
The integer rc can contain one of the following two valid values:
• 0 : the Person record represented by the method parameter ID was successfully removed from
the table
• -1: the Person record was not removed since an existing record with the same primary key was
not found in the table
From here, the basic pattern is as follows (notice what is the same as with SelectAll() on page 6,
SelectPerson() on page 8, Insert() on page 9, and Update() on page 10):
Update() on page 10, the PersonContext is used to gain access to the records in the
database
– As with SelectPerson() on page 8, Insert() on page 9, and Update() on page 10, a LINQ
query is used to determine if the Person record identified with the ID method parameter
is in the database. FirstOrDefault is used since it will not throw an exception if the object
was not found, and instead will return a null.
– person is evaluated:
∗ if null, the record was not found and rc is set to -1
∗ if not null, the record was found and the object removed using the Remove() method.
Next the SaveChanges() method is invoked to save all the changes to the database.
Variable rc Variable rc is set to 0 to indicate success
• the catch is simply a general catch that will throw any caught exception back to the calling
method of Insert()
• the last thing to do is to return rc
1.3.1.2 ProviderBase.cs
The UML diagram can now be converted to C# code:
// data provider
//Re-use : setupProviderBase()
//Input Parameter : string Provider
// - The name of the data provider to use
//Output Type : None
//
setupProviderBase(Provider);
} // end method
/// <summary>
/// This method gets the list of all the business objects from the Person datastore.
/// It returns the list of business objects
/// </summary>
public Persons SelectAll()
{
return providerBase.SelectAll();
} // end method
/// <summary>
/// This method gets a single Person object from the Person datastore.
/// It returns 0 to indicate the Person was loaded from the datastore, or
/// -1 to indicate that no Person was loaded from the datastore.
/// </summary>
/// <param name="ID">The Person ID of the Person to load from the datastore.</param>
/// <param name="person">The Person object loaded from the datastore.</param>
public int SelectPerson(string ID, ref Person person)
{
return providerBase.SelectPerson(ID, ref person);
} // end method
/// <summary>
/// This method inserts a record in the Person datastore.
/// It returns 0 to indicate the Person was inserted into datastore, or
/// -1 to indicate the Person was not inserted because a duplicate was found
/// </summary>
/// <param name="newPerson">The Person object to add to the Person datastore.</param>
public int Insert(Person newPerson)
{
return providerBase.Insert(newPerson);
} // end method
/// <summary>
/// This method updates a record in the Person datastore.
/// It returns 0 to indicate the Person was found and updated successfully, or
/// -1 to indicate the Person was not updated because the record was not found
/// </summary>
/// <param name="existingPerson">The new Person data for the record in the Person
/// datastore.</param>
public int Update(Person existingPerson)
{
return providerBase.Update(existingPerson);
} // end method
/// <summary>
/// This method deletes a record in the Person datastore.
/// It returns 0 to indicate the Person was found and deleted successfully, or
/// -1 to indicate the Person was not deleted because the record was not found
/// </summary>
/// <param name="ID">The Person ID of the Person to delete in the Person datastore.</param>
public int Delete(string ID)
{
return providerBase.Delete(ID);
} // end method
The constructor accepts one method parameter of type string. This string represents the name
of the Provider class that must be used. The constructor will then send this string to the helper
method setupProviderBase() (on page 16).
• XMLProvider - Provide CRUD functionality for manipulating Person info in a XML file
• SQLiteProvider - Provide CRUD functionality for manipulating Person info in a SQLite
database file
• MySQLProvider - Provide CRUD functionality for manipulating Person info in a MySQL
database on a local MySQL server
As more providers are implemented, this evaluation must be expanded. For now, a form of selection
will be used.
In this method, if, as an example, Provider contains the value "SQLiteProvider", the instance
field providerBase will be instantiated as a new SQLiteProvider (on page 5) object. This means
that any reference to a CRUD method via the instantiated PersonBL object, will invoke the
corresponding CRUD method in SQLiteProvider. As another example, if Provider contains the
value "XMLProvider", the instance field providerBase will be instantiated as a new XMLProvider
object. This means that any reference to a CRUD method via the instantiated PersonBL object,
will invoke the corresponding CRUD method in XMLProvider.
• in this layer, a PersonBL object must be instantiated with the correct provider. This object
must then be used to invoke the required CRUD methods
• except for the PersonBL object, NO objects or references to ProviderBase and the derived
classes may be used
The following example will be used to tie everything together. This example will be a Console
application with a simple interactive menu driven approach. Exception handling and other error
handling are kept to a minimum but must naturally be implemented in full. All the different parts of
this program will not be discussed in detail, but certain parts will be discussed after the code listing.
The following parts will be discussed:
//--------------------------------------------------------------------------------
//
// Display menus
//
//--------------------------------------------------------------------------------
//
//Method Name : void ShowMainMenu()
//Purpose : Display the main menu
//Re-use : none
//Input Parameter : none
//Output Type : none
//
WriteLine();
WriteLine("Please select an option:");
WriteLine("========================");
WriteLine("1. Person Maintenance");
WriteLine("X. Exit");
WriteLine();
} // end method ShowMainMenu()
//--------------------------------------------------------------------------------
//
// Process menus
//
//--------------------------------------------------------------------------------
public static void ProcessPersonMenu()
{
//
//Method Name : void ProcessPersonMenu()
//Purpose : Invoke appropriate method to handle user menu selection
//Re-use : ShowPersonMaint();PersonList();PersonAdd();PersonRemove();
// PersonUpdate()
//Input Parameter : none
//Output Type : none
//
char choice = ’0’;
ConsoleKeyInfo cki;
WriteLine();
ShowPersonMaint();
cki = ReadKey();
WriteLine();
choice = cki.KeyChar;
cki = ReadKey();
WriteLine();
choice = cki.KeyChar;
} // end while
} // end method
//--------------------------------------------------------------------------------
//
// Person related methods
//
//--------------------------------------------------------------------------------
if (change)
{
rc = pBL.Update(person);
if (rc == -1)
{
WriteLine(ID + " NOT updated since it is not in the DB");
} // end if
else
{
WriteLine(ID + " updated");
} // end else
} // end if
else
{
WriteLine("Nothing selected to update");
} // end else
} // end if
else
{
WriteLine(ID + " NOT found");
} // end else
} // end method
} // end method
//
//Method Name : void PersonRemove()
//Purpose : Try to remove a Person record from the DB
//Re-use : none
//Input Parameter : none
//Output Type : none
//
string code = "";
int rc = 0;
if (personList.Count > 0)
{
Write("Please enter the person ID: ");
code = ReadLine().ToUpper();
rc = pBL.Delete(code);
if (rc == 0)
{
WriteLine(code + " removed from DB");
} // end if
else
{
WriteLine(code + " NOT removed since it is not in the DB");
} // end else
} // end if
else
{
WriteLine("No person record to remove from DB");
} // end else
} // end method
//--------------------------------------------------------------------------------
//
// Main
//
//--------------------------------------------------------------------------------
public static void Main(string[] args)
{
//
//Method Name : void Main(string[] args)
//Purpose : Main entry into program
//Re-use : ShowMainMenu(); ProcessPersonMenu()
//Input Parameter : string[] args
// - command line args - not used
//Output Type : none
//
try
{
Initialise();
WriteLine();
ShowMainMenu();
cki = ReadKey();
WriteLine();
choice = cki.KeyChar;
WriteLine();
ShowMainMenu();
cki = ReadKey();
WriteLine();
choice = cki.KeyChar;
} // end while
} // end try
catch (Exception ex)
{
WriteLine(ex.Message);
} // end catch
1.4.1.2 Initialise()
This method instantiates the two global variables. Importantly, pBL is instantiated to the desired
Provider class. Currently, SQLiteProvider is used. You will recall that one of the advantages of
using the three layer pattern was the swapping of the data providers. Here, in this example, is where
the swap can be made.
Consider the following scenario: the three layer pattern was used and implemented for a simple Person
Management System. The initial datastore is a SQLite database file. Later on, a requirement arose
that instead of using the SQLite database file, an XML file must now be used. What should happen
now? Luckily, the process is straight forward. An XML Provider class must be implemented that
inherits from the abstract base class ProviderBase (on page 3). Since ProviderBase also contains
abstract methods, that represents the CRUD methods, the XML Provider class must implement these
methods. This means the the method headers in the existing SQLite Provider class and the new XML
Provider class will be exactly the same. What is critically important is that the CRUD methods in
the XML provider class must access and modify the XML file, but the behavior must be the same as
for the SQLite provider. As soon as the XML Provider is finished, method setupProviderBase (on
page 16) must be modified to include a test for XMLProvider (which is currently the case in this
example). Next, method Initialise() must be modified to instantiate pBL to XMLProvider. Save
and build the program and it will now use the XML provider, instead of the SQLite provider. Nothing
else in the whole program needs to change.
2 Summary
In this document a complete SQLite database aware application was developed using the simple three
layer pattern. As with many things programming related, such a database application can be designed
and implemented in several different ways, but for the purpose of the Software Development II
subject, this will be the standard pattern to use. The only thing that could change is obviously the
Presentation Layer, but the ProviderBase and SQLiteProvider classes will follow the pattern as
implemented in this document.