Navigating XML (LINQ to XML series – part 4)

In my last few posts on LINQ to XML (part 1, part 2 and part 3) I’ve shown you a starter on navigating around XML data. In this post I’ll continue to show you how to navigate through XML data by showing you how to navigate around sibling elements.

First consider this code:

XElement root = new XElement("root",
    new XElement("FirstChild"),
    new XElement("SecondChild"),
    new XElement("ThirdChild"),
    new XElement("FouthChild"),
    new XElement("FifthChild"));

Which produces the following XML structure:

<root>

<FirstChild />

<SecondChild />

<ThirdChild />

<FouthChild />

<FifthChild />

</root>

We can access the ThirdChild with this code:

XElement child = root.Element("ThirdChild");

From that point, we can also get access to its siblings.

To access the siblings that occur before the element we have a reference to then we can use ElementsBeforeSelf. As with Elements this returns an IEnumerable<XElement> object which allows us to iterate over the result, like this:

IEnumerable<XElement> elements = child.ElementsBeforeSelf();

foreach (XElement element in elements)
    Console.WriteLine(element);

The result is:

<FirstChild />

<SecondChild />

Conversely, we can get the siblings that come after the element we have a reference to with ElementsAfterSelf. Like this:

IEnumerable<XElement> elements = child.ElementsAfterSelf();

The result in this case will be:

<FouthChild />

<FifthChild />

Technorati Tags: ,,,

Navigating XML (LINQ to XML series – Part 3)

In my last two posts (part 1 and part 2) I’ve been introducing you to the the new XML classes in .NET 3.5. In this post I’ll continue that and show you some of the ways to navigate through XML.

First of all, lets start with a simple hierarchy of XML elements:

XElement root = new XElement("FirstGeneration",
                    new XElement("SecondGeneration",
                        new XElement("ThirdGeneration",
                            new XElement("FourthGeneration"))));

Which looks like this when rendered as XML:

<FirstGeneration>

<SecondGeneration>

<ThirdGeneration>

<FourthGeneration />

</ThirdGeneration>

</SecondGeneration>

</FirstGeneration>

Also in the last post I used Element to get a specific element from the current element. For example:

XElement child = root.Element("SecondGeneration");

Elements

If root (or FirstGeneration) only had one child element called “SecondGeneration” then everything is fine, you get what you asked for. However, if it contains multiple children all called “SecondGeneration” then you will only get the first element called “SecondGeneration”.

For example, if you add the following to the code above:

root.Add(new XElement("SecondGeneration","2"));
root.Add(new XElement("SecondGeneration","3"));

You will get a piece of XML that looks like this:

<FirstGeneration>

<SecondGeneration>

<ThirdGeneration>

<FourthGeneration />

</ThirdGeneration>

</SecondGeneration>

<SecondGeneration>2</SecondGeneration>

<SecondGeneration>3</SecondGeneration>

</FirstGeneration>

If you want to get all those additional children called “SecondGeneration” you will need to use the Elements (note the plural) method. For example:

IEnumerable<XElement> children = root.Elements("SecondGeneration");

You’ll also note that we don’t get a collection returned but an enumerable. This give us the opportunity to exploit many of the new extension methods. But I’ll leave them for another post. For the moment, we just need to know that it make it easy for us to enumerate over the data using a foreach loop. For example:

foreach (XElement child in children)
{
    Console.WriteLine(child);
    Console.WriteLine(new string('-', 50));
}

This will write out:

<SecondGeneration>

<ThirdGeneration>

<FourthGeneration />

</ThirdGeneration>

</SecondGeneration>

————————————————–

<SecondGeneration>2</SecondGeneration>

————————————————–

<SecondGeneration>3</SecondGeneration>

————————————————–

Parent

Using the same root object as above, we can see how to navigate back up the XML tree using Parent.

XElement grandchild = root.Element("SecondGeneration").Element("ThirdGeneration");
Console.WriteLine(grandchild.Parent);

The result of the code will be that the SecondGeneration element is printed.

 

Technorati Tags: ,,,

Fizz-Buzz in LINQ

This just occurred to me. It is somewhat pointless, but I thought it was interesting:

