"Hello, DevForce" is a small application which walks you through many of the common steps in building a DevForce application.
The challenge: you'd like to build a DevForce desktop application but don't know where to begin. Maybe you've seen the quickie demo and tour, but want a few more details before you build your own application.
"Hello, DevForce" is a simple WPF application which is not about the UI. Instead, it shows how to choose a new project template, how to add an entity model and some simple logic, how to monitor activity, and how to run the application n-tier. All in a few easy to follow steps.
DevForce installs several Visual Studio project templates, a few of which are shown below:
In this walk through we'll use the DevForce n-Tier WPF Application template.
In Visual Studio, choose File / New Project from the main menu; under Visual Basic or C#, find the DevForce 2010 templates; select the DevForce n-Tier WPF Application template; name and locate the new solution (all as shown in the previous screen shot), and click OK. You should see something similar to the following:
The template created a WPF project named “WpfApplication1”, whose resulting assembly is destined for deployment on the client computer. Since we selected the n-Tier WPF application, the template also created a web project (“WpfApplication1Web”) that will ultimately be deployed on a web server and will host the DevForce EntityServer. Note, however, that it will be perfectly possible to run, and develop, this application on a single machine: to do so only requires changing the setting of a single property in the app.config file. We’ll do that later.
Our next job will be to add the entity model. In a DevForce application, both the server-side and client-side applications need access to the domain model. We can accomplish that in a couple of different ways:
Both options are perfectly fine, but we have to choose one, so we’re going to create a separate project for the model.
Create a new Windows class library project in the solution. Name it “DomainModel”.
Delete the “Class1.cs” file that Visual Studio creates; we won’t need it.
To the DomainModel project, add a new item, an ADO.NET Entity Data Model. Name the file “NorthwindIBModel”.
Clicking <Add> will launch a wizard to help you build your Entity Data Model. On the first dialog, accept the default choice of “Generate from database”:
On the “Choose Your Data Connection” dialog, select or create a connection to the NorthwindIB database, and tell Visual Studio to save the connections as “NorthwindIBEntityManager”:
The EDM Wizard retrieves schema information from the database and presents you with a tree control of choices for items on which to base entities in your model. Expand the Tables node and select the following tables:
Accept the default settings for the two checkboxes:
Also accept the default model namespace of “NorthwindIBModel” (the name we specified earlier for the model itself).
If you receive the following security warning message…
… click <OK> so that DevForce can use the Entity Data Model to generate a C# or VB domain model. Until and if you turn this warning off, you will see it whenever DevForce wants to regenerate your domain model code.
The EDM designer will create an XML file with extension .edmx to capture the information you (and the database) supplied using the wizard. Then it will render the newly created model visually:
If you open the Properties panel you will see, in addition to standard Entity Framework properties, many DevForce-specific ones: If you cannot see the DevForce-specific options, please verify that the DevForce EDM Designer Extension is installed.
If you click in white space in the designer window, you see the properties that apply to the model as a whole:
If you click on a single entity, such as Customer, you see a difference set of properties, including DevForce-specific properties:
These properties allow you to specify authorization requirements for querying and saving the entity. This is discussed in more detail in a security-oriented Learning Resources example.
Click on a specific property such as the primary key CustomerID…
… or on a navigation property, such as Customer.Orders…
… to see the Entity Framework and DevForce properties specific to that item.
As you can see, DevForce is completely integrated with the Entity Model Designer. It also takes over all code generation for the .NET object model. If you select the NorthwindIBModel.edmx file in the Solution Explorer, then right-click it and select Properties, you can see that DevForce has removed the default Custom Tool name so that the default code generator won’t be used:
If at any time you wish to restore use of the default (Entity Framework) code generator, open the EDMX in the designer and set the “DevForce Enabled” property to false, then save the model. This will remove the DevForce-generated files and re-enable Entity Framework code generation.
Now let’s have a look at the generated code.
Three files now accompany the .edmx:
NorthwindIBModel.edmx.tt The template used in the generation of the domain model to NorthwindIBModel.IB.Designer.cs.
NorthwindIBModel.edmx.ReadMe describes how to customize the DevForce code generation templates.
NorthwindIBModel.IB.Designer.cs contains the.NET code generated by DevForce:
Note how, at the very top of this file, you are instructed not to modify this code. DevForce owns it, and will regenerate it at will to keep up with changes you make to the Entity Data Model. You’ll do your customization in “developer partial class” files; we’ll discuss those shortly. But for now, have a look at some of the items in the generated code:
You see entity classes such as Customer, Employee, Order, and OrderDetail. Associated with each of these entities is an EntityPropertyNames class, and a PropertyMetadata class. The EntityPropertyNames class provides string constants for the names of each of the entity’s properties: you can use these throughout your own code, wherever a property name is needed, in preference to hard-coding the string value. The PropertyMetadata class contains static EntityProperty fields corresponding to each of the entity’s business properties, which you can use in your code when an EntityProperty is required.
The main entity classes, such as Customer, inherit from IdeaBlade.EntityModel.Entity, and contain definitions of all of the entity’s business properties. Many other facilities are available via the Entity base class.
Before we leave our Entity Data Model, let’s fix a couple of property names. The Employee entity, like the Employee table in the NorthwindIB database, has an association with itself, in order to reflect the chain of management. This self-association yields two navigation properties, one to represent the Employee upstream from the current one – i.e., his Manager – another to represent those downstream – his DirectReports. The EDM designer knew from the discovered relationship that two such properties were needed, but it didn’t know enough about what they actually represent to name them very well, so it just called them Employee1 and Employee2.
By selecting the Employee1 property and viewing its properties, we see that its Return Type is a Collection of Employee:
Employee1 must therefore represent the downstream Employees; we’ll rename it to “DirectReports”. Double-checking the Employee2 properties verifies what we expect – that it returns an Instance of Employee – so we know that one represents the Manager and rename it accordingly.
We also happen to know that the Employee navigation property on the Order entity, which was automatically added to the model upon discovery of the many-to-one relationship in NorthwindIB between Orders and Employees, represents an employee who acts in the role of a sales representative for that Order. We’ll rename it to “SalesRep”. |
There’s a great deal more we could, and eventually will, do to refine our model, but it’s already sufficient fleshed out to support data retrieval and persistence, so let’s see how we can use it to do that.
You may note that both of those projects already have references to several DevForce assemblies. These were put there by the project template that you used to create the solution initially.
The DevForce project template created a WPF Application project, so let’s add some code to that. First we’ll flesh out – ever so slightly -- the MainWindow window that the template created, by adding a TextBlock within a ScrollViewer inside the window’s layout grid.
The XAML we’re starting with is this:
XAML | <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" style="width:525px"> <Grid> </Grid> </Window> |
We’ll enhance that to this:
XAML | <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" style="width:525px"> <Grid> <ScrollViewer> <TextBlock Name="_outputTextBlock" TextWrapping="Wrap" FontFamily="Courier New" /> </ScrollViewer> </Grid> </Window> |
For the purpose of this very simple application, we’re going to use that TextBlock control like the output window of a Console application, and simply write output strings to it. (We can get more sophisticated later, with streaming video, cascading 3D animations, chirping monkeys, and touch-screen facilities!)
We could, of course, put code to retrieve and display data in the “code behind” area of the Window itself, but let’s just put ourselves off to a good start and build a very basic Model-View-ViewModel architecture so we can (a) keep our view extremely lightweight and (b) minimize the obstacles to our application’s testability.
We’ll create a class we’ll call MainWindowViewModel, give it a public string property named Output, and just bind our TextBlock to the value of that property:
XAML | <Grid > <ScrollViewer> <TextBlock Name="_outputTextBlock" TextWrapping="Wrap" FontFamily="Courier New" Text="{Binding Output}" /> </ScrollViewer> </Grid> |
The MainWindow’s code behind starts out like this:
C# | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfApplication1 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } } |
VB | Imports System Imports System.Collections.Generic Imports System.Linq Imports System.Text Imports System.Windows Imports System.Windows.Controls Imports System.Windows.Data Imports System.Windows.Documents Imports System.Windows.Input Imports System.Windows.Media Imports System.Windows.Media.Imaging Imports System.Windows.Navigation Imports System.Windows.Shapes Namespace WpfApplication1 ''' <summary> ''' Interaction logic for MainWindow.xaml ''' </summary> Partial Public Class MainWindow Inherits Window Public Sub New() InitializeComponent() End Sub End Class End Namespace |
We’ll add a handler for the window’s Loaded event to do all of the following:
C# | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfApplication1 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.Loaded += new RoutedEventHandler(MainWindow_Loaded); } void MainWindow_Loaded(object sender, RoutedEventArgs e) { MainWindowViewModel aMainWindowViewModel = new MainWindowViewModel(); this.DataContext = aMainWindowViewModel; aMainWindowViewModel.Start(); } } } |
VB | Imports System Imports System.Collections.Generic Imports System.Linq Imports System.Text Imports System.Windows Imports System.Windows.Controls Imports System.Windows.Data Imports System.Windows.Documents Imports System.Windows.Input Imports System.Windows.Media Imports System.Windows.Media.Imaging Imports System.Windows.Navigation Imports System.Windows.Shapes Namespace WpfApplication1 ''' <summary> ''' Interaction logic for MainWindow.xaml ''' </summary> Partial Public Class MainWindow Inherits Window Public Sub New() InitializeComponent() AddHandler Loaded, AddressOf MainWindow_Loaded End Sub Private Sub MainWindow_Loaded(ByVal sender As Object, _ ByVal e As RoutedEventArgs) Dim aMainWindowViewModel As New MainWindowViewModel() Me.DataContext = aMainWindowViewModel aMainWindowViewModel.Start() End Sub End Class End Namespace |
Note that we did not have to know that the view’s Loaded event handler had to be a RoutedEventHandler, etc.; we simply typed in “this.Loaded +=”, and then pressed the TAB key a couple of times to let Visual Studio set up an appropriate stub. We then filled in the code representing the actions we wanted to take place.
Now let’s create that MainWindowViewModel class. Add a new class by that name to the WpfApplication1 project and flesh out its code to look like this:
C# | using System; using System.Collections.Generic; using System.Linq; using System.Text; using SysComp = System.ComponentModel; using DomainModel; using IdeaBlade.EntityModel; namespace WpfApplication1 { class MainWindowViewModel : SysComp.INotifyPropertyChanged { #region Constructor public MainWindowViewModel() { } #endregion Constructor #region Methods public void Start() { FirstSample(); } public void FirstSample() { StringBuilder aStringBuilder = new StringBuilder("Started FirstSample()...\n"); var customersQuery = from cust in _mgr.Customers where cust.ContactTitle == "Sales Representative" orderby cust.CompanyName select cust; aStringBuilder.Append(string.Format( "Retrieved {0} customers\n", customersQuery.ToList().Count)); foreach (Customer aCustomer in customersQuery) { aStringBuilder.Append(string.Format("Customer: {0}\n", aCustomer.CompanyName)); } Output = aStringBuilder.ToString(); } #endregion Methods #region Data Bound Properties public string Output { get { return _output; } set { _output = value; RaisePropertyChanged("Output"); } } #endregion Data Bound Properties #region INotifyPropertyChanged public event SysComp.PropertyChangedEventHandler PropertyChanged = delegate { }; protected void RaisePropertyChanged(string propertyName) { PropertyChanged(this, new SysComp.PropertyChangedEventArgs( propertyName)); } #endregion #region Private Fields private NorthwindIBEntityManager _mgr = new NorthwindIBEntityManager(); string _output = ""; #endregion Private Fields } } |
VB | Imports Microsoft.VisualBasic Imports System Imports System.Collections.Generic Imports System.Linq Imports System.Text Imports DomainModel Imports IdeaBlade.EntityModel Imports System.ComponentModel Namespace WpfApplication1 Friend Class MainWindowViewModel Implements INotifyPropertyChanged #Region "Constructor" Public Sub New() End Sub #End Region ' Constructor #Region "Methods" Public Sub Start() FirstSample() End Sub Public Sub FirstSample() Dim aStringBuilder As New StringBuilder( _ "Started FirstSample()..." & vbLf) Dim customersQuery = From cust In _mgr.Customers Where cust.ContactTitle = "Sales Representative" Order By cust.CompanyName Select cust aStringBuilder.Append(String.Format("Retrieved {0} customers" _ & vbLf, customersQuery.ToList().Count)) For Each aCustomer As Customer In customersQuery aStringBuilder.Append(String.Format("Customer: {0}" _ & vbLf, aCustomer.CompanyName)) Next aCustomer Output = aStringBuilder.ToString() End Sub #End Region ' Methods #Region "Data Bound Properties" Public Property Output() As String Get Return _output End Get Set(ByVal value As String) _output = value RaisePropertyChanged("Output") End Set End Property #End Region ' Data Bound Properties #Region "INotifyPropertyChanged" Public Event PropertyChanged As _ PropertyChangedEventHandler _ Implements INotifyPropertyChanged.PropertyChanged Protected Sub RaisePropertyChanged(ByVal propertyName As String) RaiseEvent PropertyChanged(Me, _ New PropertyChangedEventArgs(propertyName)) End Sub #End Region #Region "Private Fields" Private _mgr As New NorthwindIBEntityManager() Private _output As String = "" #End Region ' Private Fields End Class End Namespace |
Note a few things about our ViewModel:
Beyond that, our view model, when the Start() method is run, simply submits a LINQ query requesting Customers who meet some condition; then uses that query to retrieve, first a count of such Customers, and then the Customer objects themselves. With each operation it writes text to the Output property.
We’re almost ready to go, but we must make a couple of tweaks to the client-side App.config file to run the application initially in a single tier (or in a client-server configuration if your database is located elsewhere). Once we’ve tested in a single tier, we’ll switch to n-tier to show how simply that can be accomplished. The client-side App.config for the single-tier or client-server app looks like this:
XML | <?xml version="1.0"?> <configuration> <configSections> <section name="ideablade.configuration" type= "IdeaBlade.Core.Configuration.IdeaBladeSection, IdeaBlade.Core"/> </configSections> <ideablade.configuration version="6.00" xmlns="http://schemas.ideablade.com/2010/IdeaBladeConfig"> <logging logFile="DebugLog.xml"/> <objectServer remoteBaseURL="http://localhost" serverPort="9009" serviceName="EntityService.svc" > <clientSettings isDistributed="true" /> </objectServer> <!-- Additional configuration can be added to override defaults. See the sample config files in the Learning Resources for more information. --> </ideablade.configuration> </configuration> |
Change the line:
XML | <clientSettings isDistributed="true"/> |
to read:
XML | <clientSettings isDistributed="false"/> |
Then, at the same level as the <ideablade.configuration> section, add a <connectionStrings> section as shown below. You can find the <connectionString> section you need in the copy of App.Config in the DomainModel project, where it was written by the Entity Data Model designer when you built the model. Simply copy it from there and paste it into the App.config for the client project (WpfApplication1).
The completed App.config should look like this:
XML | <?xml version="1.0"?> <configuration> <configSections> <section name="ideablade.configuration" type= "IdeaBlade.Core.Configuration.IdeaBladeSection, IdeaBlade.Core"/> </configSections> <ideablade.configuration version="6.00" xmlns="http://schemas.ideablade.com/2010/IdeaBladeConfig"> <logging logFile="DebugLog.xml"/> <objectServer remoteBaseURL="http://localhost" serverPort="9009" serviceName="EntityService.svc" > <clientSettings isDistributed="false" /> </objectServer> <!-- Additional configuration can be added to override defaults. See the sample config files in the Learning Resources for more information. --> </ideablade.configuration> <connectionStrings> <add name="NorthwindIBEntityManager" connectionString= "metadata=res://*/NorthwindIBModel.csdl|res: //*/NorthwindIBModel.ssdl|res://*/NorthwindIBModel.msl; provider=System.Data.SqlClient;provider connection string= "Data Source=.;Initial Catalog=NorthwindIB; Integrated Security=True;MultipleActiveResultSets= True"" providerName="System.Data.EntityClient" /> </connectionStrings> </configuration> |
Now we’re ready to run our app. A few seconds after launch, you should see the following display:
So far, so good, but now let’s see how we add custom logic to our business model. As we previously pointed out, the code that currently comprises the business model – all stored in the file NorthwindIBModel.IB.Designer.cs -- is owned entirely by DevForce and will be overwritten by DevForce whenever you change the content of the Entity Data Model (.edmx) file. That makes it a very bad place to put custom code! Where such code should be put is in partial class files that extend the existing model.
You can code such files yourself from scratch, but you can also have the code generator write starter versions for you. To do that:
You will now see one new file in the DomainModel project for each of the entities that you included in your model, as at right. Let’s look at the contents of the Customer.cs file: |
C# | using System; using System.Linq; using IbEm = IdeaBlade.EntityModel; using IdeaBlade.Core; namespace DomainModel { // The IdeaBlade DevForce Object Mapping Tool generates this class once // and will not overwrite any changes you make. You can place your custom // application-specific business logic in this file. // Generated: 3/31/2010 6:17:32 PM // // This partial class is the companion to the class regenerated by the // Object Mapping tool whenever the model changes. public partial class Customer : IbEm.Entity { } } |
VB | Imports System Imports System.Linq Imports IbEm = IdeaBlade.EntityModel Imports IdeaBlade.Core 'The IdeaBlade DevForce Object Mapping Tool generates this class once 'and will not overwrite any changes you make. You can place your custom 'application-specific business logic in this file. 'Generated: 7/13/2010 11:54:13 AM ' 'This partial class is the companion to the class regenerated by the 'Object Mapping tool whenever the model changes. Partial Public Class Product Inherits IbEm.Entity End Class |
That’s pretty unimposing. But since it’s a partial class it can be enhanced to extend the generated model almost without limit.
For example, let’s add a static Create() factory method so we can create and initialize new Customers in a standard way throughout our application.
First we add a statement to make items in the DomainModel namespace available within the class:
C# | using DomainModel; |
VB | Imports DomainModel |
Then we add the Create() method:
C# | static Customer Create(string companyName) { Customer newCustomer = new Customer(); newCustomer.CustomerID = System.Guid.NewGuid(); newCustomer.CompanyName = companyName; newCustomer.EntityAspect.AddToManager(); return newCustomer; } |
VB | Private Shared Function Create(ByVal companyName As String) As Customer Dim newCustomer As New Customer() newCustomer.CustomerID = System.Guid.NewGuid() newCustomer.CompanyName = companyName newCustomer.EntityAspect.AddToManager() Return newCustomer End Function |
Our Create() method requires a company name. It instantiates a new Customer, and because the Customer type has a GUID primary key, the code assigns a primary key value. It then assigns the companyName value passed to the method to the corresponding property on the new entity, and then – very importantly – puts the new entity into the local cache and under an EntityManager’s control. It then returns a reference to the new entity to the calling method.
You can add other custom methods, and custom properties, as needed and as desired. But suppose what you want to do is to override the behavior of a generated property. You can’t override or shadow the property because it’s defined in the same class you’re working in. (Remember, you’re in a partial class that is extending the existing, generated partial class.)
So how do you accomplish what you need? The answer is to use a property interceptor. This is a bit of code that you provide and decorate with attributes that describe to DevForce your intentions for it. It is then called by DevForce at an appropriate point in a Get or Set process to alter the result of that operation. For example, here’s an interceptor that converts the value of Customer.Company to its uppercase equivalent during a Get operation:
C# | [AfterGet(EntityPropertyNames.CompanyName)] public String UppercaseLastName(String companyName) { if (!String.IsNullOrEmpty(companyName)) { return companyName.ToUpper(); } else { return String.Empty; } } |
VB | <AfterGet(EntityPropertyNames.CompanyName)> _ Public Function UppercaseLastName(ByVal companyName _ As String) As String If Not String.IsNullOrEmpty(companyName) Then Return companyName.ToUpper() Else Return String.Empty End If End Function |
If this interceptor is defined within the Customer class, DevForce assumes it is meant to apply to Customers; and because we have said so in the [AfterGet] attribute with which we decorated our interceptor method, DevForce knows the interceptor is meant to be applied only to the CompanyName property.
Run the application again and you’ll see the result of adding the AfterGet interceptor:
You can define interceptors to be as general as you need: for example, you can provide an interceptor method that will apply to every property of Customer; or even to every property of every entity in your model! You can define interceptors that jump into the middle of a Get before a value is obtained from the entity instance (which you could use, for example, to prevent certain data from being made available to unauthorized users); after the value is obtained from the business object but before it is delivered to the requestor (as in the interceptor above); before a proposed new value is pushed into the business object for which it is targetted; and after it is so pushed. You can alter the process of getting and setting business object property values in any way you need to do it.
One final note on the developer classes: recall that we got DevForce to generate starter partial classes for extending the entities in our business model by setting the Generate Developer Classes property on the ConceptualEntityModel to true. Even though we haven’t set that value back to false, we’re in no danger of losing the customizations we just made to Customer.cs. DevForce will never overwrite an existing developer partial class.
Now we’re ready for the last step, running the application in multiple tiers. We created this application using the DevForce N-Tier WPF Application template, and we’ve got a web application project in our solution which we haven’t used yet. The web application project hosts the DevForce Business Object Server, and is almost ready to go, with a few more changes to the config files.
Client-side App.Config. We toggled the isDistributed flag off earlier to show the application running in a single tier. Let’s turn that flag back on now:
XML | <clientSettings isDistributed="true" |
This flag, along with the objectServer settings indicating the URL, port and name of the BOS, will tell the client application where the BOS is located. The BOS in the web application project here is by default also listening at this address, so no additional wiring is required.
XML | <objectServer remoteBaseURL="http://localhost" serverPort="9009" serviceName="EntityService.svc" > <clientSettings isDistributed="true" /> </objectServer> |
Server-side Web.Config. We need to ensure the connectionStrings section is available here in the web.config, since data access will now be performed here on the EntityServer and not in the client application. We previously copied the connectionStrings from the Domain Model app.config into the executable project’s app.config; we can now copy (or move if you prefer) this information into the web.config. We don’t need any other changes in the web.config, so it should look something like this when done:
XML | <?xml version="1.0"?> <configuration> <configSections> <section name="ideablade.configuration" type= "IdeaBlade.Core.Configuration.IdeaBladeSection, IdeaBlade.Core"/> </configSections> <connectionStrings> <add name="NorthwindIBEntityManager" connectionString= "metadata=res://*/NorthwindIBModel.csdl|res: //*/NorthwindIBModel.ssdl|res://*/NorthwindIBModel.msl; provider=System.Data.SqlClient;provider connection string= "Data Source=.;Initial Catalog=NorthwindIB; Integrated Security=True;MultipleActiveResultSets= True"" providerName="System.Data.EntityClient" /> </connectionStrings> <ideablade.configuration version="6.00" xmlns="http://schemas.ideablade.com/2010/IdeaBladeConfig" > <logging logFile="log\DebugLog.xml"/> <!-- Additional configuration can be added to override defaults. See the sample config files in the Learning Resources for more information. --> </ideablade.configuration> </configuration> |
With these changes we’re ready to fire up the application in n-tier! One more thing before hitting F5, double-check that the web project will start up automatically. The “Always Start When Debugging” flag should be True, to ensure the ASP.NET Development Server starts when the client project does.
Now build and run, and you’ll see … the same application window you saw before. But there’s a difference this time, the application is now running in multiple tiers – the WPF client application is now communicating with the EntityServer, which is now responsible for communicating with the data tier. The EntityServer here is running on the same machine (localhost), but you can now deploy it to another machine when you’re ready. Deployment is a complex topic, covered separately, but you’ve now created an application which can run in either a single tier or n-tier at the flip of a switch!
What is actually happening as we run the applications? When is it asking for data? What does the SQL look like?
We can always monitor activity on the SQL Server using SQL Profiler. Here we assume SQL Server 2008.
Launch Microsoft SQL Server Management Studio.
Select “Tools, SQL Server Profiler” from the menu.
Select “File , New Trace ..” from the Profiler menu and connect to your database server
Click [Run] on the [Trace Properties] dialog.
Return to Visual Studio and re-run the application [F5]
The trace window fills, showing us exactly how we’re hitting the database.
As your application runs DevForce generates trace messages containing information (and warnings) about its actions. These messages will help you diagnose and debug problems should they occur. You can also use the DebugFns.WriteLine and TraceFns.WriteLine methods to send your own debug and trace messages to DevForce.
By default DevForce writes these messages to a trace log file named DebugLog.xml in the executable directory.
Open Windows Explorer.
Navigate to the ..\bin\debug directory under the executable’s directory.
Launch DebugLog.xml.
The log appears in a browser window.
Each row speaks of some event during the life of the last application run. You’ll see database access events among other event occurring from the start of the application until it shuts down.
You can launch the DebugLog while the application is running and refresh the browser from time to time to see how the log is progressing as you move through the application.
DevForce also supplies a sample utility, the TraceViewer, which comes in WinForms and WPF flavors. The TraceViewer affords a friendlier and more dynamic look at logged activity as it provides a “live” view of trace activity for your running application. You can launch the TraceViewer from the IdeaBlade DevForce/Tools menu. It can also be linked directly into your application.