Showing posts with label SQL Server. Show all posts
Showing posts with label SQL Server. Show all posts

Sunday, October 9, 2011

DataContext on the Mortgage Home Page

In our last post we covered integrating the database CRUD operations through our MortDB DataContext class. In this post we get to see how simple working with a DataContext can be.

The MortgageHome Page

In a previous post we stopped just short of diving into the MortgageHome.xaml's code behind.



















Here is the code behind:

namespace MortCalc
{
    /// <summary>
    /// Code Behind for MortgageHome.xaml
    /// </summary>
    public partial class MortgageHome : Page
    {

        public MortgageHome()
        {
            InitializeComponent();
        }

        private void Amortize_Button_Click(object sender, RoutedEventArgs e)
        {
            //error message tell them to select a mortgage
            if (mortgageListBox.SelectedItem == null)
            {
                MessageBoxResult result = MessageBox.Show(
                    (Window)Parent, "Please Select a Mortgage.",
                    "Error", MessageBoxButton.OK, 
                    MessageBoxImage.Error);
            } else{
                // View Mortgage Amortization Report
                MortgageReportPage mortgageReportPage = new 
                    MortgageReportPage(mortgageListBox.SelectedItem);
                this.NavigationService.Navigate(mortgageReportPage);
            }
        }

        private void New_Button_Click(object sender, RoutedEventArgs e)
        {
            // View Mortgage Amortization Report
            MortgageReportPage mortgageReportPage = 
                new MortgageReportPage();
            this.NavigationService.Navigate(mortgageReportPage);
        }

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            var results = from m in DBApi.DB.mortDB.Mortgage
                            select m;

            mortgageListBox.ItemsSource = results.ToList();
        }
    }
}

When looking at the form there are 3 main things that it needs to accomplish:
  1. Load the Names of the Mortgages
  2. Create a new Mortgage
  3. Amortize a selected Mortgage

Load the Names of the Mortgages

The Mortgage Names are loaded into the Listbox component.

<ListBox Name="mortgageListBox"  DisplayMemberPath="Name" FontSize="18" />
We can see that DisplayMemberPath is set to "Name". "Name" in this context makes little sense.

Name is a property in the Mortgage class. Knowing this we can now make sense of the Page_Loaded event function.
private void Page_Loaded(object sender, RoutedEventArgs e)
{
    var results = from m in DBApi.DB.mortDB.Mortgage
                    select m;

    mortgageListBox.ItemsSource = results.ToList();
}

The LINQ to SQL code:

var results = from m in DBApi.DB.mortDB.Mortgage select m;
is doing a SQL like query from the Mortgage table in our database, in code. This is equivalent to the following T- SQL:
SELECT *
FROM Mortgage
We just avoided creating a connection, sending a query, iterating through a result set, ect...

Now all that's left is to bind the results to the ListBox.

mortgageListBox.ItemsSource = results.ToList();
By binding the data we avoided for loops of loading details into the list box and matching names to objects located globally in the Page class. Data binding is powerful and elegant.

Create a New Mortgage

When the New button is clicked the Click event gets fired per our xaml code.
<Button Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" Click="New_Button_Click" Style="{StaticResource buttonStyle}">
    New
</Button>
Here is the event click function:

private void New_Button_Click(object sender, RoutedEventArgs e)
{
    // View Mortgage Amortization Report
    MortgageReportPage mortgageReportPage = new MortgageReportPage();
    this.NavigationService.Navigate(mortgageReportPage);
}
All we are doing here is navigating to the new page. We instantiate the Page object and tell our NavigationWindow class to Navigate to it. All specifying and saving of data will take place in this new Page.

Amortize a selected Mortgage