static void Main(string[] args)
{
    var result = from say in Enumerable.Range(1, 100)
                 select (say % 15 == 0) ? "BuzzFizz" :
                    (say % 5 == 0) ? "Buzz" :
                        (say % 3 == 0) ? "Fizz" : say.ToString();
    foreach (string say in result)
        Console.WriteLine(say);
}
Technorati Tags: ,,

Getting values out of XML in .NET 3.5 (LINQ to XML series part 2)

In my last post I gave a brief introduction to some of the new XML classes available in .NET 3.5. In this post I’ll continue that introduction by explaining how to get information out of the XML.

First off, lets assume we have some XML that looks like this:

XElement root = new XElement("root",
    new XAttribute("Attribute", "TheValue"),
    new XElement("FirstChild"),
    new XElement("SecondChild", new XElement("Grandchild", "The content of the grandchild")));

or, if you prefer in XML format, like this:

<root Attribute=”TheValue”>

<FirstChild />

<SecondChild>

<Grandchild>The content of the grandchild</Grandchild>

</SecondChild>

</root>

There are a number of ways to get the content of the grandchild element. For example:

Console.WriteLine(root.Element("SecondChild").Element("Grandchild").Value);

Value returns a string which contains the content of the element specified. In the above case it will output:

The content of the grandchild

However, you need to watch out for when there are child elements of the thing you want the value of as their content is included when you get the value. For example, if the above XML is extended so that it looks like this:

<root Attribute=”TheValue”>

<FirstChild />

<SecondChild>

<Grandchild>The content of the grandchild

<Great-grandchild>GGC content</Great-grandchild>

<Grandchild>

</SecondChild>

</root>

And the above line of C# is executed again, the result is now:

The content of the grandchildGGC content

As you can see the content of the element you want plus its child elements are now returned. This may not necessarily be what you want.

There is a second way to get the content from an element. That is to use a casting operator. You can cast the element to a number of types. In this case to a string. for example:

Console.WriteLine((string)root.Element("SecondChild").Element("Grandchild"));

The result is the same as calling Value on the element.

Be careful here, because casting an element to a string will not have the same result as calling ToString() on an element. You can see that if you simply pass the element itself to writeline (which will then call ToString() for you). For example:

Console.WriteLine(root.Element("SecondChild").Element("Grandchild"));

The result is:

<Grandchild>The content of the grandchild

<Great-grandchild>GGC content</Great-grandchild>

</Grandchild>

The process is similar if you are dealing with attribute. Using the above XML as an example, an attribute value can be retrieved like this:

Console.WriteLine(root.Attribute("Attribute").Value);

Or, using the cast operator to a string like this:

Console.WriteLine((string)root.Attribute("Attribute"));

Both of the above pieces of code output the same thing: TheValue

If you were to call WriteLine on an XAttribute object you’ll see that the ToString() method returns something slightly different. It returns this: Attribute=”TheValue”

Lets say, for instance, that the Attribute had a value of 123.456, which is a valid number representation. I mentioned earlier about casting operators on XElement and XAttribute. Well, you can cast this to a double if you prefer to get the value in that type. There is no tedious converting in your own code as the framework can handle that for you. For example:

(double)root.Attribute("Attribute")

That’s it for this post. There will be more on XML and LINQ soon.

Introduction to LINQ to XML

Last year I wrote about the new languages features available in C# 3.0 (Anonymous Types, Extension Methods, Automatic Properties, A start on LINQ, Object Initialisers I, Object Initialisers II, & Object Initialisers III) and since then I’ve really got in to LINQ, especially LINQ to XML. The reason for that is that I hate XPath and I see LINQ to XML as a much easier way of querying XML files without faffing about with terse XPath strings. I would much rather have the ability to easily see what is going on with the query than have to figure out why my XPath isn’t working for me.

However, LINQ to XML is more than just new funky querying mechanisms. There is a whole new set of classes to deal with XML that are much easier and more intuitive than the classes that were provided back with .NET 1.0, in my opinion.

The main two classes in the new way of doing XML are XElement and XAttribute. For example, to create a new element:

XElement root = new XElement("root");

And to add an attribute to that element:

root.Add(new XAttribute(“AttributeName”, “TheValue”));

Which produces the result: <root AttributeName=”TheValue” />

