Lesson 4 - Storing objects in the CSV format in C#
In the previous lesson, Working with text files in C# .NET, we showed you how to write and read text files. The application we made was simple, like from a textbook. Let's create a real user database today using text files. Of course, we're going to store objects, so you can easily alter the program to store diary reminders, high scores for a game, animals in a shelter, or whatever you need to store.
The CSV format
We don't have to invent a complex method of storing data in text files because a proven and standard one already exists. This approach is called CSV (Comma Separated Values), where values are separated by commas or semicolons. We mentioned CSV in the article about the Split() and Join() string methods. In today's tutorial, we're going to need them.
Let's agree on what the User class will look like. Once we establish that, we'll demonstrate how its instances will be saved in CSV. Create a new project of the Windows Forms Application type. Then, add a User class to it. We'll store their names, ages, and registration dates. The constructor will initialize the instance based on these three properties. We'll override the ToString() method so it'll return the user's name. The class will look like this:
class User { public string Name { get ; private set; } public int Age { get; private set; } public DateTime Registered { get; private set; } public User(string name, int age, DateTime registered) { Name = name; Age = age; Registered = registered; } public override string ToString() { return Name; } }
Now, let's take a look at how users will appear in the CSV format. Each line will represent a single user. The user's properties will be separated by semicolons. For example, a user named John Smith, who is 22 years old and registered on March 21, 2000, would look like this:
John Smith;22;3/21/2000
We can see at first sight that the file is relatively easy to read. However, anyone who isn't aware of the application's design probably won't guess what the number 22 represents and what the date is for.
The file can obviously contain multiple users, i.e. more lines.
Since we're programming according to object-oriented practices, we'll create a class for our database as well. It'll contain a collection of users, represented by a List class instance. The collection will be private and adding users (or deleting them, searching for, etc.) will be done using public methods. Finally, the database will contain methods to load the CSV file and to store the database contents to it. The filename will be another one of the database's private fields. Add a new class, named Database, to the project and prepare its methods:
class Database { private List<User> users; private string file; public Database(string file) { } public void AddUser(string name, int age, DateTime registered) { } public User[] ReturnAll() { } public void Save() { } public void Load() { } }
Visual Studio will then underline the ReturnAll() method in red because it doesn't return a value, but we'll ignore that for now. We'll do it all step by step starting with the constructor.
In the constructor, we'll create a List instance and store the path to the database file:
public Database(string file) { users = new List<User>(); this.file = file; }
That was very easy, so we'll move right on to next method:
public void AddUser(string name, int age, DateTime registered) { User u = new User(name, age, registered); users.Add(u); }
The ReturnAll() method will return all of the users. Similarly, we can create methods for searching for certain users. We'll return the users as an array.
public User[] ReturnAll() { return users.ToArray(); }
Saving the users to CSV
Now, we'll finally get to work with the CSV file. Let's start by adding a
using
block with a StreamWriter instance. We'll iterate over our
user list internally and create a string array for each user based on their
properties. We'll have to convert all of the non-string properties explicitly to
strings. Then, we'll join the array to make a long string in which the items are
separated by semicolons. We'll use the Join() method to do just that. Unlike
Split(), we call the Join() method directly on the String class and we specify
the "glue" (the text used to join the items) as a string, not a char. Let's get
right to it:
public void Save() { // opens the file for writing using (StreamWriter sw = new StreamWriter(file)) { // iterates over the users foreach (User u in users) { // creates an array of the user's values string[] values = { u.Name, u.Age.ToString(), u.Registered.ToShortDateString() }; // creates a new line string line = String.Join(";", values); // writes the line sw.WriteLine(line); } // flushes the buffer sw.Flush(); } }
If we only used the ToString() method to convert the date to a string, it would store the time as well, which is not what we want in this case. Let's try it all out, start by moving to the Form file (Form1.cs or press F7 in the designer). We'll create a private database field to which, in the form's constructor, we'll create an instance of our database:
private Database database; public Form1() { InitializeComponent(); database = new Database("users.csv"); }
Now, let's add a new button to the form, name it saveButton, and set its Text to "Save".
In its Click handler, which is generated by double-clicking the button), we'll add 2 users to the database. It'll only be for testing purposes now, the application will look better later on. Next, we'll store the entire database into a file.
private void saveButton_Click(object sender, EventArgs e) { database.AddUser("John Smith", 22, new DateTime(2000, 3, 21)); database.AddUser("John Brown", 31, new DateTime(2012, 10, 30)); database.Save(); }
Run the application and click the button. Now open the users.csv file in
Notepad (in project/bin/debug
), which should have the following
content:
John Smith;22;3/21/2000 James Brown;31;10/30/2012
All of it works as it should We'll finish the application and be able to load users from the file in the next lesson - Storing objects in CSV format in C# .NET, part 2.