When someone selects an existing Mortgage we need a way to tell the MortgageReportPage which record we are working on.
private void Amortize_Button_Click(object sender, RoutedEventArgs e)
{
    //error message tell them to select a mortgage
    if (mortgageListBox.SelectedItem == null)
    {
        MessageBox.Show((Window)Parent, 
            "Please Select a Mortgage.",
            "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    } else{
        // View Mortgage Amortization Report
        MortgageReportPage mortgageReportPage = new MortgageReportPage
            (mortgageListBox.SelectedItem);
            this.NavigationService.Navigate(mortgageReportPage);
    }
}
Check to make sure they selected something and if not let them know they need to.

The only way this Navigation to the MortgageReportPage differs from the last method is the passing in of the ListBox's selected item. I am not operating on the value at all and I am not casting as a Mortgage. Very Simple.

The next post will be dealing with the MortgageReport Page and how it binds data in the Mortgage class to the GUI components.


Download the Code Here

Saturday, October 8, 2011

Database and CRUD

In the last post we covered some of the Navigation/xaml code and stopped just short of binding data and what the code behind looks like.

I have worked on past projects where CRUD operations were hand coded. This makes sense if you don't have binding. Now that binding, Entity Framework, LINQ to SQL, LINQ to Objects ect... exist hand coding CRUD is a more error prone and a completely antiquated waste of time.

In a previous post we created the database file. In this post we are going to go into integrating the database into our Mortgage Calculator.

SQL Metal

One requirement I had initially for the Database was use of LINQ to SQL. SQL Server Compact supports this. However, there's a catch!

Part of developing LINQ to SQL applications involves modeling the data after the structure of the database. A code file is then generated which contains a DataContext that your application can use to preform CRUD operations on.

Normally, if you are using any other SQL Server database type, in Server Explorer, you simply drag the tables in your project into the dbml file area.

Visual studio doesn't support SQL Server Compact LINQ to SQL DataContext file generation.

This is where SQL Metal comes into play. To generate your DataContext file you have to run the command line utility and specify your database and the output file you would like to generate.
  1. Navigate to sqlmetal.exe
  2. Specify your .sdf file location
  3. Specify your c# file and location you wish to generate.
  4. Run it.
  5. Add the new cs file to your solution.
My Command line statement looked like this:
C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin>sqlmetal C:\MortCalc\MortCalc\DBApi\MortDB.sdf /code:C:\MortCalc\MortCalc\DBApi\MortDB.cs

DB API

Lets take a look at the DBApi folder:


DB.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MortCalc.DBApi
{
    static class DB
    {
        public static MortDB mortDB = new MortDB("DBApi\\MortDB.sdf");
    }
}

DB is a static class which contains an instantiation of MortDB. MortDB is itself a DataContext class. I wanted 1 and only 1 instance of MortDB accessible from the whole application. Problems arise if the same DataContext is not used everywhere.

For example: I had 2 different instances of MortDB getting created; one in the Home Page and one in a page where I saved data. When I navigated back to the Home Page the record I had just saved was missing. Having 1 MortDB solved this problem. I also wanted to avoid sending around objects inside the program unnecessarily.


MortDB.cs:

MortDB.cs is the file that SQL Metal generated. It contains the DataContext class MortDB which will handle all the CRUD operations. It also contains the Mortgage class. This is the only model class of the only table in our Database.

Inside of Mortgage all of our table columns have sections of code dedicated to them.

partial void OnPrincipalChanging(decimal value);
partial void OnPrincipalChanged();
...
[Column(Storage="_Principal", DbType="Decimal(18,2) NOT NULL")]
public decimal Principal
{
    get
    {
        return this._Principal;
    }
    set
    {
        if ((this._Principal != value))
        {
            this.OnPrincipalChanging(value);
            this.SendPropertyChanging();
            this._Principal = value;
            this.SendPropertyChanged("Principal");
            this.OnPrincipalChanged();
        }
    }
}

When something sets the property (the binded-to object) the partial unimplemented Changing and Changed methods are called.


MortDB.Helper1.cs:

MortDB.Helper1.cs is my other half of the MortDB.cs partial file implementation.

partial void OnPrincipalChanging(decimal value)
{
    if(value <= 0)
        AddError("Principal", "Principal must be greater than 0.");
    else
        RemoveError("Principal", "Principal must be greater than 0.");
}

I've added my own validation logic for incoming values to adjust errors accordingly.

If I were to add a new column to the Mortgage table I would be forced to regenerate the MortDB.cs file. Because of partial classes and my particular implementation being in a separate file, I don't have to worry about recoding my changes every time the file gets regenerated.

All that's left is to add a mechanism for individual columns to find their applicable errors.

public void AddError(string propertyName, string error)
{
    if (!errors.ContainsKey(propertyName))
        errors[propertyName] = new List<string>();

    if (!errors[propertyName].Contains(error))
        errors[propertyName].Add(error);
}

 
public void RemoveError(string propertyName, string error)
{
    if (errors.ContainsKey(propertyName) &&
        errors[propertyName].Contains(error))
    {
        errors[propertyName].Remove(error);
        if (errors[propertyName].Count == 0) 
            errors.Remove(propertyName);
    }
}

public string this[string columnName]
{
    get
    {
        return (!errors.ContainsKey(columnName) ? null :
            String.Join(Environment.NewLine, errors[columnName]));
    }
}

This last method gets called when a binded-to control requests errors for a column name.

In our next post we will be using the MortDB DataContext in the code.

Download the Code Here

Mortgage Calculator Database

SQL Server Compact

In the last post I gave a basic overview of my reasons and goals for Mortgage Calculator.

I love Microsoft. In particular I love how much time and effort they spend making the developer's life easier.

When I do anything outside my current job I default to .NET and C#.

While there are many capable database technologies out there, I was most certainly going to settle on something Redmond has implemented.

For Mortgage Calculator, I wanted something simple, light and capable for the database back end.

Aside from SQL Server, MS has Access.

SQL Server itself comes in a variety different flavors. The 2 most applicable being:
  • SQL Server Express
  • SQL Server Compact

MS Access is out, because, well it's Access. Also, I was keen on using LINQ to SQL.

SQL Server Express is more then capable. However, in order to use Express in your application you need to embed the SQL Server Express install into your install. This might be acceptable but I would just prefer to avoid it.

SQL Server Compact is file based. The beauty of which is that installing it to a user's machine involves adding the .sdf file and a few dlls for communicating with the file.

Database Table(s)

Because the concept of Mortgage Calculator is rather simple the information that needs to be stored is also simple.

Remember from the first post we want to keep track of the following values:
  • Principal
  • Interest
  • Extra Payments
  • Balance
  • Home Value
  • Tax
  • Insurance
  • Association Dues
  • Maintenance
  • Total Monthly Cost
  • Equity
  • Loan To Value
  • Rental Income
  • Cash Flow
  • Tax Appreciation
  • Insurance Appreciation
  • Association Dues Appreciation
  • Maintenance Appreciation
  • Rent Appreciation
  • Home Value Appreciation

Again, due to the simplicity of the app it is assumed there is only one user per install (potential future improvement). All we are left with is individual Mortgages that need to be amortized. Therefore we have 1 table.

Lets create it.


Numeric to Decimal

Because float and double values are not exact (Greg Dolley has a good write up on the Double vs Decimal issue) and it's accepted practice, we are going to be typing our money and interest rates in decimal in C#.

Decimal's counter part in SQL Server Compact is numeric.

Notice we have 2 different numeric types:
  1. numeric(18,2)
  2. numeric(18,5)
The first number is Precision and determines the number of digits in the value. The second is Scale and determines the number digits to the right of the decimal place.

So our values become something like the following:

1,234,567,890,123,456.78
1,234,567,890,123.45678

Because those values can become large and using decimals can be 20x more slow then doubles or floats we have to be careful to limit the input and processing on the machine.

In a future release I will have to update the interest rates to have more scale.

Currently, rounding is 5 decimal places which means rounding can result in:
  • $100,000,000 x 0.000009 = $900, $900 / 12 = $75
This means someone lost $75 a month. We want to aim for less than $0.01 a month.
  • $100,000,000 x 0.0000000009 = $0.09, $0.09 / 12 = $0.0075
Now someone is only losing $0.09 a year. 18 places means the interest rate can be 1,234,567.0000000009%. We could cap the interest rates at 1000% and feel comfortable that anything over that is for theoretical purposes and outside the scope of this project.

Show - Expand

There are several "Show" and "Expand" bit/boolean values in the table. These deal with preserving GUI state for different Mortgages and will be covered in a future post.

Null or Not Null

The only columns that are not allowed to be null are the Key, Name and the values necessary to do a basic Amortization.
  • Key
  • Name
  • Principal
  • Interest Rate
  • Term
For everything else we would prefer not to enforce updating information if we don't have to.

In the next post we will be exploring more of the xmal side of our Navigation and Home Page.


Download the Code Here