If you look at the intellisense for XElement constructor you’ll see that none of the 5 overloads takes a string. The nearest is an XName. This is because there is an implicit conversion happening between a string and an XName so that creating XElements does not have to be so arduous. It would be quite irritating to have to declare XElement objects like this:

XElement root = new XElement(XName.Get("root"));

At this point you’ll find that all the VB developers will be gloating because VB9 contains a feature called XML Literals whereby the developer can just write XML directly into the source code file and VB will parse and compile it correctly. An incredibly handy feature I’m sure you’ll agree. But, since I’m a C# developer that’s what I’ll stick with – especially considering that the majority of demos of LINQ to XML I’ve seen are VB based.

If you look closely at XName’s Get method you’ll see that there are two overrides, one for an expanded name, and the other for a local name and a namespace name. The expanded name is just a string of the name with the namespace embedded in the string inside curly braces, like this:

XName.Get("{mynamespace}root");

If you prefer you can use the other overloaded version and provide two strings. The equivalent XName in that case would be created like this:

XName.Get("root", "mynamespace");

Now, you are probably wondering why a static method is being used rather than a constructor. This is because the XML classes are clever enough to reuse existing XName objects. If you create a second XName object with the same characteristics as an existing XName object it will just reuse the existing XName. For example, the following code will output “True” to the console:

XName name1 = XName.Get("{ns}MyName");
XName name2 = XName.Get("MyName", "ns");
Console.WriteLine(object.ReferenceEquals(name1, name2));

XName is immutable (it cannot change) so this is a perfectly acceptable thing to do.

The extended name notation also works if you are using strings while constructing your XElement. For example:

XElement root = new XElement("{mynamespace}root");

However, there is another way of applying namespaces in an XElement. You can use an XNamespace object and add it to the string. Like this:

XNamespace ns = XNamespace.Get("mynamespace");
XElement root = new XElement(ns + "root");

As you can probably tell the + operator has been overloaded so it can be used to add a namespace to a string to produce an XName.

Technorati Tags: ,,,,,

Inserting geometry through a .NET Application

THIS POST REFERS TO THE NOVEMBER 2007 CTP (CTP 5) OF SQL SERVER 2008

Following from my previous posts (Getting started with Spatial Data in SQL Server 2008, Spatial Data in a .NET application) on the new spatial features of SQL Server 2008 I’ve been looking at how to get spatial data into SQL Server from a .NET application. This has not been as easy as expected.

I suspect I just have not found the right piece of documentation because my eventual solution isn’t one I’m entirely happy with.

I was unable to add a SqlGeometry as a parameter to the collection of parameters on the SqlCommand object in my .NET application. The SqlGeometry does not appear in the enumeration for SQL Server data types. My first thought was to put the data through as binary as I vaguely remember reading something about using binary in something I read recently, but I couldn’t quite remember where. However, when I created the geometry object in .NET then used .STAsBinary() on it so the parameter object would accept it the INSERT failed. The exception message was:

A .NET Framework error occurred during execution of user defined routine or aggregate 'geometry':
System.FormatException: One of the identified items was in an invalid format.
System.FormatException:
   at Microsoft.SqlServer.Types.GeometryData.Read(BinaryReader r)
   at Microsoft.SqlServer.Types.SqlGeometry.Read(BinaryReader r)
   at SqlGeometry::.DeserializeValidate(IntPtr , Int32 )
.
The statement has been terminated.

 

Yes, that was the MESSAGE from the exception. The stack trace above comes from within SQL Server itself. There is a separate stack track for my application. (I’m guessing if you are familiar with CLR stored procedures you may have seen this sort of thing before. I’ve not used the CLR code in SQL Server before, so it is a new experience)

The solution I found was to mark the parameter as an NVarChar, skip the creation of an SqlGeometry object and use a string containing the WKT (Well Known Text) representation. For example:

cmd.Parameters.Add("@Point", SqlDbType.NVarChar) = "POINT (312500 791500)";

I mentioned earlier that I’m not all that happy with this solution. That is because if I already have an SqlGeometry object I don’t want to have to convert it to a human readable format and have SQL Server parse it back again. Human Readable formats tend not to be the most efficient way of representing data and the geometry type already has a more efficient (as I understand it) format. Although in this case I bypassed the creation of an SqlGeometry object in my code, I can foresee cases where I might have created an SqlGeometry object through various operations and would have produced a fairly complex object. In those cases I wouldn’t want the additional overhead formatting it into WKT, passing it over the network and parsing it on the other side.

