Introduction
The Code First Walkthrough sample shows you how to build a DevForce Code First WPF application from scratch.
Background
The application itself is not the focus. We're not striving to make a useful application nor demonstrating best practices in UI design or development. Our purpose is to lead you through a sequence of steps that introduces the essentials of writing and consuming an entity model in DevForce Code First style.
This tutorial shows how to build a simple WPF application using Code First. You'll define a single-entity (Category
) model, add instances of it to a development database, query them back, format them as string
s, and display them on screen as logged messages.
DevForce implements an end-to-end n-tier architecture. It works by exposing a generic web service on the server that takes a LINQ expression and returns business objects to the client over the internet.
DevForce handles all the n-tier WCF communications, serialization, and marshaling for you.
Spoiler alert: All the CF entities are DevForce entities and have all same advantages as DevForce entities generated from an EDMX.
Before You Start
DevForce and Entity Framework 4.1 must be installed.
This walkthrough uses SQL Server Express, which integrates directly with Entity Framework's Code First feature. If you want to use a non-Express edition of SQL Server, see this section: Living without SQL Server Express.
Create New Project
- File | New | Project | DevForce 2010 | DevForce WPF Application
- Name it CodeFirstWalk
MainWindow.xaml: Our First Taste of WPF UI
- Open MainWindow.xaml. Make sure the XAML pane is showing.
- Make the window wider.
Width=800
is ok.
Title="MainWindow" Height="350" Width="800">
- Add a two-row grid with a title in the top row and a
ListBox
to display messages in the second row. We’re using data binding right away, binding the ListBox’s ItemSource
to a collection property called Messages
.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="Code First Walk in WPF"
FontSize="20" TextAlignment="Center" VerticalAlignment="Center"/>
<ListBox x:Name="messages" Grid.Row="1" ItemsSource="{Binding Messages}" />
</Grid>
- Close all windows (Alt-W, L).
We’ll return to the UI soon.
Add DevForce CF Marker File
- Add | New Item ([Ctrl+Shift+A]) | DevForce 2010 | DevForce CodeFirst File.
- This will also add the CF libraries we need.
- Name it
DevForce.cf
(although the name doesn’t matter at all).
- Close the editor window.
Create a Model
- Add | New Item | Class.
- Name it Model.
- Delete the template generated
Model
class.
- Add the
Category
class below.
[ProvideEntityAspect]
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
}
The ProvideEntityAspect
attribute requires us to add using IdeaBlade.Aop;
.
Add Custom EntityManager
We’ll start by creating it in this same Model.cs file.
You are welcome to move any or all of these classes into their own class files at any time.
- At the top of the file, define the following custom
ProductEntities EntityManager
class.
public class ProductEntities : EntityManager {}
The EntityManager
class requires using IdeaBlade.EntityModel;
.
- Add the
Categories EntityQuery
property so it’s easier for us to build queries of Category
entities.
public EntityQuery<Category> Categories { get; set; }
Build the Solution
- Build the solution (Ctrl-Shift-B). This is the first build with Code First model. (If you received a build error indicating that SQL Server Express is not installed, see Living without SQL Server Express).
Notice it generates ProductEntities.ibmmx
into the project.
- Look at the Build output in the Output window.
You should always build after changing the model to confirm that DevForce (re)generated the metadata .ibmmx file.
Did you get a build error that mentions Microsoft SQL Server Express? If so, see the Build Errors section below.
Create a ViewModel
Yes, we’ll use the MVVM pattern in this example. It’s super easy.
- Add | New Item | Class.
- Name it
MainWindowViewModel
.
- Make it a
public
class.
- Add a parameterless constructor.
- Call the following four methods:
Start();
SetManager();
AddTestData();
ShowTestData();
- Let Visual Studio (or R#) create stubs for these methods (work bottom up so the stubs are created top down).
Start()
- Delete the line throwing a
NotImplementedException
.
- Initialize a
Messages
collection property to hold our string
messages (remember back in the UI, we said we’d bind to it).
Messages = new ObservableCollection<string>();
The ObservableCollection
class requires using System.Collections.ObjectModel;
.
- Next call a
Log(…)
method that we’ll soon write:
Log("Starting application");
- Let Visual Studio (or R#) create the stub for the
Log
method.
- Add a
public
Messages
auto-property just below the Start
method.
- At this point, this section looks like so:
private void Start()
{
Messages = new ObservableCollection<string>();
Log("Starting application");
}
public ObservableCollection<string> Messages { get; set; }
private void Log(string startingApplication)
{
throw new NotImplementedException();
}
- Implement the
Log
method such that it inserts a numbered message at the front of the Messages
collection. This gambit causes more recent messages to appear at the top our messages ListBox
. The implementation follows:
private void Log(string message)
{
message = String.Format("{0}: {1}", ++_messageCounter, message);
Console.WriteLine(message);
Messages.Insert(0, message); }
private int _messageCounter;
- We are logging to the Visual Studio Console window for good measure.
SetManager()
We create and initialize your custom EntityManager
here.
We prefer to put EntityManager
creation and query and save logic such as you’ll see here in a Repository or DataServices
class. Not in this demo; that’s an exercise for the future.
- Delete the line throwing a
NotImplementedException
.
- Instantiate your
ProductEntities
class and assign it to a Manager
property.
- Add a private
Manager
auto-property just below the SetManager
method.
The server may not always be kind. An exception thrown on the server will be sent to the client and surfaced here as an EntityServerException
. We can catch them here in an EntityServerError
event handler which will:
- indicate that we’re handling the error,
- log the problem, and
- undo any pending changes in the
EntityManager
that may be the source of the problem.
- We can define the handler in a lambda. The result is as follows:
private void SetManager()
{
Manager = new ProductEntities();
Manager.EntityServerError += (s, e) =>
{
e.Handled = true; Log("Server error: " + e.Exception.Message);
Manager.RejectChanges(); };
}
private ProductEntities Manager { get; set; }
- You might want to add a breakpoint to the first line in the lambda expression in case we get an error and want to see the entire exception.
AddTestData()
- Delete the line throwing a
NotImplementedException
.
- Create a new
Category
entity and set its name.
var cat = new Category
{CategoryName = "Sweet Things on " + DateTime.Now.ToString("o")};
- Add it to the
Manager
, Log the fact that we are saving now, and call SaveChanges
.
Manager.AddEntity(cat);
Log("Saving new test data");
Manager.SaveChanges();
- The
AddTestData
method should look like this:
private void AddTestData()
{
var cat = new Category { CategoryName = "Sweet Things on " +
DateTime.Now.ToString("o") };
Manager.AddEntity(cat);
Log("Saving new test data");
Manager.SaveChanges();
}
ShowTestData
- Delete the line throwing a
NotImplementedException
.
- We’ll log the fact that we’re showing test data:
Log("Showing test data");
- Query for all
Category
entities. This will pick up both the one we just added and any others that are lurking in the database from previous application sessions.
Add this query:
var cats = Manager.Categories.ToList();
- Log the categories we fetched, using a helper method:
cats.ForEach(LogCats);
ToList()
and ForEach()
require using System.Linq;
.
- Let Visual Studio (or R#) create the stub for the
LogCats
method.
- Change the
obj
parameter name to cat
.
- Set format
string
: var fmt = "Category {0} Name={1}";
.
- Call Log:
Log(string.Format(fmt, cat.CategoryId, cat.CategoryName));
.
The ShowTestData
and LogCats
methods should look like this:
private void ShowTestData()
{
Log("Showing test data");
var cats = Manager.Categories.ToList(); cats.ForEach(LogCats);
}
private void LogCats(Category cat)
{
var fmt = "Category {0} Name={1}";
Log(string.Format(fmt, cat.CategoryId, cat.CategoryName));
}
Create the ViewModel in the View
Time to tie the View
(MainWindow.xaml) to the ViewModel
(MainWindowViewModel.cs). We’ll do that in the view’s code behind.
That is not often the best choice, but it’s good enough for our demo.
- Open MainWindow.xaml.cs.
- Delete every
using
except System.Windows
(optional).
In the constructor, assign the view’s DataContext
with a new instance of the MainWindowViewModel
ViewModel
class. The constructor looks like this when you’re done.
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
Run in Debug [F5]
It builds again. This time the build messages in the output window end as follows:
Model metadata created for ProductEntities<br />
Model metadata for ProductEntities.ibmmx is unchanged
The metadata ibmmx file is unchanged. But the assembly has been changed … because now that metadata file is embedded in the CodeFirstWalk.exe
assembly.
The application runs. And it works!
The messages appear top down in reverse order. At the top is the Category
we just created.
Run the app again and you’ll see two Categories
in the application window, the one we created in the previous session and the new one we just added.
How is that possible? We haven’t defined a database. We haven’t even named a database.
In the first session, when the AddTestData()
method asked the EntityManager
to save the new Category
, the EntityManager
issued the first command that requires a database. Somewhere in the chain of events that followed, DevForce gave the Entity Framework the name of a connection string. In our example, it supplied the name of the EntityManager
class (ProductEntities
).
Because there is no ProductEntities
connection string in the app.config
file, the Entity Framework Code First creates a database to match our entity model in SQL Server Express and calls it ProductEntities
.
As it stands, you must have Microsoft SQL Server Express installed and running or the Entity Framework can’t create the missing database for you. You can change that default.
Examine the database in SQL Server Management Studio:
The database exists and has a “Categories
” table with the two rows displayed in the UI.
Summary
In this part of the Code First Walkthrough, we:
- Created a new DevForce WPF Application
- Defined a DevForce entity model entirely in code using “Code First”
- Put our entity access code in a simple
ViewModel
and bound that ViewModel
to the View.
- Added new entities, saved them, and queried them back from the database.
- Let the Entity Framework create a database to match our
Model
.
Appendix: Common Build Errors
You got a build error such as the following:
An error occurred during metadata generation and a metadata file could not be created. Error: A connectionString was not found. Since SQLExpress is not installed, DevForce metadata discovery cannot continue. Add a .config file with a connectionString named 'ProductEntities' to this project. Providing a connection string at build time ensures that the correct model metadata is generated.
Entity Framework, by default, expects you to have installed Microsoft SQL Server Express. You can get around that by specifying a connection string in a configuration file or change the default database server to SQL Server by setting the DefaultConnectionFactory
. These options are discussed in the Advanced Database Connection Options topic of the DevForce Resource Center.
If you don't have SQL Server Express, but do have the full SQL Server installed, try adding the following static
constructor to the ProductEntities
. Remember to look closely at the parts of the baseConnectionString
to ensure they match your SQL Server name.
public class ProductEntities : EntityManager
{
static ProductEntities()
{
const string baseConnectionString =
"Data Source=.; " + "Integrated Security=True; " +
"MultipleActiveResultSets=True; " +
"Application Name=CodeFirstWalk";
Database.DefaultConnectionFactory =
new SqlConnectionFactory(baseConnectionString);
}
}
History
- v.1: Original
- v.2: Added information about DevForce based on a question in the comment thread
- v.3: Updated Before you start section to make it clear that DevForce and EF 4.1 must be installed