In the previous article, you learned how to store data in your app's isolated storage. In this article, we focus on working with local databases that live in your app's isolated storage. You'll learn about database operations using LINQ, database schemas as well as how to fetch data from a local database.
1. Introduction
You can store relational data in a local database that lives in your app's isolated storage. All database operations on Windows Phone are performed using LINQ to SQL. It is used to define the database schema, select data, and save changes to the underlying database file residing in the local folder.
The LINQ to SQL object model uses the System.Data.Linq.DataContext
namespace to make a proxy call to the local database. The LINQ to SQL runtime acts as a bridge between the data context object and the real data to do manipulations.
When working with local databases on Windows Phone, it's important to keep the following in mind:
- The local database runs in the Windows Phone app's process. It does not run continuously as a background service.
- It can be accessed only by the corresponding Windows Phone app.
- It can be accessed only with LINQ to SQL, Transact-SQL is not supported.
- To synchronize access to the local folder across different threads, the
Mutex
class is used. - It's not recommended to encrypt the reference database file if you're going to access it exclusively from the installation folder. Doing so prevents the system from performing routine database maintenance operations, such as re-indexing, upon the first connection.
This article shows how you can create a local database and insert, update or delete data from it. We will build a sample application with a User Details table and perform various operations on it. We will segregate the code files for different operations such as insert, update and delete for the sake of convenience.
2. Building the Data Context
The tables for the database can be defined anywhere in the app as long as it is accessible globally. We create a separate file, DB.cs, for all the tables. In this file we specify an object model that determines the database schema and create the data context.
Step 1: Adding References
Add the following directives at the top of the DB.cs file to reference LINQ to SQL assembly:
using System.Data.Linq; using System.Data.Linq.Mapping;
Step 2: Creating a Table
Add an entity class named User_details
that represents the database table of the app in the local database. The attribute [Table]
indicates the LINQ to SQL runtime to map the class to a local database table.
[Table] public class User_details { [Column(IsDbGenerated = true, IsPrimaryKey = true)] public int ID { get; set; } [Column] public string user_name { get; set; } [Column] public string user_email { get; set; } }
As you can see, the User_details
class has three public properties that correspond to three database columns:
ID
is an identifier column that is automatically populated by the database. It's also the primary key for which a database index is automatically created. These settings and more are specified with the LINQ to SQL column mapping attribute written above the property syntax.user_name
is a column to store the name of the user.user_email
is a column to store the email address of the user.
Step 1: Defining Data Context
The class UserDataContext
inherits from DataContext
and is referred to as the data context. This code calls the base constructor and declares the database table named User_details
. The local database is stored in the app's isolated storage and in our example it is saved as Databases.sdf
.
public class UserDataContext : DataContext { public static string DBConnectionString = @"isostore:/Databases.sdf"; public UserDataContext(string connectionString) : base(connectionString) { } public Table<User_details> Users { get { return this.GetTable<User_details>(); } } }
Note that the database connection string is not required to be a static field. We use DBConnectionString
in this case just for convenience.
3. Creating the Database
To create the database, open the code behind file of the app, named App.xaml.cs. Add the following code at the end of its constructor, named App
.
using (UserDataContext context = new UserDataContext(UserDataContext.DBConnectionString)) { if (!context.DatabaseExists()) context.CreateDatabase(); }
The database creation code is added here so that the database will be present before the code from the main page runs. The code checks whether the database exists and, if no database is found, a new one is created.
4. Database Operations
Once the data context has been built and the database has been created, we can perform operations on it. You can insert, update, and delete records from the table we just created. We have created separate classes to hold functions for insert, delete, and update operations.
Step 1: Inserting Records
It's convenient to create a separate class file, DatabaseAdd.cs, for adding any records to the database. Define a function AddUser
that takes name
and email_id
as parameters and adds a record to the User_details
table.
public void AddUser(String name, String email) { using (UserDataContext context = new UserDataContext(UserDataContext.DBConnectionString)) { User_details du = new User_details(); du.user_name = name; du.user_email = email; context.Users.InsertOnSubmit(du); context.SubmitChanges(); } }
The AddUser
function uses theUserDataContext
data context to connect to the database, creates a new instance of the User_details
entity class, and inserts a record.
New items that have been added to the data context are not saved to the database until the SubmitChanges
function is called. Call the AddUser
function to add a record to the database.
DatabaseAdd add=new DatabaseAdd(); add.AddUser("vivek","maskaravivek@hotmail.com");
Step 2: Fetching Records
Create a separate class file, FetchDatabase.cs, for fetching records from the database. This class contains a GetAllUsers
function that returns an IList
instance, containing the records fetched from the table.
The connection is set up using the data context after which records are fetched from the User_details
table. The LINQ query returns an IList
instance containing the fetched records. The IList
instance is nothing more than a collection of objects of same type.
public IList<User_details> GetAllUsers() { IList<User_details> list = null; using (UserDataContext context = new UserDataContext(UserDataContext.DBConnectionString)) { IQueryable<User_details> query = from c in context.Users select c; list = query.ToList(); } return list; }
In the below code snippet we define a Users
class with data members id
, name
, and email
to hold the details from the fetched records.
class Users { public string id { get; set; } public string name { get; set; } public string email { get; set; } }
Create another function, getAllUsers
, that returns a list of Users
when called. The function creates a new list to hold the details of the fetched users. It iterates through the IList
instance named usrs
and adds the details of each user to the allUsers
instance.
public List<Users> getUsers() { IList<User_details> usrs = this.GetAllUsers(); List<Users> allUsers = new List<Users>(); foreach (User_details m in usrs) { Users n = new Users(); n.id = m.ID.ToString(); n.name = m.user_name; n.email = m.user_email; allUsers.Add(n); } return allUsers; }
In the sample application of this article, the getUsers
function is used to set the ItemSource
of the Listbox
named allusers
as shown below.
FetchDatabase fetch = new FetchDatabase(); allusers.ItemsSource = fetch.getUsers();
Step 3: Updating Records
Updating records is very similar to adding records to a table. Continuing with our modular approach, we create a new class file, DatabaseUpdate.cs, for database updates. Unlike delete operations, there's no function to update multiple records at once.
Add a function UpdateUsers
that accepts an id
, name
, and email_id
, and updates the table row for the corresponding id
.
public void UpdateUser(int id, String email, String name) { using (UserDataContext context = new UserDataContext(UserDataContext.DBConnectionString)) { IQueryable<User_details> entityQuery = from c in context.Users where c.ID == id select c; User_details entityToUpdate = entityQuery.FirstOrDefault(); entityToUpdate.user_name = name; entityToUpdate.user_email = email; context.SubmitChanges(); } }
The function queries the database and stores the first matched record in the entityToUpdate
variable. It then updates the record by assigning the new values and submits the changes to update the database.
The database is not updated until we call the SubmitChanges
function. This function will update only the first matched record. The UpdateUser
function is called to update existing records in the database as shown below.
DatabaseUpdate update = new DatabaseUpdate(); update.UpdateUser(1, "maskaravivek@hotmail.com", "vivek.maskara");
To update multiple records, you'll need to iterate through the records that you'd like to update one by one. In the following code snippet, we update the name of every use in the database by making it lowercase.
using (UserDataContext context = new UserDataContext(UserDataContext.DBConnectionString)) { IQueryable<User_details> entityQuery = from c in context.Users select c; IList<User_details> entityToUpdate = entityQuery.ToList(); foreach(User_details user in entityToUpdate) { user.user_name = user.user_name.ToLower(); } context.SubmitChanges(); }
Step 4: Deleting Records
Create a class file, DatabaseDelete.cs ,for delete operations. In the DatabaseDelete
class, add a DeleteUser
function that accepts a parameter id
and deletes a single user whose id
matches the passed-in parameter.
public void DeleteUser(String id)//delete user by id { using (UserDataContext context = new UserDataContext(UserDataContext.DBConnectionString)) { IQueryable<User_details> entityQuery = from c in context.Users where c.ID.Equals(id) select c; User_details entityToDelete = entityQuery.FirstOrDefault(); context.Users.DeleteOnSubmit(entityToDelete); context.SubmitChanges(); } }
The function calls DeleteOnSubmit
, which deletes a single record from the database. The changes are saved when the SubmitChanges
function is called. The DeleteUser
function deletes a single record from the database as shown below.
DatabaseDelete delete = new DatabaseDelete(); delete.DeleteUser("1");
The System.Data.Linq
assembly provides a function DeleteAllOnSubmit
to delete multiple records at once. The following code snippet truncates the User_details
table.
using (UserDataContext context = new UserDataContext(UserDataContext.DBConnectionString)) { IQueryable<User_details> entityQuery = from c in context.Users select c; IList<User_details> entityToDelete = entityQuery.ToList(); context.Users.DeleteAllOnSubmit(entityToDelete); context.SubmitChanges(); }
This function accepts a list of records and deletes the records in that list. Note that in both cases the changes are saved only when the SubmitChanges
function is called.
Conclusion
Windows Phone apps use LINQ to SQL for all database operations. LINQ to SQL is used to define the database schema, select data, and, save changes to the underlying database file residing in the app's isolated storage. LINQ to SQL provides an object-oriented approach for working with data stored in a database and consists of an object model and a runtime. I recommend you read this MSDN article for best practices on using local databases. Feel free to download the tutorial's source files to use as reference.