If I find a better solution I will post it.

Improving performance with parallel code

While waiting for my car to get serviced today I finally managed to catch up on reading some articles in MSDN Magazine. One of them, on optimising managed code for multi-core machines, really caught my attention.

The article was about a new technology from Microsoft called Parallel Extensions to .NET Framework 3.5 (download) which is currently released as a Community Technology Preview (CTP) at the moment. The blurb on the Microsoft site says “Parallel Extensions to the .NET Framework is a managed programming model for data parallelism, task parallelism, and coordination on parallel hardware unified by a common work scheduler.”

What I was most excited about was the ease with which it becomes possible to make an algorithm take advantage of multiple cores without all that tedious mucking about with threadpools. From what I gather the extensions are able to optimise the parallelism across the available cores. A looping construct can be set up across many cores and each thread is internally given a queue of work to do. If a thread finishes before others it can take up the slack by taking some work from another thread’s work queue.

Obviously, if an algorithm never lent itself well to parallelism in the first place these extensions won’t help much. Also the developer is still going to have to deal with concurrent access to shared resources so it is not a panacea. Those caveats aside these extensions to the .NET will make the job of using multi-core machines to their best much easier.

What is a DAL (Part 4)

As has been mentioned previously, one of the purposes of the DAL is to shield that application from the database. That said, what happens if a DAL throws an exception? How should the application respond to it? In fact, how can it respond to an exception that it should not know about?

If something goes wrong with a query in the database an exception is thrown. If the database is SQL Server then a SqlException is thrown. If it isn’t SQL Server then some other exception is thrown. Or the DAL may be performing actions against a completely different type of data source such as an XML file, plain text file, web service or something completely different. If the application knows nothing about the back end database (data source) then how does it know which exception to respond to?

In short, it doesn’t. It can’t know which of the myriad of possible exceptions that could be thrown will be and how to respond to it. The calling code could just catch(Exception ex) but that is poor practice. It is always best to catch the most specific exception possible.

The answer is to create a specific exception that the DAL can use. A DalException that calling code can use. The original exception is still available as an InnerException on the DalException.

using System;
using System.Runtime.Serialization;

namespace Dal
{
    public class DalException : Exception
    {
        public DalException()
            : base()
        {
        }

        public DalException(string message)
            : base(message)
        {
        }

        public DalException(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        public DalException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }
}

The DAL will catch the original exception, create a new one based on the original and throw the new exception.

public DataSet GetPolicy(int policyId)
{
    try
    {
        SqlDataAdapter da =
            (SqlDataAdapter)this.BuildBasicQuery("GetPolicy");
        da.SelectCommand.Parameters.AddWithValue("@id", policyId);
        DataSet result = new DataSet();
        da.Fill(result);
        return result;
    }
    catch (SqlException sqlEx)
    {
        DalException dalEx = BuildDalEx(sqlEx);
        throw dalEx;
    }
}

The code for wrapping the original exception in the DAL Exception can be refactored in to a separate method so it can be used repeatedly. Depending on what it needs to do it may be possible to put that as a protected method on one of the abstract base classes

private DalException BuildDalEx(SqlException sqlEx)
{
    string message = string.Format("An exception occured in the Policy DALrn" +
        "Message: {0}", sqlEx.Message);
    DalException result = new DalException(message, sqlEx);
    return result;
}

Previous articles in the series:

 

 

Technorati Tags: , ,

 

What is a DAL (Part 3)

In this continuation of my series on the DAL I’m going to show the ability to create several DALs and have a Factory class instantiate the correct DAL based on settings in a config file.

One of the purposes of the DAL is to shield the application from the detail of accessing the database. You may have a system where you may have to talk to different databases depending on the way the software is installed. Typically this is most useful by creators of components that need to plug into many different database systems.

The DAL will no longer be a singleton, but rely on a Factory to create the correct DAL class. This is most useful when the data may be coming from different sources. It can also be useful when your application is in development and the database isn’t ready because you can then create a mock DAL and return that instead of the real thing.

UML Diagram showing multiple DALs

UML Diagram showing multiple DALs

The above UML Diagram shows the base DAL that was introduced in the last article in this series. Again, it is an abstract class as it does not contain enough functionality to be instantiated on its own. However, it is now joined by two additional abstract classes. These add a bit more functionality specific to the target database, but still not enough to be useful as an object in its own right.

Finally at the bottom layer are the various concrete DAL classes that act as the proxies to the database.

So that the calling application doesn’t need to know what database it is dealing with the concrete classes implement interfaces. That way the application receives a reference to an IPolicyDal, for example, without having to know whether it is connected to an Oracle database or a SQL Server database (or any other future database you may need to support)

The factory class, not shown in the UML diagram above, picks up information in a config file which tells it which specialised DAL to actually create. It returns that specialised instance through a reference to the interface. This means that the calling code does not need to know what specialised class has actually been created. Nor should it need to care.

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace Dal
{
    /// <summary>
    /// Creates and dispenses the correct DAL depending on the configuration
    /// </summary>
    public class DalFactory
    {
        private static IPolicyDal thePolicyDal = null;
        private static IClaimDal theClaimDal = null;

        /// <summary>
        /// Gets the policy DAL
        /// </summary>
        /// <returns>A dal that implements IPolicyDal</returns>
        public static IPolicyDal GetPolicyDal()
        {
            if (thePolicyDal == null)
            {
                string policyDalName = DalConfig.PolicyDal;
                Type policyDalType = Type.GetType(policyDalName);
                thePolicyDal =
                    (IPolicyDal)Activator.CreateInstance(policyDalType);
            }
            return thePolicyDal;
        }

        /// <summary>
        /// Gets the claim DAL
        /// </summary>
        /// <returns>A DAL that implements IClaimDal</returns>
        public static IClaimDal GetClaimDal()
        {
            if (theClaimDal == null)
            {
                string claimDalName = DalConfig.ClaimDal;
                Type claimDalType = Type.GetType(claimDalName);
                theClaimDal =
                    (IClaimDal)Activator.CreateInstance(claimDalType);
            }
            return theClaimDal;
        }
    }
}

The factory stores the each DAL class as it is created so that if a Get...() method is called repeatedly it always returns the same instance. The DAL classes should not be storing any state so this is a safe thing to do.

The DalConfig class is a very simple static class that consists of a number of property getters that retrieves the information out of the config file. The PolicyDal and ClaimDal properties return the fully qualified name of the specialised DAL class to instantiate.

Previous articles in the series:

Technorati Tags: ,

What is a DAL?

I occasionally see posts on forums asking for help with some database
problem. Then I see they’ve been using the the wizards in Visual Studio (which
is okay for a beginner, but should really be ditched once the basic concepts
have been learned). I suggest that they use a DAL and I get lots of positive
signals back that they’ll do that in future. Then a week or two later they’ve
got themselves into another problem and they’re still using wizard generated
code.

So, it occurred to me that just saying “Use a DAL” doesn’t mean anything. If
someone told me that I need to use the finkle-widget pattern I wouldn’t know
what they are talking about. And if a person has never heard the term DAL
before, or never had it explained to them, then it is just as useful as saying
“use the finkle-widget pattern”

So, I figured it would be a good idea to show what a very basic DAL looks
like and to explain what it is.

A DAL is a Data Abstraction Layer. It is the part of the application that is
responsible for communicating with a data source. That data source is typically
a database, but it can be anything you like, such as an XML file, a plain text
file or anything else that data can be read from or written to.

In this example it will be a static class that communicates with a SQL
Server. It picks up the connection string
from the config file. It will communicate with stored procedures and return
DataTables, DataSets or single values. It doesn’t do everything a DAL could do,
but it shows the basic functionality.

Whenever I create DAL classes I try and think about them as if they are a
proxy for a group of actual stored procedures. I give the methods the same name
as the stored procedures, I give each method the same parameter names as the
stored procedure.

A DAL class has to mange the connection, so in this example I have a static
initialiser that gets the connection string from the config file and stores
that.

        private static string connectionString;
        static Dal()         {             connectionString =
                ConfigurationManager.AppSettings["ConnectionString"];         }

On each query a new connection object is created. The reason for this is that
the recommendation is that you acquire-query-release. Acquire a connection,
perform your query, then release the connection back to the pool.

            cmd.Connection.Open();
            int result = (int)cmd.ExecuteScalar();
            cmd.Connection.Close();

The connection Open / Close calls are not necessary when using a DataAdapter
as it will open the connection and then close it again in the Fill method. If
the connection was open before Fill is called then it will stay open.

There are two helper methods that are not publicly available. They are
BuildCommand and BuildBasicQuery.

BuildCommand creates the connection and command object and attaches the
connection to the command. It also tells the command the name of the stored
procedure that is to be called.

BuildBasicQuery uses BuildCommand, but then attaches the command to a
DataAdapter so that it can be used to obtain query results.

The full code of the sample DAL is below:

using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data;

namespace Cam.DataAbstractionLayer
{
    public static class Dal
    {
        private static string connectionString;

        /// <summary>
        /// The static initialiser will be called the first time any
        /// method on the class is called. This ensures that the
        /// connection string is available.
        /// </summary>
        static Dal()
        {
            connectionString =
                ConfigurationManager.AppSettings["ConnectionString"];
        }


        /// <summary>
        /// Builds the command object with an appropriate connection and
        /// sets the stored procedure name.
        /// </summary>
        /// <param name="storedProcedureName">The name of the stored
        /// procedure</param>
        /// <returns>The command object</returns>
        private static SqlCommand BuildCommand(string storedProcedureName)
        {
            // Create a connection to the database.
            SqlConnection connection = new SqlConnection(connectionString);

            // Create the command object - The named stored procedure
            // will be called when needed.
            SqlCommand result = new SqlCommand(storedProcedureName,
                connection);
            result.CommandType = CommandType.StoredProcedure;
            return result;
        }

        /// <summary>
        /// Builds a DataAdapter that can be used for retrieving the
        /// results of queries
        /// </summary>
        /// <param name="storedProcedureName">The name of the stored
        /// procedure</param>
        /// <returns>A data adapter</returns>
        private static SqlDataAdapter BuildBasicQuery(
            string storedProcedureName)
        {
            SqlCommand cmd = BuildCommand(storedProcedureName);

            // Set up the data adapter to use the command already setup.
            SqlDataAdapter result = new SqlDataAdapter(cmd);
            return result;
        }

        /// <summary>
        /// A sample public method. There are no parameters, it simply
        /// calls a stored procedure that retrieves all the products
        /// </summary>
        /// <returns>A DataTable containing the product data</returns>
        public static DataTable GetAllProducts()
        {
            SqlDataAdapter dataAdapter = BuildBasicQuery("GetAllProducts");

            // Get the result set from the database and return it
            DataTable result = new DataTable();
            dataAdapter.Fill(result);
            return result;
        }

        /// <summary>
        /// A sample public method. It takes one parameter which is
        /// passed to the database.
        /// </summary>
        /// <param name="invoiceNumber">A number which identifies the
        /// invoice</param>
        /// <returns>A dataset containing the details of the required
        /// invoice</returns>
        public static DataSet GetInvoice(int invoiceNumber)
        {
            SqlDataAdapter dataAdapter = BuildBasicQuery("GetInvoice");
            dataAdapter.SelectCommand.Parameters.AddWithValue(
                "@invoiceNumber", invoiceNumber);

            DataSet result = new DataSet();
            dataAdapter.Fill(result);
            return result;
        }

        /// <summary>
        /// A sample public method. Creates an invoice in the database
        /// and returns the invoice number to the calling code.
        /// </summary>
        /// <param name="customerId">The id of the customer</param>
        /// <param name="billingAddressId">The id of the billing
        /// address</param>
        /// <param name="date">The date of the invoice</param>
        /// <returns>The invoice number</returns>
        public static int CreateInvoice(int customerId,
            int billingAddressId, DateTime date)
        {
            SqlCommand cmd = BuildCommand("CreateInvoice");
            cmd.Parameters.AddWithValue("@customerId", customerId);
            cmd.Parameters.AddWithValue("@billingAddressId",
                billingAddressId);
            cmd.Parameters.AddWithValue("@date", date);

            cmd.Connection.Open();
            int result = (int)cmd.ExecuteScalar();
            cmd.Connection.Close();
            return result;
        }
    }
}

So, that is that. A very basic introduction to creating a DAL.

Tags: