Building Layered Web Applications with Microsoft ASP.NET 2.0 - Part 2


NOTE: the concepts presented in this article are now considered obsolete possibly because better alternatives are available.

Update!! - I have written a new series on N-Layer design targeting ASP.NET 4.5 and Entity Framework 5. You can check out the new series here.

Update!! 12-24-2008 - I have written a new series on N-Layer design as a major follow up to this one. It builds on the foundation created in this first series, but digs much deeper into concepts like Validation, Sorting, Paging, Concurrency and Security. You can check out the new series here.

Update!! 04-25-2007 - There is now also a VB.NET version of the code available for download. You find the download at the end of this article. For more information about the translation, check out this blog post.

Update!! 04-24-2007 - Fixed a bug in the stored procedure that deletes a Contactperson, based on feedback from reader csharpdev. This also affects the downloadable code for this article, which now also has the update applied.

This is part two of the article series "Building Layered Web Applications" that shows you how to build N-Layer applications with Microsoft ASP.NET 2.0. These articles teach you how to design, build and use custom business objects in your web application. The target audience for this series are developers that are ready to make the switch from using SqlDataSource controls to ObjectDataSource controls with custom business objects. Experience with ASP.NET 2 and C# is necessary while some knowledge about object oriented design certainly helps. The design I am going to show you in these articles is a simplified version of a design you would use in a real world application. It doesn't feature all the necessary functionality your application needs, but instead focuses on the underlying concepts.

Part one dealt with the design of the application: what business objects do you need to fulfill the requirements of the application. What should these objects be capable of and how do they look. This article (part two) continues where the previous article stopped: it shows you how to code the classes that were designed in part one. You'll see how to implement the data access methods and database code and you'll learn how you can connect these classes. Part three then deals with using the business objects in a web application. You'll see how to make use of the out-of-the-box ASP.NET 2.0 controls together with the business objects coded in this part.

If you haven't read part one yet, you should really read it first, as this article uses many concepts that have been explained earlier. Afterwards, check out part three where you'll see the concepts from this article in an actual web site. The entire series (including this current article) can be found here:

The article uses a SQL Server 2005 Express database which is easy to use in development scenario's. However, the downloads for this series also come with the T-SQL scripts to recreate the database in SQL Server 2000 or SQL Server 2005. You'll find the download link at the end of this article. Besides the aforementioned SQL scripts and database, the download also contains the full source for the demo application in C#

Introduction

In part 1 you saw the design of the classes that make up the Contact Manager application. You saw the classes in the Business Objects layer, the Business Logic Layer and you saw a number of classes in the DAL (Data Access Layer).

In this installment, I'll show you the code for these classes. You'll see how to implement the public properties for each of the classes in the Business Objects layer, how to write the code for methods like GetItem and GetList, and you'll see how to connect to the database in a safe, transactional manner for inserts and updates. But, before we dig into the code, let's take a brief look at the class design again. With the class design in mind, it's easier to make decisions for the layout of your project. Although you could simply drop all your classes in the App_Code folder or in a separate class library project all under the same namespace, it's often easier to put them in separate folders and namespaces so the code is easier to find. The following figure shows the organization of the classes in the application:

The Full Class Diagram for the ContactManager Application
Figure 1 - The Full Class Diagram for the Contact Person Manager Application

At the top of the image you see the business objects in the BO namespace. These classes are placed in the Spaanjaars.ContactManager.BO namespace, following a best practice in namespace names where each namespace is named in the format CompanyName.ProjectName.Layer. I also put the enumerations in that layer, as they are used directly as types for certain properties of the classes in the same layer.

All the *Manager classes are put in the Spaanjaars.ContactManager.Bll namespace and similarly, I put the *DB classes in the Spaanjaars.ContactManager.Dal namespace.

To continue this separation, I also used different folders inside the App_Code folder of the application. In a larger application you may want to use three separate class libraries instead. The Business Layer class library would then have a reference to the Business Objects and Data Access class libraries, the DAL would also have a reference to the Business objects layer, while the final web site would reference the BO and BLL layers. In the sample application for this article, I decided to keep things simple and store everything in separate folders under App_Code, but the exact same principles would apply if you'd be using class library projects.

So, after I created a brand new web site in Visual Studio 2005 (I am using the Professional Edition, but you can follow along if you have the Express Edition), I added four folders under the special ASP.NET App_Code folder, and put a number of empty class files in them, each one named after the class or enum from the diagram in Figure 1. I ended up with the following Solution Explorer:

The Solution Explorer for the ContactManager Project
Figure 2 - The Solution Explorer for the Contact Person Manager Application

Each class in the BusinessLogic folder is put in the Bll namespace, files in the BusinessObject folder fall under the BO namespace while the classes in the DataAccess folder are put in the Dal namespace. For example, the Address class in the BusinessObject folder looks like this:

namespace Spaanjaars.ContactManager.BO
{
public class Address
{
// Implementation here

}
}

Although the enumerations actually fall under the BO namespace, I added them in a separate folder so they are easier to find.

Now that you've seen the basic structure of the site, let's take a look at the actual code for the classes.

Code Organization in the Contact Person Manager Application

Before I show you the specific implementation of the classes, let's first take a look at the basic structure of the class files. To make it easier to locate and maintain code, I often wrap them in #region / #endregion constructs. I typically use at least four different regions although I don't use all of them in every file. Figure 3 shows the collapsed code for a typical business object:

The Collapsed Code for the Address Class Showing the Four CodeRegions
Figure 3 - The Collapsed Code for the Address Class Showing Two Code Regions

The Private Variables region contains all the backing variables for the public properties and optionally other private, class scoped variables for the business objects. They're often assigned a default value here as well. For example:

private string street = String.Empty;		  

The Public Properties region contains all the properties that the class exposes, like Id, Street and HouseNumber in the case of the Address class. Most of these properties use the private fields from the Private Variables region.

Figure 4 shows the collapsed code for a typical class in the data access layer:

The Collapsed Code for the AddressDB Class Showing Two CodeRegions
Figure 4 - The Collapsed Code for the AddressDB Class Showing Two Other Code Regions

Many classes in the sample application contain a region called Public Methods. This region contains the class's public methods that are used to interact with the object and the underlying database. You'll find methods like GetItem and GetList to retrieve the objects, and Save and Delete to propagate object changes back to the database. In addition, the class also contains a Private Methods region that contains methods that are only accessible from within the class that defines them.

Coding the Classes for the Contact Person Manager Application

With the code organization behind our back, it's time to look at the actual implementation of the classes. Since the Address, EmailAddress and PhoneNumber classes are very similar, I'll only look at one of them: the EmailAddress class. Of course, I'll also show you the EmailAddressManager and EmailAddressDB classes as we progress through the code.

After you have seen the EmailAddress class, I'll take a detailed look at the ContactPerson class. Since this class is a bit more complex than the other three, it helps to understand the other three classes before you look at ContactPerson.

The EmailAddress Class - Implementation

The first region in the class, called Private Variables contains the following code:

private int id = -1;
private string email = String.Empty;
private ContactType type = ContactType.NotSet;
private int contactPersonId = -1;

The private field id is initially set to -1. This value is used later on to determine whether an object is entirely new (id is still -1), or that it already has an associated record in the database (id is greater than 0). Normally, this is considered a magic number, where a fixed number has a special meaning that cannot be derived easily by its value. Magic numbers are usually considered a bad programming practice. You can avoid magic numbers by using Nullable Types (new in .NET 2) where an int could be null as well to indicate it doesn't have a value. In this case, I can live with the magic number. Legal values for the id and contactPersonId fields come from identity columns in the database. These values are always greater than zero, so it's clear from the code that -1 means something special. If you don't like this, then check out Nullable Types on the MSDN site.

The email field (which contains the actual e-mail address) defaults to String.Empty while the type defaults to ContactType.NotSet, a custom value of the ContactType enumeration to indicate it hasn't been set to another value before. Think of this as a null value for the ContactType type. Finally, the contactPersonId is set to -1 as well.

In the Public Properties region of the code, you find one property for each of these fields. All of them look similar to this:

public string Email
{
get
{
return email;
}
set
{
email = value;
}
}

You can see that each of the private fields is used as a backing variable for the public property.

Earlier I said that the classes in the BO namespace, like EmailAddress, are "dumb" classes. They are not capable of performing any action; all they do is contain data. To work with instances of EmailAddress, you need to use the methods in the EmailAddressManager class.

Working with Instances of EmailAddress

The EmailAddressManager contains a number of methods that allow you to perform CRUD operations on an EmailAddress: Create, Read, Update and Delete. Let's take a look at Read first.

First of all, the EmailAddressManager class has a method called GetItem. This method accepts the ID of the EmailAddress in the database. The implementation of this method is really simple: all it does is forward the call to the GetItem method in the data access layer:

public static EmailAddress GetItem(int id)
{
return EmailAddressDB.GetItem(id);
}

Although this seems like a lot of work for nothing (why not have the calling code access the DAL method directly?), it serves a distinct purpose. Although not implemented in GetItem for the EmailAddressManager class, the code in the business layer is an ideal place for things like security. Instead of directly calling EmailAddressDB.GetItem, you could perform a security check first to determine whether the current user has sufficient access right to perform the call. You could, for example, modify GetItem so it looks like this:

public static EmailAddress GetItem(int id, IPrincipal currentUser)
{
if (!currentUser.IsInRole("EmailAddressManagers"))
{
throw new NotSupportedException(@"You're not allowed to call
EmailAddressManager.GetItem when you're not in the
EmailAddressManagers role");
}
return EmailAddressDB.GetItem(id);
}

You would then call this method from an ASPX page like this:

EmailAddress myEmailAddress = EmailAddressManager.GetItem(10, Context.User);

And, instead or in addition of this, you could use this method to cache the object returned by GetItem for some time, so you don't have to hit the database every time you need to work with the object. Another valid activity for methods in the business layer is validation. You'll see how this works when the Save method is discussed.

Since these kind of requirements - security, validation, caching and so on - are often application specific, it's important to implement them in the business layer. If you implement them in the data access layer, you can't reuse that layer in an application that has different requirements or demands.

The GetItem method is static which means you can call it without an object instance. With this method, calling code can get an instance of EmailAddress with a single line of code:

EmailAddress myEmailAddress = EmailAddressManager.GetItem(id);		  

where id is the ID of a valid EmailAddress in the database.

As you saw, GetItem in the business layer simply forwards the call to GetItem in the EmailAddressDB class. That method looks like this:

public static EmailAddress GetItem(int id)
{
EmailAddress myEmailAddress = null;
using (SqlConnection myConnection =
new SqlConnection(AppConfiguration.ConnectionString))
{
SqlCommand myCommand = new SqlCommand(
"sprocEmailAddressSelectSingleItem", myConnection);
myCommand.CommandType = CommandType.StoredProcedure;
myCommand.Parameters.AddWithValue("@id", id);

myConnection.Open();
using (SqlDataReader myReader = myCommand.ExecuteReader())
{
if (myReader.Read())
{
myEmailAddress = FillDataRecord(myReader);
}
myReader.Close();
}
myConnection.Close();
}
return myEmailAddress;
}

First, this method creates a new SqlConnection by passing in the connection string (retrieved from the web.config file through the static property ConnectionString on the AppConfiguration class). It then creates and sets up a new SqlCommand. The command is configured to call a stored procedure called sprocEmailAddressSelectSingleItem which returns a single EmailAddress by its ID. This ID is passed to the stored procedure by adding a parameter object to the command using AddWithValue. The stored procedure itself is as straight forward as it can be:

CREATE PROCEDURE sprocEmailAddressSelectSingleItem

@id int

AS

SELECT
Id,
Email,
EmailType,
ContactPersonId
FROM
EmailAddress
WHERE
Id = @id

This procedure simple retrieves all the fields from the EmailAddress table for the specified ID.

The GetItem method in the EmailAddressDB class finally opens a connection to the database and then calls ExecuteReader to get an open SqlDataReader. Each row in the SqlDataReader implements the generic IDataRecord interface that gives you strongly typed access to the values within each row. The private helper method FillDataRecord is then used to create and fill an instance of the EmailAddress class based on the data in the IDataRecord instance:

private static EmailAddress FillDataRecord(IDataRecord myDataRecord)
{
EmailAddress myEmailAddress = new EmailAddress();
myEmailAddress.Id =
myDataRecord.GetInt32(myDataRecord.GetOrdinal("Id"));
myEmailAddress.Email =
myDataRecord.GetString(myDataRecord.GetOrdinal("Email"));
myEmailAddress.Type = (ContactType)
myDataRecord.GetInt32(myDataRecord.GetOrdinal("EmailType"));
myEmailAddress.ContactPersonId =
myDataRecord.GetInt32(myDataRecord.GetOrdinal("ContactPersonId"));
return myEmailAddress;
}

This method first creates a new instance of the EmailAddress class and then copies each of its fields from the IDataRecord in the EmailAddress. At the end of the method, the EmailAddress object is returned. This helper method is used to create a single instance of each class in GetItem, but is also used to get lists of objects in the GetList methods. To make it easier to use this code as the basis for an implementation of a different database, the method accepts an IDataRecord, an interface that all DataReaders, like the SqlDataReader and the OleDbDataReader implement.

The code in this method simply copies the data from the reader into the private fields of the classes by calling the appropriate Get* methods. These Get* methods expect a zero based index of the column in the IDataRecord. However, using "magic numbers" in your code makes it hard to maintain and read, so I used GetOrdinal to "translate" the name of a column in its index. Why the Get* methods don't have an overload that accepts a string with the column name as well is beyond me though. This may be a nice addition for the .NET 3.5 framework.

Since all the properties of the EmailAddress are required columns in the database, there's no need to check for null values in the helper method. In cases where you do expect null values, you need to take extra precautions, as is the case with the Address class. This is code from that class's FillDataRecord method:

if (!myDataRecord.IsDBNull(myDataRecord.GetOrdinal("ZipCode")))
{
myAddress.ZipCode =
myDataRecord.GetString(myDataRecord.GetOrdinal("ZipCode"));
}

Since ZipCode is not a required column in the Address table, I have to check for null values using IsDBNull. Only when the column isn't null, I assign the ZipCode property the value from the reader. Otherwise, I leave it to its default value of String.Empty.

At the end of GetItem in the data access layer, the EmailAddress object is returned to GetItem in the business logic layer which in turn returns it to the client.

As you can see, the code in the EmailAddressDB class is tied to a specific database provider: SQL Server. However, with minimal changes, you can make it work with Access or Oracle. Alternatively, you can write almost completely provider agnostic code using the new database provider factory model in .NET 2. Check out my book ASP.NET 2.0 Instant Results for an example of this in the chapter The Wrox Blog.

But, however you change the code in the DAL, you don't need to change the business objects or business logic layers; as long as the DAL returns an object from the BO namespace, which is shared between the Bll and the Dal namespaces, everything will work, regardless of the database you're using.

The GetList method in the EmailAddress class in the Business Layer follows a similar approach. However, since we're potentially working with more than one e-mail address, we need some way to work with a collection of EmailAddress records. In .NET 2, Microsoft introduced a concept called Generics that allow you to write classes that can work with different types of other classes. In the case of the ContactPerson, I make use of the generic List<T> class. The List<T> works very similar to an ArrayList in that it allows you to easily add and remove objects to a list. However, one of the shortcomings of the ArrayList is the fact you can store *any* object in it. So, it's possible to accidentally store both an EmailAddress and an Address in a list. This obviously can lead to problems when you want to use the list to bind it to a GridView for example. The GridView is set up to work with a single object type and will be confused (and raise an exception) when you pass it different object types. Also, because the GridView isn't able to determine the actual type of the object, it also can't generate strongly typed columns for you, impacting the design time experience and your productivity.

The List<T> on the other hand is designed to work with objects of type T. You determine what T represents when you declare a variable of type List<T> as in the following example that creates a list that can only hold EmailAddress instances:

List<EmailAddress> tempList = new List<EmailAddress>();		  

With this declaration, you'll get a compile time error whenever you try to add an object to the list that is not of type EmailAddress.

In my initial design for this application, I used List<T> directly. So, my ContactPerson class had properties of type List<Address>, List<EmailAddress> and so on. Additionally, the GetList methods would work with and return types of List<T> as well. However, after a discussion with someone I work with, I decided to create four separate *List classes, each inheriting from its generics counterpart. For example, the EmailAddressList class looks like this:

public class EmailAddressList : List<EmailAddress>
{
public EmailAddressList()
{ }
}

Although at this stage I am not really adding anything to List<T> in my custom list, this additional layer serves two purposes. First of all, the API has now become a lot simpler for users unfamiliar with generics as the generics have been taken out of the API completely. So, instead of declaring a list that can hold e-mail address records with the following generics syntax:

private List<EmailAddress> emailAddresses = new List<EmailAddress>();

you can now declare an address list with this simplified code:

private EmailAddressList emailAddresses = new EmailAddressList();

This code is a lot easier to read and understand by other developers.

Another benefit of the separate class is that it is easier to add additional methods or even change the underlying type of EmailAddressList completely, to something like CollectionBase for example without breaking existing code.

Once you understand a little how List<T> and the classes that inherit from it work, the rest of the code in GetList is straight forward:

public static EmailAddressList GetList(int contactPersonId)
{
EmailAddressList tempList = null;
using (SqlConnection myConnection =
new SqlConnection(AppConfiguration.ConnectionString))
{
// Open connection here. Not shown.
using (SqlDataReader myReader = myCommand.ExecuteReader())
{
if (myReader.HasRows)
{
tempList = new EmailAddressList();
while (myReader.Read())
{
tempList.Add(FillDataRecord(myReader));
}
}
myReader.Close();
}
}
return tempList;
}

Once the ExecuteReader method returns an open SqlDataReader, the code starts looping, creating a new instance of EmailAddress with FillDataRecord and adding it to the list on each iteration. Creating the EmailAddress object is identical to the code in GetItem. The initial time it took to the create the private helper method has now been compensated for as we can easily reuse it in different situations where we need to create an EmailAddress from any object that implements the IDataRecord interface.

Inserting, Updating and Deleting Data

While it's nice that you can read a single item or a list of items from the database, the EmailAddressManager class really needs some methods to send data to the database. In particular: it needs an Insert, an Update and a Delete method to complete the CRUD acronym I talked about earlier.

In the case of the Contact Person Manager application, both Insert and Update are handled by the same upsert method: Save(). This means that the code in the business logic and in the data access layer makes no distinction between these two operations.

The Save method in the business layer is straight forward; just as you saw with GetItem and GetList, it simply forwards the call to the DAL. It stores the return value of the Save method (which is the ID of the EmailAddress in the database) in the Id property:

public static int Save(EmailAddress myEmailAddress)
{
myEmailAddress.Id = EmailAddressDB.Save(myEmailAddress);
return myEmailAddress.Id;
}

Remember, this would be the place to do security checks. For example, you could check if the current user was a ContentManager before you'd allow the call to Save(). After I show you how Save() is implemented in the DAL, I'll show you another variation of Save in the business layer that validates the EmailAddress before it sends it to the DAL.

The current version in the demo application calls the Save method on the associated database class. Let's take a look at the code to see how this all works. This is the Save() method in the EmailAddressDB class in the data access layer:

public static int Save(EmailAddress myEmailAddress)
{
int result = 0;
using (SqlConnection myConnection =
new SqlConnection(AppConfiguration.ConnectionString))
{
SqlCommand myCommand = new SqlCommand(
"sprocEmailAddressInsertUpdateSingleItem", myConnection);
myCommand.CommandType = CommandType.StoredProcedure;

if (myEmailAddress.Id == -1)
{
myCommand.Parameters.AddWithValue("@id", DBNull.Value);
}
else
{
myCommand.Parameters.AddWithValue("@id", myEmailAddress.Id);
}

myCommand.Parameters.AddWithValue("@email", myEmailAddress.Email);
myCommand.Parameters.AddWithValue("@emailType", myEmailAddress.Type);
myCommand.Parameters.AddWithValue(
"@contactPersonId", myEmailAddress.ContactPersonId);

DbParameter returnValue;
returnValue = myCommand.CreateParameter();
returnValue.Direction = ParameterDirection.ReturnValue;
myCommand.Parameters.Add(returnValue);

myConnection.Open();
myCommand.ExecuteNonQuery();
result = Convert.ToInt32(returnValue.Value);
myConnection.Close();
}
return result;
}

Similar to what you saw before, this code sets up a connection and a command to call the stored procedure sprocEmailAddressInsertUpdateSingleItem.

Then, based on whether the ID of the EmailAddress is -1 (a new item) or greater than -1 (an existing item), it creates a parameter called @id and passes it the relevant value: DBNull.Value (which translates to null in database land) for the new item, or the actual ID for an existing item. This is the "magic number" I talked about earlier in the article. I'll show you in a bit how the stored procedure deals with these values.

The code then continues to setup a few other parameters for the other fields of the EmailAddress class: email, emailAddressType and contactPersonId.

It also sets up a ReturnValue parameter to catch the return value from the stored procedure. This procedure returns the new ID of an item when it's inserted, or the existing ID in case of an update.

At the end, ExecuteNonQuery is called to send the data to the database, the return value is retrieved from the parameter which is then returned to the calling code. It's useful to have the Save method return the ID of the item so you can use it in your presentation layer somehow, for example, to redirect the user to a details page shopwing the new e-mail address.

The final thing you need to look at is the stored procedure that performs either the INSERT or the UPDATE:

CREATE PROCEDURE sprocEmailAddressInsertUpdateSingleItem

@id int,
@email nvarchar (100),
@emailType int,
@contactPersonId int

AS

DECLARE @ReturnValue int

IF (@id IS NULL) -- New Item
BEGIN

INSERT INTO EmailAddress
(
Email,
EmailType,
ContactPersonId
)

VALUES
(
@email,
@emailType,
@contactPersonId
)

SELECT @ReturnValue = SCOPE_IDENTITY()

END
ELSE
BEGIN

UPDATE EmailAddress SET
Email = @email,
EmailType = @emailType,
ContactPersonId = @contactPersonId
WHERE Id = @id

SELECT @ReturnValue = @id

END
IF (@@ERROR != 0)
BEGIN
RETURN -1
END
ELSE
BEGIN
RETURN @ReturnValue
END

GO

When the @id parameter contains null, the code performs an INSERT in the EmailAddress table. It then uses SCOPE_IDENTITY() to get the new ID of the record that was just inserted.

If @id does contain a value, the procedure performs an UPDATE for the requested record and then assigns the ID of the record to the @returnValue variable which is returned at the end to the calling code.

In both case, the ID of the record is returned to the calling code. There it is caught by the return value parameter, converted to an int and returned to the Save method in the business logic layer.

Earlier I said that the Save method in the business logic layer is also a good place to validate the objects you're saving. To do this, you can implement a method like this:

private static void Validate(EmailAddress myEmailAddress)
{
if (String.IsNullOrEmpty(myEmailAddress.Email))
{
throw new Exception(@"Cannot save an EmailAddress
without a valid Email property.");
}
if (myEmailAddress.Type == ContactType.NotSet)
{
throw new Exception(@"Cannot save an EmailAddress
without a valid Type property.");
}
if (myEmailAddress.ContactPersonId == -1)
{
throw new Exception(@"Cannot save an EmailAddress
without a valid ContactPersonId property.");
}
}

This method accepts an instance of EmailAddress, validates its properties and then throws an exception when the EmailAddress doesn't appear to be valid. In this example, the EmailAddress is considered invalid when the Email property has not been set, when the Type property is still ContactType.NotSet or when the ContactPersonId contains a value of -1.

You call the Validate method right before you send the object to the data access layer:

public static int Save(EmailAddress myEmailAddress)
{
  Validate(myEmailAddress);      
  myEmailAddress.Id = EmailAddressDB.Save(myEmailAddress);
return myEmailAddress.Id;
}

Since the Save method can now throw exception when the object is not in a valid state, it's a good idea to implement a public method Validate as well that performs the same validation but doesn't throw the exception. You could add an overload for the Validate method and have it accept a bool that indicates whether or not you want to throw an exception when the object is in an invalid state. With the public Validate method, users of your class can check if the object is valid before they attempt to call Save.

The Delete mechanism in the EmailAddressManager class follows a very similar approach, so I won't show you the code for this. Instead, you're advised to download the entire application at the end of this article and see for yourself.

This concludes the fields, properties, constructors and methods of the three contact record classes. While I only showed you the implementation for the EmailAddress class, it should be easy to understand Address and PhoneNumber as well, as both of these classes follow the exact same pattern.

In the next section, I'll show you the ContactPerson class.

The ContactPerson Class - Implementation

The ContactPerson class is discussed separately, because it has a few twists that are worth looking at.

In the Private Variables and Public Properties regions you see code that sets up three lists: one for the EmailAddresses, one for the Addresses and for the PhoneNumbers. Here's the code for the three private backing variables:

private AddressList addresses = new AddressList();
private PhoneNumberList phoneNumbers = new PhoneNumberList();
private EmailAddressList emailAddresses = new EmailAddressList();

These lists allow you to access the respective contact data collections for a contact person. Earlier you saw how each of these lists inherits from its generics List<T> counterpart.

There's one more public property that's worth looking at: the FullName:

public string FullName
{
get
{
string tempValue = firstName;
if (!String.IsNullOrEmpty(middleName))
{
tempValue += " " + middleName;
}
tempValue += " " + lastName;

return tempValue;
}
}

You'll see there's no backing variable for the FullName property because the getter makes use of existing variables: firstName, middleName and lastName to build up the complete name of a ContactPerson. With this property, showing a contact person's full name is now as easy as accessing this property. Client code no longer needs to bother with the individual fields, and whether the middleName actually contains a value or not.

The GetList method of ContactPersonManager is almost identical to the ones you saw before, so I won't go over it anymore. The GetItem method, however, is quite different. In addition to getting its own data, the GetItem method is also responsible for getting the associated contact records, like Addresses and Phonenumbers. However, since there are circumstances where you may not need to associated contact records, the GetItem method has a second overload that allows you to control this. Either call GetItem with only the ID of the contact person, or call GetItem and pass false for the second parameter: getContactRecords. If you call GetItem with the ID and true as the value for the getContactRecord parameter, you get a fully populated ContactPerson object, with all of its contact data collections set up as well.

public static ContactPerson GetItem(int id)
{
return GetItem(id, false);
}

public static ContactPerson GetItem(int id, bool getContactRecords)
{
ContactPerson myContactPerson = ContactPersonDB.GetItem(id);
if (myContactPerson != null && getContactRecords)
{
myContactPerson.Addresses = AddressDB.GetList(id);
myContactPerson.EmailAddresses = EmailAddressDB.GetList(id);
myContactPerson.PhoneNumbers = PhoneNumberDB.GetList(id);
}
return myContactPerson;
}

The first overloads calls the second, and passes a default value of false for getContactRecords.

Just as with the EmailAddressManager class, the ContactPersonManager class calls ContactPersonDB.GetItem(id) to get a ContactPerson instance from the database. It then checks if the ContactPerson returned from the DAL is not null and that getContactRecords is true. If both are true, the code proceeds by assigning values to the three lists for the Addresses, EmailAddresses and the PhoneNumbers. Each of these lists gets a value by calling its associated GetList method in the DAL, passing it the ID of the contact person. That way, the Address class knows for which contact person it must retrieve address records for example.

At the end of the code, if the ContactPerson was found in the database, it is returned to the calling code. Otherwise, myContactPerson would contain null indicating to calling code that there is no ContactPerson for the requested ID.

The Save method of the ContactPersonManager class is also capable of saving the associated contact records. It does this by calling Save on each contact record in the list when requested:

public static int Save(ContactPerson myContactPerson)
{
using (TransactionScope myTransactionScope = new TransactionScope())
{
int contactPersonId = ContactPersonDB.Save(myContactPerson);
foreach (Address myAddress in myContactPerson.Addresses)
{
myAddress.ContactPersonId = contactPersonId;
AddressDB.Save(myAddress);
}

foreach (EmailAddress myEmailAddress in myContactPerson.EmailAddresses)
{
myEmailAddress.ContactPersonId = contactPersonId;
EmailAddressDB.Save(myEmailAddress);
}

foreach (PhoneNumber myPhoneNumber in myContactPerson.PhoneNumbers)
{
myPhoneNumber.ContactPersonId = contactPersonId;
PhoneNumberDB.Save(myPhoneNumber);
}

// Assign the ContactPerson its new (or existing) ID.
myContactPerson.Id = contactPersonId;

myTransactionScope.Complete();

return contactPersonId;
}
}

First, the ContactPersonManager saves the ContactPerson by calling a method with the same name in the ContactPersonDB class. This is identical to how objects like EmailAddress are saved in the database. Then the code continues to save each individual contact record by looping through the respective collections and passing each contact record to the Save method in the respective *DB class.

foreach (EmailAddress myEmailAddress in myContactPerson.EmailAddresses)
{
myEmailAddress.ContactPersonId = contactPersonId;
EmailAddressDB.Save(myEmailAddress);
}

Before Save is called, each contact record gets its ContactPersonId property assigned, which is the result of the Save method of the ContactPerson.

Notice how the entire code block for the method is wrapped inside a using block that creates a new TransactionScope object. The TransactionScope automatically sets up a transaction for all other code that falls within its scope. This is useful to ensure the database remains in a valid state. Whenever an error occurs, for example because the code tries to save an Address with an invalid ContactPersonId, all previous database actions are rolled back. So, for example, when you save a contact person, then four of its addresses and two email addresses but the code fails on the first Save action of a phone number, the transaction for the ContactPerson, Addresses and EmailAddresses is undone.

For this code to work correctly, you need to have the Microsoft Distributed Transaction Coordinator up and running. As of Service Pack 2, it's off by default. Check the following article for more details about configuring MSDTC.

If you have the web site and the database on different machines, make sure you configure MSDTC on both of them.

The Delete method in the ContactPersonManager class always deletes the associated contact records; there is no point in having an Address record in the database without an associated ContactPerson. In fact, the database will throw an exception when you try to delete a ContactPerson that still has contact records attached to it.

public static bool Delete(ContactPerson myContactPerson)
{
return ContactPersonDB.Delete(myContactPerson.Id);
}

The Delete method doesn't use a TransactionScope object to manage transactions. All contact data records are deleted in the procedure that deletes a contact person:

CREATE PROCEDURE sprocContactPersonDeleteSingleItem

@id int

AS

BEGIN TRAN

DELETE FROM
Address
WHERE
ContactPersonId = @id

IF @@ERROR <> 0
BEGIN
ROLLBACK TRAN
RETURN -1
END

DELETE FROM
EmailAddress
WHERE
ContactPersonId = @id

IF @@ERROR <> 0
BEGIN
ROLLBACK TRAN
RETURN -1
END

DELETE FROM
PhoneNumber
WHERE
ContactPersonId = @id

IF @@ERROR <> 0
BEGIN
ROLLBACK TRAN
RETURN -1
END

DELETE FROM
ContactPerson
WHERE
Id = @id

IF @@ERROR <> 0
BEGIN
ROLLBACK TRAN
RETURN -1
END

COMMIT TRAN

This code first deletes all contact data based on the incoming ID of the contact person. Once the contact records have been deleted, it also deletes the contact person itself. Notice how after each DELETE statement the code checks for an error, and if necessary rolls the transaction back. This is necessary to avoid records from being deleted when an error occurs later in the stored procedure. Thanks to csharpdev who commented on this at the end of this article, pointing out a big mistake I had in an earlier version of this article.

Wrapping it Up

With the code you have seen so far, we have a nice API to manage ContactPerson objects and their associated contact records. With the available classes, you can now create, edit and delete ContactPersons, EmailAddresses, Addresses and PhoneNumbers programmatically. The following code snippet shows you how to create a ContactPerson and save it in the database:

ContactPerson myContactPerson = new ContactPerson();

myContactPerson.FirstName = "Imar";
myContactPerson.LastName = "Spaanjaars";
myContactPerson.DateOfBirth = new DateTime(1971, 8, 9);
myContactPerson.Type = PersonType.Family;

Address myAdress = new Address();
myAdress.Street = "Some Street";
myAdress.HouseNumber = "Some Number";
myAdress.ZipCode = "Some Zip";
myAdress.City = "Some City";
myAdress.Country = "Some Country";
myContactPerson.Addresses.Add(myAdress);

EmailAddress myEmailAdress = new EmailAddress();
myEmailAdress.Email = "Imar@DoNotSpam.com";
myEmailAdress.Type = ContactType.Personal;
myContactPerson.EmailAddresses.Add(myEmailAdress);

PhoneNumber myPhoneNumber = new PhoneNumber();
myPhoneNumber.Number = "555 - 2368";
myPhoneNumber.Type = ContactType.Personal;
myContactPerson.PhoneNumbers.Add(myPhoneNumber);

ContactPersonManager.Save(myContactPerson);

This code creates a new ContactPerson, sets a few public properties, and then adds an Address, an EmailAddress and a PhoneNumber instance to the respective collections of the ContactPerson object. As soon as Save is called, the ContactPerson is saved in the database, as well as all of its contact records.

Getting a fully populated ContactPerson is now very easy as well. Let's assume the ContactPerson in the previous code block got an ID of 29. Getting it from the database, together with all its associated data can be done with a single line of code:

myContactPerson = ContactPersonManager.GetItem(29, true);

To see what the ContactPerson looks like, you can set a breakpoint in your code and then look at the ContactPerson object in the watch window. To do this, follow these steps:

  1. Clear all existing break points by pressing Ctrl+Shift+F9
  2. Put your cursor on the line that calls ContactPersonManager.GetItem(29, true) and press F9. This sets a breakpoint in the code window. When the code executes, it'll stop here.
  3. Hit F5 to start debugging your application. As soon as the code hits the line above, execution will halt. Notice that the actual line with the breakpoint hasn't executed yet, so you'll need to press F10 to execute it.
  4. Next, right-click the myContactPerson variable and choose Add Watch. The variable is added to the Watch window where it looks like this:

The Watch Window for the ContactPerson showing the contact person data and its associated contact records
Figure 4 - The Watch Window for the ContactPerson

Notice how I expanded the Addresses and PhoneNumbers collection one level deep, while I also expanded the first EmailAddress in the collection, showing its full details. You may wonder how it's possible that you see these details in the Watch window, instead of the default Namespace.ClassName naming scheme that Visual Studio usually uses for custom types. The full details are made available by a DebuggerDisplay attribute on the respective classes. For example, the attribute for the Address class looks like this:

[
DebuggerDisplay("Address: {Street, nq} {HouseNumber, nq}
{City, nq} - {Country, nq} ({Type})")
]
public class Address
{

Within the DebuggerDisplay's constructor you specify a string with placeholders, each one wrapped in a pair of curly brackets. At debug time, these placeholders are replaced with their run-time values. In this example, the Address object displays its Street, Housenumber, City and Country properties and displays its type in parentheses. The nq inside the placeholders stands for no quote and is used to remove the quotes from string variables. Take a look at this article for a detailed examination of the DebuggerDisplay attribute.

While it's certainly useful to manage your contact persons and their contact data programmatically, this isn't always the easiest way to deal with it. It would be much easier if you'd be able to drag and drop a number of controls on a web page, set some properties using wizards, property grids and tasks panes and then be able to manage your contacts in a web browser.

This is exactly what part three of this article series will show you. You'll see how to create pages that display a list of contact persons. This lists is retrieved by calling GetList() on the ContactPersonManager class through an ObjectDataSource control. Each contact person in the list will be updatable and obviously, you'll be able to add new contact persons as well. You'll also be able to add, edit and delete the contact records for each contact person.

Summary

This article showed you how to implement your own business objects in .NET. You saw how to build the classes in the Business Objects and Logic Layers and how to write code for data access in the Data Access Layer. The four classes in the business layer, ContactPersonManager, AddressManager, EmailAddressManager and PhoneNumberManager can be used to manage your contact persons and their contact data in any type of application as they are not specific to ASP.NET.

First I showed you how most of the class files in the application are laid out, with a #region for each important section of the class. Then you saw how to write the private and public properties of the classes in the business objects layer. Most of the article was spent on discussing the inner workings of the methods in the classes that are responsible for getting data from the database and for inserting, updating and deleting existing records.

At the end of the article you saw a quick example of how you can use these classes programmatically.

Since a programmatic way to manage your contact persons isn't often the easiest solution, part three of this article series will show you how to use the classes in a web application that mostly uses declarative databinding.

Download Files


Where to Next?

Wonder where to go next? You can read existing comments below or you can post a comment yourself on this article .


Consider making a donation
Please consider making a donation using PayPal. Your donation helps me to pay the bills so I can keep running Imar.Spaanjaars.Com, providing fresh content as often as possible.



Feedback by Other Visitors of Imar.Spaanjaars.Com

On Saturday, February 17, 2007 9:59:49 PM sur said:
I came across part 1 only today and read it from start to finnish. I then noticed Part 2 had just been posted today and read that right through. Im looking forward to part 3. Very well written and i intend to read both articles again. I've read several articles regarding n-tier designs and yours seems to be the best thought out.

There was one line near the end of part 2 which wasnt clear:
myContactPerson = ContactPersonManager.GetItem(26);
Isnt that supposed to read GetItem(26, true)?

Brilliant article again!!!
On Saturday, February 17, 2007 11:33:22 PM Imar Spaanjaars said:
Hi sur,

Glad you like the article!! I am working on part three which should be posted on or before Feb 25th.

And yes, good catch. It should indeed be GetItem(26, true). Thanks for that; it's fixed now.

Cheers,

Imar
On Sunday, February 18, 2007 2:11:56 PM Tahir said:
Imar,

Good article as expected! One question: ContactPerson's GetItem is overloaded to accept Boolean value and then it retrieves Emails etc. You mentioned that in smart classes one can put lazy loading in property procedures. Is this approach of overloading GetItem similar to that as I guess it achieves the same purpose

Tahir
On Sunday, February 18, 2007 2:29:02 PM Imar Spaanjaars said:
Hi Tahir,

Well, not really, actually.

Lazy loading is about postponing loading data, where you postpone loading the values for the properties until you actually need them. As soon as you access a property for the first time, its data is retrieved from the data source. After that, the data remains available.

With the GetItem for the ContactPerson I give a developer the option to choose whether to load all data, or only the contact person's base data. There is no direct way to load the contact records data afterwards.

However, that's the whole idea; you use false as the parameter (or the overload version without the bool option) to get a contact person without any of its related data only when you don't need that data at all.

Hope this clarifies things,

Imar
On Sunday, February 18, 2007 2:49:14 PM Tahir said:
Imar,

Yes that was helpful.

As we do not have the smart classes here, in order to mimic the lazy loading will we then write something like:

myContactPerson.Addresses = AddressDB.GetList(id);

This will load Addresses and then we can use it. Is this correct?

Tahir
On Sunday, February 18, 2007 2:54:47 PM Imar Spaanjaars said:
Hi Tahir,

It depends on where you're calling this from.

If your example comes from the Bll, then yes, that is something you could do.

If you're calling this in the presenation layer then you shouldn't use this as it uses the DB classes directly. Instead, you'd use something like this:

myContactPerson.Addresses = AddressManager.GetList(myContactPerson.Id);

Imar
On Sunday, February 18, 2007 3:06:13 PM Tahir said:
Imar,

Yes that is what I meant actually :).

However with this approach there can be one question whether Addresses, Emails etc should be then part of ContactPerson BO because they can are being accessed directly. That however is a matter of choice I guess.

The article is nice as I said before.

Tahir
On Sunday, February 18, 2007 5:53:19 PM Imar Spaanjaars said:
Hi Tahir,

Glad you like this article.

Address and ContactPerson are already in the same namespace so they can "legally" use / consume each other.

Making Address "part off" ContactPerson probably isn't a good idea. By making it a nested class, it becomes less clear how to work with instances of Address directly....

Imar
On Sunday, February 18, 2007 6:46:21 PM Tahir said:
Hi Imar,

I have discussed about another layer Business Processes previously and in P2P wrox forum as well. I would have created a Process e.g. CreateNewContactProcess that will do the same you did in following code:

ContactPerson myContactPerson = new ContactPerson();

myContactPerson.FirstName = "Imar";
myContactPerson.LastName = "Spaanjaars";
myContactPerson.DateOfBirth = new DateTime(1971, 8, 9);
myContactPerson.Type = PersonType.Family;

Address myAdress = new Address();
myAdress.Street = "Some Street";
myAdress.HouseNumber = "Some Number";
myAdress.ZipCode = "Some Zip";
myAdress.City = "Some City";
myAdress.Country = "Some Country";
myContactPerson.Addresses.Add(myAdress);

EmailAddress myEmailAdress = new EmailAddress();
myEmailAdress.Email = "Imar@DoNotSpam.com";
myEmailAdress.Type = ContactType.Personal;
myContactPerson.EmailAddresses.Add(myEmailAdress);

PhoneNumber myPhoneNumber = new PhoneNumber();
myPhoneNumber.Number = "555 - 2368";
myPhoneNumber.Type = ContactType.Personal;
myContactPerson.PhoneNumbers.Add(myPhoneNumber);

ContactPersonManager.Save(myContactPerson);

In the CreateNewContactProcess I would do following

{
ContactPerson myContactPerson = new ContactPerson();
myContactPerson.FirstName = "Imar";
myContactPerson.LastName = "Spaanjaars";
myContactPerson.DateOfBirth = new DateTime(1971, 8, 9);
myContactPerson.Type = PersonType.Family;
ContactPersonManager.Save(myContactPerson);

Address myAdress = new Address();
myAdress.Street = "Some Street";
myAdress.HouseNumber = "Some Number";
myAdress.ZipCode = "Some Zip";
myAdress.City = "Some City";
myAdress.Country = "Some Country";
AddressManager.Save(myAddress);

EmailAddress myEmailAdress = new EmailAddress();
myEmailAdress.Email = "Imar@DoNotSpam.com";
myEmailAdress.Type = ContactType.Personal;
EmailAddressManager.Save(myEmailAddress);

PhoneNumber myPhoneNumber = new PhoneNumber();
myPhoneNumber.Number = "555 - 2368";
myPhoneNumber.Type = ContactType.Personal;
PhoneNumbersManager.Save(myPhoneNumber);
}


This way each of the business object (ContactPerson, Address, EmailAddress, PhoneNumber) will be cohesive and flexible and UI can call the process which saves us from putting the code behind code-behind. The process can do validations by calling validation funtions of individual business objects (BLL's).

What do you think? I might be generating more work for myself by creating a seperate layer however I feel that it gives more flexibility.

Tahir
On Sunday, February 18, 2007 9:41:56 PM Imar Spaanjaars said:
Hi Tahir,

I fail to see how this works or why this would be useful. Is the example correct? What's the point of hardcoding stuff like this:

ContactPerson myContactPerson = new ContactPerson();
myContactPerson.FirstName = "Imar";
myContactPerson.LastName = "Spaanjaars";

in a separate class?

I am sure you have some way to send information from the UI in this process class, but I can't see how. Either this class has parameters for all the information you want to pass, or it has a strong relation with the presentation layer, in which case I don't really see the difference with, say, the code behind class of a page.

Can you elaborate? It's a bit difficult to post code here, so maybe you can post an example somewhere with more details?

Imar
On Sunday, February 18, 2007 10:10:19 PM Tahir said:
Hi Imar,

Well I was not able to explain myself. From the UI the process will get three business objects. The process will then call *ManagerDB.Save for each of them, after doing validations, security checks etc. Note that validations and security etc is also in the BLL's but only called from the process rather than the UI.

I hope I am clearer this time :)

Tahir
On Sunday, February 18, 2007 10:14:11 PM Tahir said:
Hi Imar,

The difference between having such calls from UI and a process, IMO, is that one can change the application type between Web, Windows, Web Service etc.

I am converting a Windows based application into a Web based application and such an approach makes life easier. Well don't ask why I am converting as its client's request :).

Tahir
On Monday, February 19, 2007 1:37:21 PM Tahir said:
Hi Imar,

I dont see anything wrong with the architecture you presented. Infact this is what many of us use in our real-world project. However, I have one question. Are such architectures Object-Oritned? I am asking this after reading Expert VB.NET Business Objects (CSLA.NET) which you recommended me. Our architecture here seems more procedural than OO.

What are your thoughts on this?

Tahir
On Monday, February 19, 2007 1:58:22 PM Imar Spaanjaars said:
Heuh? Why shouldn't this be object oriented? What makes this procedural?

And even of it was, what would it matter?

Imar
On Monday, February 19, 2007 2:06:40 PM Tahir said:
Hi Imar,

The reason why I am saying it *may* be procedural is that all we are doing is calling procedures in BLL and DAL. In other words if this *is* OO then what is the difference between this and prcedural architectures?

Why it matters? Well it does'nt at all. Author of even the worse architecture can say 'so what'. I am only discussing a thought!!!

Tahir
On Monday, February 19, 2007 2:09:25 PM Imar Spaanjaars said:
Well, it depends on how you look at it.

I'd say that:

myContactPerson.Address.Add(new Address());
myContactPerson.Save();

classifies as OO.....

Imar
On Monday, February 19, 2007 2:15:48 PM Tahir said:
Yes it will however we are not doing this, we are doing something like:

ContactPersonManager.Save(myContactPerson);

This is calling a procedure, passing an argument and getting back a result.

What I am discussing with you is 'Architecture'. As I said I have used similar design myself as well but its just a thought.

Tahir
On Monday, February 19, 2007 2:18:08 PM Imar Spaanjaars said:
>> just a thought.

Sure....
On Tuesday, February 20, 2007 10:40:50 AM Simon said:
Imar, great follow up article.

I was wondering if you could recommend any code tools (like codesmith) that you use to reduce the manual production of Layered Web apps in this style? Or do you tend to do everything manually?
On Tuesday, February 20, 2007 12:26:44 PM Imar Spaanjaars said:
Hi Simon,

In-house, in the company I work for, we've created custom code generators that generate much of this code, including all the properties and methods, stored procedures etc. Tools like LLBLGEN and CodeSmith can certainly be used for this as well.

In addition to that, I also use Coderush from DevExpress.com. This tool doesn't generate entire classes for you, but can certainly improve your productivity.

Cheers,

Imar
On Tuesday, February 20, 2007 12:58:23 PM Tahir said:
Imar,

The Architecture you presented here is great and works well. Its flexible, team-friendly and simple/logical to understand.

You mentioned on P2P forum in our discussion that the architecture you produce 'depends' on the application, whether its $2000 or $20,000. I agree 100%. Can you tell us what scale projects are suitable for this architecture. Also how would you extend/improve this for larger projects. What are the things you will add/change/remove for larger projects.

Tahir
On Tuesday, February 20, 2007 5:46:27 PM Imar Spaanjaars said:
Hi Tahir,

The architecture presented in this article scales up to projects of exactly $17,248.33 and not a penny more...... ;-)

Come on!!!!!! You keep asking questions I cannot answer and worse, you keep asking the same question over and over again. How long is a piece of string?

My last reply in this thread is STILL my take on it: http://p2p.wrox.com/topic.asp?TOPIC_ID=55781&whichpage=4

If you'd agreed 100% you would have understood.....

Imar
On Tuesday, February 20, 2007 6:19:08 PM Stan said:
Imar,
Very great article.
I learned very quickly (sorry for the english) and the way you teach the Ntier architecture is very clear
I've bought many ASP.net 2.0 books on this topic to understand the concept but yours, once again, is very very great.

However, I have a question about smart class and dumb classes.
Which one is better to use ?

By the way you talked about a Part III, when do think it will be available ????

Stan
On Tuesday, February 20, 2007 9:53:23 PM Imar Spaanjaars said:
Hi Stan,

Glad you like the article.

In the intro of this article I said I am aiming to publish part three on or before February 25th. Hopefully, I can make that self-inflicted dead-line.

Cheers,

Imar
On Tuesday, February 20, 2007 11:29:39 PM Tahir said:
Hi Imar,

:) Sorry for being a pest. I think what I should do is to write something about my ideas. I will write a short article and if you give me permission I will implement this same application of contacts management using the architecture I am working towards. I dont have a website (yet) so if you dont mind I can send you the article to publish on your website. If not I am planning to build a website for myself and I can publish it there.

Writing about my own ideas is what I should do. This will give me a basis for further discussions with you and other developers.

Tahir
On Wednesday, February 21, 2007 3:22:03 AM Glen said:
Firstly, thanks for replying to another question I posted earlier.  

Again another great article but would you be able to explain how you would handle a delete of one of the items in a list eg. Address as part of the ContactPersonDB.Save. i.e. Your current code does a foreach save on each address however if an address is deleted no operation would be performed on that address as part of the current save functionality?  Is that right?
On Wednesday, February 21, 2007 3:37:12 AM Glen said:
Hi,

An amendment to my post earlier, that should read the PersonContactManager.Save not as I have said PersonContactDB.  

I have just been thinking about it though, would it be good practice to have a delete flag as part of the business object and setting this when a delete has happend in the UI.  As part of the PersonContactManager.Save and iterating thru the Addresses you could then call the AddressManager.Delete when flagged?
On Wednesday, February 21, 2007 6:58:18 AM Imar Spaanjaars said:
Hi Glen,

Yeah, there are multiple ways to do this. One would be to override Remove on the Addresses and other lists (you'll need to implement a separate type for that) and then set a Delete flag or immediately delete the item.

Alternatively, you can create a separate list type that holds the addresses. In addition to the standard list of Items, you could keep track of a DeleteItems collection as well. Then saving the Addresses collection would work like this:

foreach (Address myAddress in DeleteItems)
{
  myAddress.Delete();
}

foreach (Address myAddress in Items)
{
  myAddress.Save();
}

You would still need to override Remove and then move an item from the Items collection into the DeleteItems collection.

Does this help?

Imar
On Wednesday, February 21, 2007 7:00:01 AM Imar Spaanjaars said:
Hi Tahir,

Yes, writing about it is a good way to clarify your ideas and thoughts about this.
There are many free Blog sites out there so I am sure you'll find a spot to publish your article on....

Imar
On Wednesday, February 21, 2007 9:49:11 AM Tahir said:
Hi Imar,

Hmmm that is a good idea. I will look for a website to blog. Do you recommend any?

Tahir
On Wednesday, February 21, 2007 9:49:39 AM Alex said:
Hi Imar,

... if I cannot use "MSDTC", have I to open connection and to call BeginTransaction in BL? After this, have I to pass the connection object to DA classes?

I do not speak english very well, sorry!!!

On Wednesday, February 21, 2007 3:00:59 PM antonio said:
How do you avoid circular references if you were to build your application using "smart" objects that manage their own persistence logic?
On Wednesday, February 21, 2007 5:51:52 PM Imar Spaanjaars said:
Hi Alex,

Take a look at the SqlTransaction class here:

http://msdn2.microsoft.com/en-us/library/system.data.sqlclient.sqltransaction.aspx

Cheers,

Imar
On Wednesday, February 21, 2007 5:54:29 PM Imar Spaanjaars said:
Hi antonio,

One way to avoid that is to have the data access code embedded in the object as well. That way, you have an object that holds data, has methods to get and save data, and access the database at the same time.

Alternatively, with a separate data layer, you get methods like this in the DAL:

public int Save(int id, string name, string lastName etc etc)
{}

So, for each public property of the business object, you send a .NET simple type in the DAL method.

Does this help?

Imar
On Wednesday, February 21, 2007 9:05:51 PM Tahir said:
Hi Imar,

In case of smart classes we will have methods like Load, Save, Remove performing operations on the private properties. However if a client wants to get a list of 'smart' BO then there can be a shared/static method e.g. LoadList that retrieves a list of BOs. The issue that I am thinking is that to load one record it is easy i.e. just filling in the private variables. However how can we return a list of BOs? Secondly I dont want to have a seperate BOList class because that makes data binding with ASP.NET more complex e.g. a datagrid is bound to objectdatasource having TypeName = "BOList" but then the add and delete methods are in BO.

Tahir
On Friday, February 23, 2007 3:49:19 AM Glen said:
Hi Imar,

Thanks for the article I am now beginning to put this into practice in my current project.  Would you care to comment on the code attributes that are in your code source.

i.e. [DataObjectAttribute()], [DataObjectMethod(DataObjectMethodType.Select, true)] etc
On Saturday, February 24, 2007 12:39:10 PM Imar Spaanjaars said:
Hi Glen,

I am glad you like the article. Hopefully, you're making good progress using these principles in your own project?

The next article, part three, talks about DataObjectAttribute and DataObjectMethod, so stay tuned. Hopefully, I can publish the third part this weekend.

Imar
On Monday, February 26, 2007 4:49:22 PM sur said:
Just a quick question, in your FillDataRecord methods you use something like:

myDataRecord.GetInt32(myDataRecord.GetOrdinal("Id"));

Could you also use this instead:
Convert.ToInt32(myDataRecord["Id"]);

or even this:
(int)myDataRecord["Id"];
On Monday, February 26, 2007 5:46:12 PM Imar Spaanjaars said:
Hi sur,

Yes, you could do that too, and it would give you the exact same result.

Imar
On Wednesday, March 07, 2007 8:44:55 AM Goran said:
Hi Imar!

This is indeed a masterpiece! I love it that you have value objects in
one layer (BusinessObject) and the logic in one layer(BusinessLogic).
But you should have the layers in different assemblies...

/Goran

On Wednesday, March 07, 2007 5:52:14 PM Imar Spaanjaars said:
Hi Goran,

Glad you like the article.

*Technically*, the different layers *are* already in separate assemblies as each folder in App_Code is compiled to a separate assembly ;-) However, I understand that's not what you meant.

To make it easier for readers of this article to follow along, I decided to use a single web site with an App_Code folder. But I agree with you that for more serious projects it would be necessary to create separate assemblies / projects. That's why I wrote this:

"To continue this separation, I also used different folders inside the App_Code folder of the application. In a larger application you may want to use three separate class libraries instead. The Business Layer class library would then have a reference to the Business Objects and Data Access class libraries, the DAL would also have a reference to the Business objects layer, while the final web site would reference the BO and BLL layers. In the sample application for this article, I decided to keep things simple and store everything in separate folders under App_Code, but the exact same principles would apply if you'd be using class library projects. "

Not everyone has the full version of Visual Studio  2005 which is required to work with separate class libraries in the same solution as the web site. With my current setup, everyone using Visual Web Developer Express Edition and up can follow along with the code and article.

Cheers,

Imar
On Thursday, March 08, 2007 7:20:16 AM Saif Khan said:
Great article. From an architect point-of-view this

myContactPerson.Addresses = AddressDB.GetList(id);
myContactPerson.EmailAddresses = EmailAddressDB.GetList(id);
myContactPerson.PhoneNumbers = PhoneNumberDB.GetList(id);

Is a bit chatty. Developers should strive for least connection as possible.

Great articles though Imar.
On Thursday, March 08, 2007 8:27:40 AM Goran said:
Hello again Imar!

I've totally missed that section in your article, sorry about that.
And as you said not everyone has full version of Visual Studio  2005.

/Goran
On Thursday, March 08, 2007 9:42:59 AM Imar Spaanjaars said:
Hi Saif,

Yeah, that's an interesting point to consider. One way around this is to tie the ContactPerson even further to the contact record classes, and pass an open connection to their respective data access methods. Alternatively, you can have the ContactPerson procedures query the details for the contact person and all of its related data in a single procedure and then use NextResult on a data reader to forward each reader into a constructor of the respective contact data collections.

E.g.

SqlDataRReader myReader = Get reader with person data
// fill the person

// Next fill the Addresses
myContactPerson.Addresses = AddressDB.GetList(myReader);

This way, GetList accepts an open reader and uses it to set up the internal list which is then returned. This is just an example, and there are many other ways to avoid chatty calls in the DAL.

However, since connection pooling is on by default on many Windows systems, this greatly minimizes the impact of opening and closing a connection. Therefore, although the code uses a chatty way to access the data, you probably won't find much performance difference in many situations.

Obviously, your mileage may vary, so it's always good to consider (and test) this. Thanks for bringing it up.

Imar
On Thursday, March 08, 2007 2:26:22 PM Rumata said:
Overall, a simple and clean approach. I have a high level question though. I am developing a very large scale web application (millions, hunderds of millions of rows of certain data). Obviously, I am very concerned with performance when picking an architecture. I am actually in the process of rewriting the app and open to various new ideas.

While overall I like your approach and it appeals to me, my concern is performance especially in regards to GetList functionality, where you have to instantiate a class for each record. I think this would impact performance immensly in my case. Do you have any alternative suggestions? What is your take on developing a much larger scale app (where there might not be a lot of classes, but the amounts of data is huge)?

Thanks

On Thursday, March 08, 2007 8:34:07 PM Imar Spaanjaars said:
Hi Rumata,

Whatever design you choose, I don't hope you're going to hold all those hundreds of millions of rows in memory at the same time.

Instead, you should look for "paging" or filtering methods in your design. It's a bit hard for me to propose what to do exactly as I don't know how your data looks like and what you want to do with it, but I wouldn't try to have them in memory all the time.

Think of how SQL Server and ADO.NET do it. SQL can contain millions of rows, but when you need them (for example, when exporting data), you don't get all of them in a single object like a DataSet, but instead use a SqlDataReader to get them one by one. That way, you won't bring down your system when you read the data.

Sorry I can't give you a more detailed answer, but it's a but difficult to suggest something without knowing the full details of your business domain and problem.

Cheers,

Imar
On Friday, March 09, 2007 9:54:20 PM Saif Khan said:
Rumata,
As Imar said, it depends on the design. Look at the app in two ways, is this OLTP and reporting.

If reporting, use datasets and paging. There are reappy no objects to reporting so consider webservices so the same reports can then be implemented by several future applications.

If OLTP, No customer will scroll a million records. These days it's always about BI to make quick decisions and most app are trying to incorporate quick summary odf data. And above all, like Imar said, why load millions of rows in memory, that's a waste of resources.

Filter the GetList by business parameters like, CustomerID, Date Range, ProductID and so.

Imars work can handle any amount of data, it how use use his design and implementation.

Keep the word "REFACTOR" always at the back of your mind.

Saif
On Friday, March 09, 2007 9:56:08 PM Saif Khan said:
Pardon my Grammer.....blame it on Vista. Just kidding.
On Saturday, March 10, 2007 7:50:15 PM Jesse Dyson said:
Imar,
Thank you for this excellent article!  Your article provides the only example I've found so far that is based on Business Objects and leverages the .NET platform.  I am using much of your advice in a project I am working on now.  I just had one question.

In your part 2 article, under "Working with Instances of EmailAddress", you demonstrate a method called GetItem that accepts as a parameter an IPrincipal object.  I like this approach since it allows us to delegate the security checks to the Business Logic Layer.  How would I go about passing Context.User to this parameter from a DataGrid or DetailsView?

Thanks in advance for any assistance.
On Sunday, March 11, 2007 10:40:55 AM Imar Spaanjaars said:
Hi Jesse,

Assuming your data bound controls use an ObjectDataSource to get the data, you can use one of the *ing events that fire before the Bll is called (e.g. Selecting, Updating, Inserting and Deleting). Inside these events, you can assign the Context.User to the parameters for the method. Let's take GetList of ContactPerson as an example. First, you need to change the signature:

public static ContactPersonList GetList(IPrincipal currentUser)
{}

so it accepts an IPrincipal.

Next, you add an additional parameter to the ODS that selects the data:

[SelectParameters]
  [asp:Parameter Name="currentUser" Type="Object" /]
[/SelectParameters]

Finally, you handle the Selecting event of the ODS:

protected void odsContactPersons_Selecting(
         object sender, ObjectDataSourceSelectingEventArgs e)
{
  e.InputParameters["currentUser"] = Context.User;
}

This code assigns the current user to the InputParameters dictionary so eventually it ends up in the currentUser argument of GetList.

Hope this helps,

Imar
On Sunday, March 11, 2007 12:21:35 PM Math Random said:
Another great article.

It left me with some questions:
1. When you implement security in the BLL using the IPrincipal like you did, can that work with webservices as well? The layered application allows us to create a second or third top layer, but if it NEEDS to pass IPrincipal to each method, isn't that a limitation for webservices?

2. Wouldn't it be better to return an empty list instead of null when there are no records found? I'm not sure about the databinding issues with null values though.

3. How would you program the DAL if you didn't want to use TransactionScope but a 'normal' SqlTransaction? Is that possible when the SQL Commands are run from different methods in different classes?

4. You delete every related record in the Delete method for the ContactPerson, but you also have delete methods on Address and PhoneNumber, so why not loop over the collections and delete them through their own Delete method?

Looking forward to your response. I think it's great that you take the time to write this and answer our questions. You're welcome for lunch anytime.

Thanks!

Oh and about the lazy loading:
You could make the property getter check if the addresses are loaded and if not load them. However, that would mean you need a reference from the BO class to a BLL class, and that would mean a circular reference right?
On Sunday, March 11, 2007 12:42:12 PM Imar Spaanjaars said:
Hi Math Random,

1. Not necessarily, but it may mean a lot of work. In the book "Expert VB / C# 2005 Business Objects" book by Rockford Lhotka the author presents a way to serialize identities over the network (including web services) allowing you to write security code on both ends of the wire.

2. Yes, I think it would indeed be easier and better to do that. That would be easy to fix. Instead of this:

EmailAddressList tempList = null;

use this:

EmailAddressList tempList = new EmailAddressList();

and then don't instantiate a new list inside the HasRows block.

3. One way to do this is to pass your connection object around from method to method.

4. You could do that and it would be a purer way to do things. However, it also means a lot more data access calls. First, you would need to instantiate a ContactPerson, then load its contact data collections and then delete each and every individual object. By letting the sproc handle all of this, you only need a single call.

Regarding the lazy loading: yes, that's the case.  However, you might be able to solve this by using an interface although I haven't actually tested this.

Imar
On Tuesday, March 20, 2007 9:34:52 AM KUMARAN said:
Hi Imar

Excllent article which help me to create Ntier application.

But I found difficult to create Object datasource and converting into the class file.

Please can you refer me a article to learn creating objectdatasource, converting into class

Hope you could help me.

thanks in advance

regards
kumaran.m
On Tuesday, March 20, 2007 11:50:24 AM Imar Spaanjaars said:
Hi KUMARAN,

What do you mean with "create Object datasource and converting into the class file"?

If I understand you correctly, this is not how it works. First, you create the class file, and then connect to that class with an ObjectDataSource. Can you explain your question in more detail?

Imar
On Friday, March 23, 2007 5:00:32 AM Saif said:
My advice/opinion: stay away from ODS!
On Friday, March 23, 2007 8:11:02 AM Imar Spaanjaars said:
Hi Saif,

In its current form, this is quite a useless statement. You tell people not to use the ODS, but don't offer any alternatives.

Would you care to explain *why* you should stay away from the ODS and what your alternatives are?

Imar
On Sunday, March 25, 2007 2:53:07 PM Garry said:
Hey Imar,

Although you probably tire of hearing it, I'll repeat "great article!".

I need some help with the circular reference issue though. I'm new to OO and have just been exposed to the circular reference concept. From where I sit, I suspect it *could* be a problem at times, but is it always bad?

Math Random's question above talked about using the getter of the business object to lazy-load the collection. Even though this is technically a circular reference, is it a bad one? It would work to do it this way, wouldn't it?

It seems like the BLL methods would always be static. I could imagine this being a problem if 2 instances referenced each other. You could probably end up in a deadlock, right? But in this case I fail to see the problem apart from a conceptual perspective.

Thanks,

Garry
On Sunday, March 25, 2007 3:54:42 PM Imar Spaanjaars said:
Hi Garry,

Technically, there's often not much stopping you from adding a circular reference. When you try to do it in Visual Studio though, by adding a reference from project A to Project B and then from project B to project A, it won't let you. However, you can bypass this check by browsing to the physical DLL of the other project.

However, this also means that one project is build using an *older version* of the other project. The compiler needs to start with one project. If that project references another, it can't do a full compile of that other project first, so it will use an outdated assembly. This may lead to run-time problems when you later add code to the projects and forget to update all you references and assemblies.

So, it's certainly possible to have circular references in your projects, but it's probably best to always try to avoid them as they can lead to subtle bugs or other issues. In many cases, redesigning your application makes the problem go away.

For some more info on this, take a look here:

http://www.codeguru.com/forum/archive/index.php/t-332083.html

At the moment, the site is down, but searching Google for this URL should bring up a cached version.

Also try searching for:

"Adding this project as a reference would cause a circular dependency"

It brings up some interesting results that deal with this topic in more detail.

Cheers,

Imar
On Tuesday, March 27, 2007 2:52:16 AM Ashley said:
Hi,

You use the AddressList class to enable the following code simplification:


--note square brackets below are substituting normal generics syntax
**********************
private List[EmailAddress] emailAddresses = new List[EmailAddress]();
    you can now declare an address list with this simplified code:
private EmailAddressList emailAddresses = new EmailAddressList();
**********************

I'm struggling to achieve the same code simplification using vb.net.  I can create the list class fine, but when I try to declare the list elsewhere, it still expects the (of T) syntax.

eg: Dim emailAddress as EmailAddressList(Of EmailAddress) = new EmailAddressList(Of EmailAddress)

Am I doing something wrong, or is this a "limitation"/"difference" of vb.net

Thanks
Ashley.
On Tuesday, March 27, 2007 5:13:53 AM Imar Spaanjaars said:
Hi there,

How does the declaration for your AddressList in VB look like?

And sorry about the errors; you're not supposed to post anything that looks like HTML, which, unfortunately, includes C# generics syntax... ;-)

Imar
On Tuesday, March 27, 2007 5:21:35 AM Ashley said:
My declaration of AddressList in VB looks like..

Public Class AddressList(Of Address)
    Public Sub AddressList()

    End Sub
End Class
On Tuesday, March 27, 2007 6:54:28 AM Imar Spaanjaars said:
Hi Ashley

You're missing the inherits part:

Public Class AddressList
    Inherits List (Of Address)

End Class

In your code, you just defined a closed generic class, which didn't take out generics from the API.

Cheers,

Imar
On Tuesday, March 27, 2007 7:00:24 AM Ashley said:
thanks, that's fixed it.
On Monday, April 02, 2007 6:00:46 PM JJ said:
Good article on the designing of the objects and how to go about it.  Your business objects are nicely separated; however the catch is that you would be making multiple database connection when you execute the following statement.  I am assuming you are making a database connection for each method.

private AddressList addresses = new AddressList();
private PhoneNumberList phoneNumbers = new PhoneNumberList();
private EmailAddressList emailAddresses = new EmailAddressList();

By having each database connection, your objects are nicely encapsulated.  I thought about getting all the information with one database connection but that would make the business objects a real pain to design.  So in an enterprise setting, what methods are preferred?  I know the answers is "it depends" but just want some opinions on the issue.
On Monday, April 02, 2007 6:09:57 PM Imar Spaanjaars said:
Hi JJ,

Take a look at my reply to Saif from 3/8/2007 10:42:59 AM.

There are a few ways to do this, including forwarding open connections and forwarding open *DataReaders (where a single sproc retrieves all the data).

However, with connection pooling you can minimize the impact of opening and closing the connection, so having each DAL class perform its own data access will still peform pretty well.

Hope this helps,

Imar
On Tuesday, April 03, 2007 12:00:28 PM Milan said:
Hi Imar, great article series. Thanks

My q is: If for some reason I want to return ContactPerson list with one Address and one email Address, would you recommend returning them from database immediately with other contact data, or using AddressManager to retrieve one address for each contact?
On Tuesday, April 03, 2007 6:11:31 PM Imar Spaanjaars said:
Hi Milan,

Glad you like the articles.

There are a few approaches you can take in your case. One is to create additional methods in the Address class that return a single Address instance based on some criteria and load it in the list.

Another way is to load the entire list into the Addresses collection, and then create a method or property on the Addresses class that is capable of searching the list, and returning the right Address instance to calling code.

Hope this helps,

Imar
On Tuesday, April 10, 2007 4:45:38 AM Przemek said:
Hi,
I'm building an app using your tutorial as a template. I tried to compile my code and i get the following error numerous times:

"Error Cannot convert null to 'int' because it is a value type"

This is occuring because I decided to use the Nullable Types solution instead of the -1 solution for the value of NotSet for the Enums. I've read through the link that you gave in your tutorial but I cannot figure out how to code this type of application differently so that I can use Nullable Types.

I know the format is supposed to be 'int ? variable', but I don't know how to code that in the enum.

Please let me know if you know the solution. Thank you.

Przemek
On Wednesday, April 11, 2007 7:04:32 AM Imar Spaanjaars said:
Hi Przemek,

You shouldn't use an int?, you should use a nullable version of the enum. E.g.:

ContactType? myContactType = null;

Imar
On Friday, April 13, 2007 3:21:30 PM Edmund Ward said:
Hi Imar

Great article (set of articles)! It's such a relief to have a technical article well written. It makes it so much easier to understand.

My question is: if you wanted to store your enums in a table in the database, how would you manage them? Would you have a ContactType business object, ContactTypeManager object and a ContactTypeDB object or is there a simpler way to deal with a "lookup" table?
On Saturday, April 14, 2007 4:25:00 AM Przemek Lach said:
I understand your solution; however, how to do implement it in the enum?
Using your example your code looks like this:
public enum ContactType
  {
    Business = 0,
    Personal = 1,
    NotSet = -1
  }

How do I make the NotSet = null without getting compile time errors? That is what is happening right now when I run my project.

Thank you.
On Saturday, April 14, 2007 7:38:21 AM Imar Spaanjaars said:
Hi Edmund.

Glad you like the series.

I would probably use generics to make a generic ListManager that can retrieve all kinds of lists, like ContactType, Country, Status and so on.

I think I would create a generics class called NameValue (just an example) with a generic K key and a generic V value. You can then instantiate objects of this type using, for example, an int and a string. The int would be the Key in the database (1, 2, 34, whatever) and the string Value would contain the friendly description like Business, Personal and so on.

The ListManager could have methods like GetList which expects an argument that determines the type of list to retrieve. Then GetList would fire the appropriate stored procedure or SELECT statement and return a List of NameValue objects which can be used for databinding.

Finally, the objects itself would store the Id as a property. For example, Address could have a ContactTypeId of type int which maps to the Key (int) field of the NameValue object used to bind, say, a DropDownList.

Hope this helps,

Imar
On Saturday, April 14, 2007 7:47:32 AM Imar Spaanjaars said:
Hi Przemek,

The NotSet element in the ContactType enum is used to *emulate* null values. However, since you're now using a true nullable type, you don't need it any longer:

With this code:

public enum ContactType
{
  Business = 0,
  Personal = 1
}

ContactType? myContactType = null;

you get the same stuff as I had with the NotSet option.

Of course, since it's a nullable type now, you have to treat it a little different and use its properties like Value and HasValue.

Cheers,

Imar
On Sunday, April 15, 2007 6:45:37 AM Przemek said:
For some god forsaken reason I cannot get access to the AppConfiguration class. I have a web.config file, I have the proper inclusions in my class but I cannot get it to recognize. When I try to build I get an error at this line:
using (SqlConnection myConnection = new SqlConnection(AppConfiguration.ConnectionString))
Any ideas? Am I possibly missing something painfuly obvious?
Thanks
On Sunday, April 15, 2007 6:46:45 AM Przemek said:
Sorry about multiple posts. IE glitched out... :-(...???
On Sunday, April 15, 2007 7:27:17 AM Imar Spaanjaars said:
Hi Przemek,

Make sure that the casing of the class is the same; as C# is case sensitive.
Also, make sure that if you are using namespaces in your code, both the web page and the AppConfiguration either share the same namespace, or that the page has a using statement for the AppConfiguration's namespace.

Finally, make sure that AppConfiiguration doesn't contain compile errors.

Imar'
On Sunday, April 15, 2007 7:57:30 PM Przemek Lach said:
I am casing it correctly: AppConfiguration
I am using namespace in my code. I'm not really sure how to add a namespace in the web.config file. Basically my setup is exaclty the same as yours. I can build your application no problem and I do get AppConfiguration no problem. But, not in mine thought.
I'm totally out of ideas. I think I'm screwing something up with the namespace;however, my setup is same as yours so I don't know what else it can be???

Przemek
On Sunday, April 15, 2007 8:21:38 PM Imar Spaanjaars said:
Hi Przemek,

You can't add a namespace to the web.config. You should add it to the code.
E.g.

namespace SomeName
{
  //Definition of AppConfiguration here
}

Then in your classes that use AppConfiguration:

using SomeName;

But can you expain the problem in more detail? Do you get errors or warnings? What happens when you leave out the implementation from the properties in the AppConfiguration class? Are you then able to see it?

And what if you try:

Your.NameSpace.Here.AppConfiguration

Can IntellSense see your class?

Finally, do you get IntelliSense on ConfigurationManager in the ConnectionString property? Maybe there's a problem with your code seeing the System.Configuration namespace...

Imar
On Sunday, April 15, 2007 8:30:49 PM Przemek said:
That is the problem, the class doesn't come up in IntellSense. So when i try to enter the following line:

SqlConnection myConnection = new SqlConnection(AppConfiguration.ConnectionString)

I get AppDomain as my first choice, no AppConfiguration.

Now, you talk about creating an AppConfiguration class. Is that something you did in your project? I can't seem to find it.

I thought that in order to access the AppConfiguration class all i would need was a web.config file and a .cs file that has the following at the top:
using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;

So two files. In my case I have the web.config file an a ThumbnailDB.cs file in a DataAccess folder. The ThumbnailDB.cs file uses the BO namespace and has its own namespace of name.project.Dal.
On Sunday, April 15, 2007 10:18:48 PM Przemek said:
Imar,

I'm a complete moron. I figured it out.

Thanks anyways.
On Monday, April 16, 2007 11:33:15 AM Edmund Ward said:
Hi Imar

Thanks for your earlier reply.

On another issue, I have a product which may qualify for VAT (a tax factor). If I store the factor as a string in the web.config, in which layer should I apply the VAT to the product price? Should it be as part of the presentation, thus simplifying the underlying objects which all deal with prices exclusive of this tax, or should I apply it as part of the business layer:

public class MyProduct
{
private decimal price;
private bool vatqualifying;

public decimal Price
{
get
{
if (vatqualifying)
{
  return price * Convert.ToDecimal(WebConfigurationManager.AppSettings["VATFactor"]);
}
else
{
    return price;
}
}
set { price = value; }
}
}


This means that the price is always stored (in the object and the underlying database) as net of tax, whereas it will be displayed inclusive of tax if appropriate (on a website for example). The trouble with this approach, is that at the product user interface, we have to be careful since we don't know whether the user is entering prices inclusive or exclusive of tax. This makes me wonder whether it might be safer to apply this at the presentation layer. What would you recommend?

Cheers
Edmund
On Monday, April 16, 2007 6:28:39 PM Imar Spaanjaars said:
Hi Przemek,

Ha ha, by now you probably figured out that AppConfiguration is a custom class in the App_Code folder and not built-in in the .NET Framework? ;-)

Glad it's working.

Imar
On Monday, April 16, 2007 6:48:29 PM Imar Spaanjaars said:
Hi Edmund,

I would do it as part of the object. That way, the object is in control, so it can determine which amounts are with VAT and which are without. That makes it easier to work with the object.

However, your current design may be a little flawed. Normally, you should be able to set a property more than once, without any side effects. However, in your case, I don't think this is the case. Consider this:

myObject.Price = myObject.Price + 0;

What do you think happens here when vatqualifying is true? Under normal conditions, you'd think this would do nothing to the underlying value. However, in your case, the price is now updated with the VAT included. Do this a couple of times, and your products become too expensive and people will go to your competitors... ;-)

Instead, I'd create a read-only property like PriceWithVat, and leave Price alone, like this:

public decimal Price
{
  get
  {
      return price;
  }
  set
  {
    price = value;
  }
}

public decimal PriceWithVat
{
  get
  {
    if (vatqualifying)
    {
      return price * Convert.ToDecimal(WebConfigurationManager.AppSettings["VATFactor"]);
    }
    else
    {
      return price;
    }
  }
}

Hope this helps,

Imar
On Tuesday, April 17, 2007 7:51:55 AM Edmund Ward said:
Hi Imar

Ah, yes, I see what you're saying. The problem with this approach is that I will need to get the users to add/amend prices net of VAT, but possibly view them inclusive of VAT, which might cause confusion. I guess clear labelling is required. Also, this is not really related to the layered model design.

Thanks for your help.
Edmund
On Tuesday, April 17, 2007 11:37:42 AM Edmund Ward said:
Hi Imar

As a result of trying to build my app using your model, I keep coming up against questions. So apologies if you're getting tired of these, but here's another one.

I'm building an app for managing used cars. So originally I had a vehicle object and a transaction object, because that is how I expect to store the data in the database. Vehicles might appear in the system as more than one transaction. Vehicle data is stuff like registration plate, VIN, Make, Model, Derivative, etc. Transaction data is stuff like purchase price, value, selling price, mileage, date into stock, date out of stock, delivery date, etc.

When I come to create the presentation layer, however, I'm really mostly interested in Transactions, but will need to have vehicle data to hand too. Would it make sense, therefore, to have a VehicleTransaction object in my BO layer rather than separate Vehicle and Transaction objects? If I have separate objects, how will I bind the vehicle and transaction data to the same control? Or am I barking up the wrong tree here?

Cheers
Edmund
On Tuesday, April 17, 2007 6:11:46 PM Imar Spaanjaars said:
Hi Edmund,

Yeah, you could make an intermediate object like VehicleTransaction that acts as the bridge between the two objects. It's important to realize that, unlike relational databases, it's OK to duplicate data in OO objects, as long as the underlying data source stores normalized data. With OO designs like this, it's important to normalize behavior, not necessarily data.

However, you could also create a property of one type on the other, e.g.:

myVehicle.Transaction.Price

for example.

But that all depends on your design and intentions. Hard for me to judge over a few comments on this article.... ;-)

Imar
On Wednesday, April 18, 2007 6:59:53 AM Archie said:
Hi Imar
          you added collection classes for each business objects using *List T
i'm just wonderin if it would be more effective to use *BindingList T for databinding purposes.Do you think this matters?

I'm actually new here, read that somewhere, just trying to get some insights.

*apparently angel brackets are not allowed

thanks,

Archie
On Wednesday, April 18, 2007 8:09:01 AM Edmund Ward said:
Hi Imar

Thanks for your reply. With an intermediate class, would it be independant of the vehicle and transaction classes (i.e. would I need to create a completely new set of private variables and public properties for the intermediate class that encompases a combination of those of the other two classes, and also have its own BLL and DAL classes?) or would it have properties that somehow use the other classes?

If I went down the other route, having one object as the property of the other (myVehicle.Transaction.Price), would I have to use templates in my presentation layer to specify the properties of the transaction object? I assume that when the ODS hands a list to the GridView, the GridView will only display the vehicle properties. Perhaps I'm wrong, although it doesn't matter too much because I'm intending to use explicit columns anyway to gain control of the column headings.

Your comments are much appreciated.
Cheers
Edmund
On Wednesday, April 18, 2007 9:18:58 AM Imar Spaanjaars said:
Hi Edmund,

Yeah, you would probably need to write separate DAL classes.

And indeed: controls like the GridView can't handle nested objects really well.

Imar
On Wednesday, April 18, 2007 6:34:17 PM Imar Spaanjaars said:
Hi Archie,

Yes, you could do that too. However, BindingList is more useful in a Windows Forms scenario, while the main topic of this series was Web forms. If your BLL needs to support both environments, BindingList is probably a better choice.

Imar
On Thursday, April 19, 2007 9:43:24 AM archie said:
Hi Imar
    Thanks for your reply,this series has really moved me a great deal to develop a layered application with your approach/idea (i. e. the use of custom business objects and generics).am currently working on a project now and i've decided to separate the layers in class libraries for scalability. I'm kinda confused on how i'd put up the DAL specifically the way it retrieves the connection string.Unlike the project in this example where there's a web.config to store such info and can be retrieve using "AppConfiguration.ConnectionString", though i could add a connection parameter (i.e GetItem(int ID,string sConn) is there other implementation for this, so i could maintain the signature of the GetItem(int ID) method and makes it look "cleaner"?
On Thursday, April 19, 2007 8:08:02 PM Imar Spaanjaars said:
Hi archie,

You could create a static property in the DAL layer that looks in the configuration file just as in my examples.

The location where the DAL is used determines where the connection string is searched for. So, if you use Dal.dll in a web application, it looks in the current web.config by default. For an application, it looks in YourExe.exe.config.

HtH,

Imar
On Tuesday, April 24, 2007 4:14:27 AM csharpdev said:
Regarding the contactPerson delete functionality you stated that you did not need to use an ADO transaction, and that:

"Since all the code in a stored procedure already automatically runs inside a transaction, there's no need to wrap this code in a TransactionScope object. Either all records are deleted, or none of them are."

Thats not right is it? Unless you use ADO's transactions, you would have to explicitly use T-SQL transactions (BEGIN TRANSACTION ROLLBACK, COMMIT, etc.) in order to implement  transactions in a stored procedures right? There is certainly no automatic transactions in SP's in my SQL Servers that I am aware of...is this a setting that I can toggle on?
On Tuesday, April 24, 2007 5:09:19 PM Adnan said:
hi Imar,
Its a realy good & clean article.

I have question about business objects, how we put validation rules for business objects, like if FirstName can have maximum 50 characters, and so on for other properties, i know we have validation controls on GUI for better interaction with user, but will it be better idea that we check bussiness object valid data via some rules, and where to put other business logics in BLL like i have scenrio, a user send me a document with a barcode, if the barcode is valid i want to route this mail a success message using success template and same in case of failure with different kind of failure.

In short my questions are:
Where we should put business objects validation in the architecture?
And how we can display error messages to the user based on if validation fails (Some sort of generic way to handle this)?
Where we should put Business Logic in the architecture ?

Many Thanks & Best Regards,
adnan
On Tuesday, April 24, 2007 5:41:21 PM Imar Spaanjaars said:
Hi Adnan,

I mentioned a Validate method in this article, and gave an example on how you can implement and call it.

For a much more detailed implementation of validation in your business objects, check out the CSLA framework by Rockford Lhotka: http://www.lhotka.net/

Cheers,

Imar
On Tuesday, April 24, 2007 6:19:24 PM Imar Spaanjaars said:
Hi csharpdev,

You are absolutely right. When I initially wrote this, the stored procedure contained only a single DELETE statement. A single statement like that runs in a transaction automatically, so if you can delete record 1 and 2, but not 3, everything is rolled back. I then later updated the procedure, forgot to add transaction code to the stored procedure and update the article :-( Big mistake.

I have updated the database that comes with this article as a download. The procedure now looks like this:

BEGIN TRAN

DELETE FROM
Address
WHERE
ContactPersonId = @id

IF @@ERROR <> 0
BEGIN
ROLLBACK TRAN
RETURN -1
END

DELETE FROM
EmailAddress
WHERE
ContactPersonId = @id

IF @@ERROR <> 0
BEGIN
ROLLBACK TRAN
RETURN -1
END

DELETE FROM
PhoneNumber
WHERE
ContactPersonId = @id

IF @@ERROR <> 0
BEGIN
ROLLBACK TRAN
RETURN -1
END

DELETE FROM
ContactPerson
WHERE
Id = @id

IF @@ERROR <> 0
BEGIN
ROLLBACK TRAN
RETURN -1
END

COMMIT TRAN

After each DELETE statement @@ERROR is checked, and if there is an error, the transaction will be rolled back immediately.

I haven't changed the calling code that now has to understand what -1 means which is returned from the sproc in case of an error, but I'll leave that to implementors of this code.

BTW: In SQL Server 2005 you could also use Try/Catch blocks to do pretty much the same, but with a bit cleaner code.


Thanks for catching this; much appreciated. I'll try to update the article ASAP.

Cheers,

Imar
On Tuesday, April 24, 2007 6:34:09 PM csharpdev said:
Ok, I thought so, but the rest of your articles are often so good I read that and thought maybe there was some setting I wasn't aware of in SQL Server that would automatically run all SP's as transactions, which might be great for some databases.

TRY CATCH looks interesting but it is only in SQL Server 2005. I might use it in the future. Sometimes I resist things that abstract us from whats going on under the hood but offers very little in return, besides maybe slightly cleaner code, though I might use it in the future.

Thanks for the articles, I think they are a great contribution to the .NET community. I already have Lhotkas book and I've read parts of it. It's really for projects that are of much larger scale than I work with, but interesting nonetheless. The patters you present here is really more in line with the kind of work I do on a daily basis.
On Thursday, April 26, 2007 2:24:22 PM Edmund Ward said:
Hi Imar

Thanks for your replies so far. Next question!! I want to add some AJAX CascadingDropDown control extensions to my application. Whereabouts in your model, would you put the web service? Also the WalkThrough on the ajax.asp.net website suggests using TableAdapters to get the data for the drop downs. These TableAdapters are set up in the App_Code directory. Is this the way to do it for an n-tier model? Is there another way using the business and data layers you have described here? Hope this isn't too far off topic, but I understand your answers better than anyone else's :-o
On Thursday, April 26, 2007 4:58:25 PM Imar Spaanjaars said:
Hi Edmund,

Yeah, it's a bit too off-topic for me to answer in detail.

Bottom line is: it's not much different in this case. Put the webservice in the web site and have it call the appropriate methods in the Business Layer.

Cheers,

Imar
On Friday, April 27, 2007 9:45:54 PM Suzan said:
Hi Imar,  
Thank you very much for your article.  Your article is great and it really helps.  But I have a problem and maybe you can help me.  I have a form with a gridview and it displays all employees until you enter the employee number.  Below is EmployeeManager class with 2 public methods. When I ran this code, I got an error message "Cannot implicily convert type 'EHPersonnel.PeLog.BO.EmployeeList' to 'EHPersonnel.PeLog.BO.Employees' Is there any way to call EmployeeList GetList() method from Employees GetEmployeeByEmplID method?  Thanks for your assistance in advance.
Suzan


public class EmployeeManager
    {

        [DataObjectMethod(DataObjectMethodType.Select, true)]
        public static EmployeesList GetList()
        {
            return EmployeeDB.GetList();
        }              
        
        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public static Employees GetEmployeeByEmpID(string Empl_ID)
        {

            if (string.IsNullOrEmpty(Empl_ID))
                return GetList();
                
            else
                return EmployeeDB.GetEmployeeByEmpID(Empl_ID);
                    
        }
    }
On Saturday, April 28, 2007 8:36:17 AM Imar Spaanjaars said:
Hi Suzan,

You're mixing up the collection and the individual items.

You're GetEmployeeByEmpID is supposed to get a single employee; however when Empl_ID is null or empty, you try to return a list.

Depending on your requirements, there are two ways to fix it:

1. Have GetEmployeeByEmpID return an Employee only; when Empl_ID is null or empty, simply return null.

2. Have GetEmployeeByEmpID return a collection of employees. When Empl_ID is null or empty, return the list. Otherwise, return a list where the first item in the list is the Employee you were looking for.

I think I'd opt for number two; as the method is more consistent; you always return a list, that sometimes only contains a single instance.

However, you could also redesign your app a little and call separate methods: GetEmployeeByEmpID and GetList, depending on the state of the application.

Hope this helps,


Cheers,

Imar
On Monday, April 30, 2007 7:23:17 AM Anwer Baig said:
Why u didnt use datasets, to store record set and directly bind those datsets to control like, Gridview, one more thing, is there any harm to use DAAB (Data Access Application Block) to make the DAL.

Regards

Anwer
On Monday, April 30, 2007 11:35:56 AM Imar Spaanjaars said:
Hi Anwer,

I am not using DataSets for the same reason I am not using TableAdapters. Check out part one for a discussion on this topic.
IMO, DataSets tend to break the n-tier model and bind the data layer to the presentation layer directly.

And yes, you can use DAAB to build your DAL.

Cheers,

Imar
On Monday, April 30, 2007 6:49:25 PM Suzan Cha said:
Hi Imar,
This is in response to your answer.  You are right.  When I changed my application little bit creating another list that takes a parameter that returns when it's null or empty and it works fine.  Thank you very much.

Suzan
On Tuesday, May 01, 2007 4:53:09 AM Kurt Gooding said:
Imar.. You genius!

Thank you ever so much for providing such an extensible tutorial.

I have a question which I have a feeling you might be able to clarify for me.

I have created a few validation nmethos with the manager classes, those such as bool CheckDuplicateEmail(EmailAddress myEmail); which simply checks the DB to see if it exists.

I am calling this both from the Presentation layer (A button a user can click to check the availibility of the email) and within the manager class (when a new contact person is being created).

The problem I am facing which I am hoping you can clarify is if I am creating a ContactPerson and the CheckDuplicateEmail returns false (email already in use) how would I display this to the user?

The same goes for users without permission when passing the Context.User and they don't have permission, how do I pass an error to the presentation layer?

Throwing exceptions seems ugly to me, is returning certain values a clean way? like "-777" and then perform the check in the presentation layer. Or do I simply call the CheckDuplicateEmail in the presentation layer?

Once again thank you!

Kurt
On Tuesday, May 01, 2007 5:04:05 PM Imar Spaanjaars said:
Hi Suzan,

Glad it's all working now.

Cheers,

Imar
On Tuesday, May 01, 2007 5:09:20 PM Imar Spaanjaars said:
Hi Kurt,

Glad you like my articles!!

What you could is have Validate return some kind of status, in the form of an enum. E.g.:

public enum DataOperationStatus
{
  Success,
  DuplicateRecord,
  ValidationErrror,
  NotAllowed,
  AndSoOnAndSoOn
}

Then have your method return this enum instead of a bool to indicate success. IMO, this is much cleaner than returning magic numbers like -777 which are hard to read and understand.

Your BO could expose a collection of "broken rules". That's how the CSLA framework from Rockford Lhotka does it. When IsValid on the object returns false, you can look at the BrokenRules collection to see what's wrong.

How you implement this, is up to you. BrokenRules could be a simple string collection that you can easily bind to the UI to inform the user what's wrong. Alternatively, it could be a collection of BrokenRule objects that provide more information, like error codes and descriptions.

Hope this helps,

Imar
On Tuesday, May 01, 2007 6:18:21 PM Kurt Gooding said:
Music to my ears!

I actually started to add a property to the class itself then checking this.. but an enum in this case would definately be a better choice.

So thank you yet again this will work perfect.

Kurt

PS. I noticed you do not have a donations link anywhare.
On Tuesday, May 01, 2007 8:38:28 PM Imar Spaanjaars said:
Hi Kurt,

You're right, I don't have a Donation link anywhere. It's a good idea though... I try to make some money from ads on this site, but it's not working very well...

I do have an Amazon Wish Lists for anyone who wants to show his or her appreciation.... ;-)

Cheers,

Imar
On Thursday, May 03, 2007 3:52:36 AM Przemek said:
Imar,
I've taken the harder road and I've been trying to learn this N-Layer thing using the nullable type. In one of your answers earlier you mentioned that when using the nullable type, instead of the -1, you have to now use the value and hasValue properties.
Can you show where and how you would modify areas in your code to take this into account.
I've been stuck on this for so long that my next step is going to be to go to doing things the -1 way. I really don't want to do that.

Thank you.
On Thursday, May 03, 2007 6:11:19 AM Imar Spaanjaars said:
Hi Przemek,

Take a look here:

http://msdn2.microsoft.com/en-us/library/2cf62fcy(vs.80).aspx

It describes how to declare, use and box nullable types. If you have specific questions, I'll be happy to answer them here. Googling for "using nullable types C#" should bring you more useful articles.

However, it's almost impossible for me to post a code solution like the one you're requesting in a comment on this article, so if you need more help, may I suggest you post this in a forum so it's easier to discuss?

Cheers,

Imar


On Thursday, May 03, 2007 6:16:42 AM Przemek said:
Imar

Thanks, will do.

PS
Clicked on your ads a couple times to get you some dough ;-)
On Thursday, May 03, 2007 6:32:31 PM Suzan Cha said:
Hi Imar,  Thanks for your help and I have another problem with DELETE method.  I have 4 parameters to pass the delete method.  When I click delete button link, it only reads first param corretly and the rest are either 1 or null which is wrong.  Here are the methods.  Thanks for your help in advance.
Suzan

for DAL

public static bool Delete(string Empl_ID, int RatingYear, int RatingMonth, string PEType)
        {

            int result = 0;
            using (SqlConnection myConnection = new SqlConnection(_ConnectionString))
            {
                SqlCommand myCommand = new SqlCommand("Admin.sp_DeletePESingleItem", myConnection);
                myCommand.CommandType = CommandType.StoredProcedure;
                myCommand.Parameters.AddWithValue("@Empl_ID", Empl_ID);
                myCommand.Parameters.AddWithValue("@RatingYear", RatingYear);
                myCommand.Parameters.AddWithValue("@RatingMonth", RatingMonth);
                myCommand.Parameters.AddWithValue("@PEType", PEType);

                myConnection.Open();
                result = myCommand.ExecuteNonQuery();
                myConnection.Close();

            }
            return result > 0;
        }

Here is my BLL


[DataObjectMethod(DataObjectMethodType.Delete, true)]
        public static bool Delete(PeLogs myPELogs)
        {
            return PeLogDB.Delete(myPELogs.Empl_ID, myPELogs.RatingYear, myPELogs.RatingMonth, myPELogs.PEType);
        }
On Thursday, May 03, 2007 8:00:35 PM Imar Spaanjaars said:
Hi Suzan,

Hard for me to tell without seeing the rest of the code, like the controls in the ASPX page.

May I suggest you post this on a forum like http://p2p.wrox.com? This is a bit too off-topic for me to investigate and fix for you.

Sorry,

Imar
On Friday, May 04, 2007 8:22:35 AM Edmund Ward said:
Hi Imar

I am really pleased with my application now, thanks to your brilliant help. Thank you for your generous time and attention. I have yet another question, however. I want to add an error notification element to my application to let me (or some admin group, say) know when something goes wrong. Say I want to add an email notification element, which layer would actually contain the code to send the email, and which layer would initiate an email send operation? If, for example, there's a database fault in the DAL which causes an exception to bubble up through the layers to the presentation layer, allowing a friendly message to be displayed to the user, the notification mechanism could be started at any layer as part of the exception handling. Is there an accepted wisdom on where this happens? And where would you put the method that actually does the work?

Many thanks for your help.

Cheers
Edmund
On Friday, May 04, 2007 5:35:45 PM Imar Spaanjaars said:
Hi there,

There are a number of ways to do this. One common way is to handle all errors in the web application. Simply throw errors when appropriate in the Bll and Dal layers, and catch them globally in the Global.asax file using Appliction_Error. My book ASP.NET 2.0 Instant Results discusses this in more details.

Alternatively, you may want to Google for Elmah

Cheers,

Imar
On Friday, May 11, 2007 11:13:09 PM Suzan said:
Thanks for your help and I have another question.  For Save method, your sample code returns one int value to matching the record.  However my database table has 4 fields to make unique ID.  Can you help me out?  Thanks...
On Saturday, May 12, 2007 8:02:11 AM Imar Spaanjaars said:
Hi Suzan,

You can choose whatever you want. You can return a bool to indicate success or failure, you could return the entire object itself, or you could create a Key object that you create yourself. This (composite) Key class simply contains properties for all your unique columns that you assign to in the Save method. Calling code can then access those properties after they called Save.

Imar
On Friday, May 18, 2007 6:05:58 PM Rob said:
Does the below statement mean that every BO should have a field/property of type BrokenRulesList?  And, after every operation of a BO, you should check to see if BrokenRulesList is DataOperationStatus.Success from the Enum?

On Tuesday 5/1/2007 7:09:20 PM Imar Spaanjaars said:
....
"Your BO could expose a collection of "broken rules". That's how the CSLA framework from Rockford Lhotka does it. When IsValid on the object returns false, you can look at the BrokenRules collection to see what's wrong"
....
Thanks,
Rob
On Saturday, May 19, 2007 4:32:56 AM Przemek said:
Imar,

I've did some reading up on nullable types, like you suggested but I still don't understand what is going wrong.
Here is a specific example:
I get a runtime error when my app hits this line of code:
myThumbnail.Gender = (GenderType)myDataRecord.GetInt32(myDataRecord.GetOrdinal("GenderType"));
Gender is an enum that I created and has various values. The runtime error says that the 'specfied cast is not valid'. The value that is coming fromt he database is the number 1.
I do not understand why it is not letting me cast?

Thanks.
On Saturday, May 19, 2007 7:49:43 AM Imar Spaanjaars said:
Hi Przemek ,

The problem with this code is not the enum or the nullable type, but the way you get it from the database:

myDataRecord.GetInt32(myDataRecord.GetOrdinal("GenderType"))

When GenderType is null in the database, you'll get an error when you convert it to an Int32. You'll need to check for DBNull.Value, just as I did with FillDataRecord for the ContactPerson the application.

Cheers,

Imar
On Saturday, May 19, 2007 7:55:00 AM Imar Spaanjaars said:
Hi Rob,

Yes, that's how it's done in the CSLA framework. Method like Saves throw an exception when the object is not valid, but you can check the IsValid property before you try to save the object. Then you can use the BrokenRules collection to thell the user what went wrong.

Imar
On Monday, May 21, 2007 6:45:50 PM Rob said:
Do any of the 12 examples in you book use the CSLA framework?

Thanks again.
On Monday, May 21, 2007 6:47:46 PM Imar Spaanjaars said:
Hi Rob,

Nope, none of them use it. However Rockford Lothka has written a few books about it, as it's his framework.

Imar
On Monday, May 21, 2007 6:52:21 PM Imar Spaanjaars said:
BTW: it's important to understand that the CSLA framework uses a lot of inheritance where behavior like this is all implemented in base classes in the framework. You don't have to implement a whole lot in your own BOs to make use of features like BrokenRules.

Imar
On Sunday, May 27, 2007 11:56:55 PM Przemek said:
Ref: Saturday 5/19/2007 9:49:43 AM Imar Spaanjaars

I do understand what you mean; however, there is a value in the database for this column, it's 1. I was wondering if perhaps the order of the columns in my SELECT has anything to do with it? Or as long as the name that I use, in this case "GenderType" is the same as the name of the column name then I'm ok?
I've tried to use the debugger to see what values I'm actually getting returned here but the debugger has like 50 levels to it and it is difficult for me to figure out exaclty what the values are.
Please advise, I'll keep digging.
Thanks.

ShAm
On Monday, May 28, 2007 12:46:23 AM Przemek said:
Imar,
Well I figured it out, basically a shot in the dark and it worked. Apparently the datatype in the database has to be 'int', in CANNOT be 'tinyint'. I thought I would save some room in the database by using tinyint but this won't work.
On Monday, May 28, 2007 8:25:52 AM Imar Spaanjaars said:
Hi Przemek,

Great; glad it's working.... Thanks for the update.

Imar
On Tuesday, June 05, 2007 4:20:06 AM sudhir rawat said:
Hi Imar.

great article imar, thanks for writing article on N-layer.
in the future please write article on OOPS in ASP.net 2.0 web application.
means how to extensivly use OOPS while develping web application..

Regardss,
Sudhir
On Friday, June 15, 2007 2:46:03 AM Clever Almeida said:
Dear Imar,

I liked very much your articles. I intend to use for my project.
Do you know any code geration application that create these layers from database to C# (BOL, BLL and DAL) ?

Thanks

Clever
On Friday, June 15, 2007 5:30:28 AM Imar Spaanjaars said:
Hi Clever,

There are a number of tools you can use for this like myGeneration, CodeSmith and llblgen.

I don't have any ready-made templates for these tools (I use an in-house code generator for these kind of applications). However, if anyone has suitable templates, I'll be more than happy to host them here.

Cheers,

Imar
On Saturday, June 23, 2007 3:45:01 PM tomas said:
Hi Imar

Nice article but there is one thing I am concern about: -> THREAD-SAFE
Is this design thread-safe? For example: what if 100 unique people call method "EmailAddressManager.GetItem(id)" at the same time?
Can I count on that method that will return correct data for each client?

Thanks for answer
Tomas
On Saturday, June 23, 2007 3:50:47 PM Imar Spaanjaars said:
Hi tomas,

Yes, you can. Although the methods are static, they create method-scoped variables and return instances of the objects and collections. So, each call has its own object / collection to work with.

Cheers,

Imar
On Friday, June 29, 2007 11:57:20 AM Dave Poleon said:
Hi Imar

This is the best article I have seen about layered web apps by a long way!. I am using the structure described to build my own signature management application.

In the data access layer you use IDataRecord to hold a record from the database. One of the fields I am using has an image data type. Which IDataRecord type should I use i.e GetByte, GetData and in the corresponding class what type should that property be, I have tried Object but I can't get the casting right.

I would be very grateful for any help you can give me.

Thanks!
On Friday, June 29, 2007 5:24:36 PM Imar Spaanjaars said:
Hi Dave,

Glad you like these articles so much. Spread the word, spread the word!!

Not sure if any one the Get* methods would work as well, but you can also take the column's content directly, like this:

fileData = (byte[])myReader[myReader.GetOrdinal("FileData")];

In this case, myReader is an SqlDataReader but this should work equally well for an IDataRecord (which is essentially the record that the reader exposes).

For a more detailed example, check out this article: http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=414

Cheers,

Imar
On Monday, July 09, 2007 6:47:41 AM Muhammad Zeeshanuddin Khan said:
Hello,

I want to ask 2 questions regarding Quick Doc Id 419.

Reagarding this Stored Procedure "sprocContactPersonDeleteSingleItem" I am not understanding why did you delete the records from child table manually. Is the better way to deal with it to use CASCADE DELETE OPERATION. Please tell me what is the difference.

Reagarding this Stored Procedure "sprocEmailAddressInsertUpdateSingleItem" you combine Insert and Update operation in a single proc. But you don't provide the detail that why you do this. And if we separate these then what performance loss or benifit get. Please provide the detail regarding this.

Waiting for your quickest reply. Because I am working on my project based on your article.

Regards

Muhammad Zeeshanuddin Khan
On Monday, July 09, 2007 3:35:25 PM Imar Spaanjaars said:
Hi Muhammad,

Cascading deletes would have worked equally well in this situation. However, I personally don't like them too much. I am much rather in control, enabling me to delete what I want and when I want it. I could, for example, keep certain data for "time travelling scenarios" (e.g. the state things were in some time ago) or I might want to keep it for other purposes.

There shouldn't be a diffeence in performance if you have one or two stored procedures for inserts and updates. I prefer to have only one, as it makes writing the update code (e.e. Save() or Update()) a little easier. Additionally, the code is easier to update; if you add or remove a column, you only need to do it in one place.

Cheers,

Imar
On Thursday, July 19, 2007 7:03:38 AM Muhammad Zeeshanuddin Khan said:
Hi Imar,

You write above that we can easily change the database provider to Access or else.

But can I ask you that what about Stored Procedures.

Because as I know Stored Procedures are not for Access.

And by the Triggers which you don't use in this. But If we use then what will do for them.

Do you understand?

Waiting for your reply.

Regards

Muhammad Zeeshanuddin Khan
On Thursday, July 19, 2007 7:30:01 AM Imar Spaanjaars said:
Hi Muhammad,

Access supports some kind of stored procedures in the form of Queries. Personally, I find them a bit awkward to work with, and quite limiting in what they can do. If you're using Access, you're much better off using in-line SQL statements in the DAL layer.

Same goes for triggers; if you need them, Access is not the right choice of DBMS.

Imar
On Wednesday, July 25, 2007 7:26:11 AM Muhammad Zeeshanuddin Khan said:
Hi Imar,

I have a question.

In this scenario or in any other one. When the data grows up. And paging is applied. Then you need the data for the current page.

In this condition some methods are applying by differnt programmers.
Some treat them in Stored Procedures to create temp table.
Some call the whole data and when put them in dataset then take the data needed.

Which you prefer. And any resource regarding this.

Regards

Muhammad Zeeshanuddin Khan
On Wednesday, July 25, 2007 7:32:28 AM Imar Spaanjaars said:
Hi Muhammad,

I prefer to use stored procedure paging using temp tables or the ROW_NUMBER function....

Search Google for loads of examples of this.

Imar
On Friday, July 27, 2007 5:10:53 AM Muhammad Zeeshanuddin Khan said:
Hi Imar

Would you tell me about this method

public static bool Delete(ContactPerson myContactPerson)
{
  return ContactPersonDB.Delete(myContactPerson.Id);
}  


you take the ContactPerson Parameter in delete method
and provide myContactPerson.Id to ContactPersonDB.Delete

What is the reason behind this.

Because you can easily put myContactPerson.Id as parameter of ContactPerson

I think this is not out off the topic...............

Cheers

Muhammad Zeeshanuddin Khan
On Friday, July 27, 2007 6:41:50 AM Imar Spaanjaars said:
Hi again,

At the database level, deleting an item by id is fine, as that's the primary ID for the record in the database. So, all you need to do is pass the ID to the Delete method in the Dal. However, in the business layer, you may need to perform other tasks based on the entire object. That's why I usually have a few overloads of Delete in the Bll. The one you saw in the code and this one:

public static bool Delete(int id)
{
  return ContactPersonDB.Delete(id);
}

With both methods in place, calling code can choose whether they simply pass the ID, or an entire business object. Within the Bll, you can choose what overload calls the other, depending on your requirements.

Cheers,

Imar
On Monday, August 06, 2007 10:33:07 AM Muhammad Zeeshanuddin Khan said:
Hello Imar,

Business Object (OB) is very useful. I have a question regarding it.

For example you have BO which has 5 Fields.

In some conditions all are useful but in some conditions some fields are useless.

In those circumstances what will we do?

Either leave the null or empty if this is true then what about there stored procedures. Eithe we create many stored procedures for specific fields.

Can you help me reagarding this.

Muhammad Zeeshanuddin Khan
On Monday, August 06, 2007 10:48:03 AM Imar Spaanjaars said:
Hi Muhammad,

Leaving them empty is odd, IMO. You should load the full object, as another developer may not realize he only gets half an object. You could create light objects, like a *Summary or a *Info object with less fields.

Linq may solve some of these issues, as it allows you to create new anonymous types on the fy.

Imar
On Monday, August 06, 2007 11:06:02 AM Muhammad Zeeshanuddin Khan said:
Hi Imar,

I have another question. Hope you will reply.

For Example we have 3 tables
-------------
Language
-------------
Id
Name
-------------

-------------
Service
-------------
Id
Name
-------------

--------------------------
LanguageService
--------------------------
ServiceId
LanguageId
--------------------------

In this condition will we make 3 Business Objects.

Or

Make 2 Business Objects and use LanguageService Table withihn Language or Service Table

Do you get?

If you found this is out off topic. Then you have the right to delete. But if you know please reply. This will really helpful for me at current condition.

Muhammad Zeeshanuddin Khan
On Monday, August 06, 2007 12:28:21 PM Imar Spaanjaars said:
Hi Muhammad,

I am not sure if it's too off-topic. However, I am not your free consultant. I wrote this artice series to help developers like you understand the basic principles of n-layer design. From there, you'll need to experiment yourself, try things out, and get a feel for what you want and what works for you.

You can always try to hire me: http://www.designit.nl

Imar
On Tuesday, August 07, 2007 4:18:15 AM Muhammad Zeeshanuddin Khan said:
Mr. Imar

I think you are really disturbed with my questions. That's why you are giving the replies in negative sense.

I read another comment on this article series in which you replied to others for some off topics.

like

Hi Richard,

Well, by creating a generic wrapper, you pass around generic objects. Yet, under the hood, these objects still stick to the XPO contract. They are, in fact, still truely strong typed objects, like Person or People.

So, in your business layer, you know you can call Save on the object and expect it to save correctly.

But of course, it's difficult for me to suggest something.  I don't know much about your setup, your object model and so on, so it's hard for me to suggest something that works best for you.


But If you don't want to question agian from you Ok That's fine I will not do it again.

I know you are not my free consultant. So why you write that article for free.
On Tuesday, August 07, 2007 5:56:47 AM Imar Spaanjaars said:
Hi Muhammad,

I am not sure I understand or appreciate your comment. I am not disturbed by your questions and I didn't give you a negative response; I simply don't have the time to answer them all in detail, especially if they touch on topics that go outside the scope of the article or are too specific to your own work and business case. That they are "helpful for [you] at [your] current condition" is great, but that doesn't make them my top priority. I have only so many hours in a day, and I hope you understand I can't spend them all on answering questions here.

I am also not sure why you quote a reply to Richard. What point are you try to make? That I reply to others in detail and not to you? That seems hardly fair. You have posted a number of questions here (probably the most of all), and each time I have given you a detailed response, trying to help you out by giving you useful suggestions. And in my response to Richard I said I couldn't suggest anything as I didn't know anything about his object model and setup.

Like I said, there is a time where you'll need to experiment yourself, read loads of other articles on the subject, try things out and see if they work for you. I can't possibly answer each and every question I receive through this web site or my private e-mail in detail.

And finally, I don't understand your remark about why I wrote the article. What does that have to do with being a free consultant? IMO, nothing. I wrote this article so you can read it, and use the concepts presented in it as you see fit. Nowhere in this article will you find a phrase that says that by reading the article you're entitled to free support on all topics related or not related to the ideas presented in the article.

You can read my articles for your own benefits. If you have a question, you can ask it here. If you're lucky, you get an answer. Just realize you're not entitled to free support on the articles from this web site.

I hope you understand my take at this. If you do, that's great. You're welcome to come here and ask questions which I'll try to answer if and when I have the time. If you don't understand: that's fine too. Just press the Back or Close button now....

Imar
On Wednesday, August 08, 2007 5:29:07 AM Muhammad Zeeshanuddin Khan said:
Hi Imar,

You close the connection in GetItem Methods but not in GetList Methods.

Can you tell me why?

I think it is not off-topic.

Muhammad Zeeshanuddin Khan
On Wednesday, August 08, 2007 5:40:54 AM Imar Spaanjaars said:
Hi Muhammad,

Good point. However, before I go into that, would you mind responding to my previous reply? As you can see from the length of it, I didn't agree with your reply, and I would like to know how you look at this.

BTW, no need to indicate whether it's off topic or not every time. Let me be the judge of that....

Imar
On Wednesday, August 08, 2007 6:27:26 AM Muhammad Zeeshanuddin Khan said:
Hi Imar

Basically I have belive in helping others.

But it is not neccessary that every one adobt it.

Everyone has the right to decide.

I read your comment and agree with you. And INSHALLAH AZZAWAJAL I will try my best to ask question related to the topic. And INSHALLAH AZZAWAJAL will not write off topic or not.

But If I ask any question not related to the article. You have the right to reply or not. I will appreciate both.

And forgive me if I have done any mistake. I have the beleive to accept my mistakes.

But what about my last question.

Happy Day And Night For You And All

Muhammad Zeeshanuddin Khan
On Wednesday, August 08, 2007 6:58:26 AM Imar Spaanjaars said:
Hi Muhammad,

You're not really answering my questions (why did you post a reply, why did you refer to writing and article with relation to not being a free consultant) but maybe I should give up. Apparently, you and I have different ideas about it. And maybe it's OK since you agree with me....

The missing call to Close() is probably a bug / omission in my code. There's no real reason for it. It's not a problem though, as the code is wrapped in a using block which closes the Connection at the end automatically.

Imar
On Wednesday, August 08, 2007 7:46:33 AM Muhammad Zeeshanuddin Khan said:
Hi Imar

Ok its not neccessary then why you put it on GetItem methods.

I think for both methods its not neccassary.

If you agree with it then please put some not on the article to inform devlopers that dont' close the connection.

Muhammad Zeeshanuddin Khan
On Wednesday, August 08, 2007 8:24:15 AM Imar Spaanjaars said:
Hi Muhammad,

Closing connections explicitly is always a good practice, so I'd rather add it to the other method than remove it from GetItem.


Imar
On Wednesday, August 08, 2007 9:54:36 AM Muhammad Zeeshanuddin Khan said:
Hi Again,

Sure Imar you are right.

But please mention this on updates. As user will get the idea.

Will you update it on the article?

Muhammad Zeeshanuddin Khan
On Wednesday, August 08, 2007 10:23:57 AM Imar Spaanjaars said:
No, I won't. It's in the comments now, isn't it?

Imar
On Wednesday, August 08, 2007 10:43:11 AM Muhammad Zeeshanuddin Khan said:
Hi Imar,

This is up to you. But I think most of people does not read the commetns. That's why its better to inform it on the top position.

Muhammad Zeeshanuddin Khan
On Tuesday, August 14, 2007 2:33:43 PM Edmund Ward said:
Thanks again for this article, Imar. I have a question about business objects. Obviously, if I want to view an individual object, I want to populate as many of the properties as possible and display them. If I want to create a report of a large number of these objects but with less detail per object, should I be creating a List<> of them? What happens to the large number of properties I'm not using in my report, and am therefore not initialising? Do they take up unnecessary memory? If so should I create a bespoke report row object with just the properties I want in the report?
On Tuesday, August 14, 2007 2:44:23 PM Imar Spaanjaars said:
Hi Edmund,

There are a couple of ways to solve this:

1. Load everything. This indeed impacts performance and memory consumption.

2. Use lazy loading. That is, postpone loading complex properties (lists etc) until you really need them.

3. Create light-weight objects, like a Report Info or Report Summary with just a subset of the data in the real object. The full object could then inherit from the light-weight object, or they could both implement the same interface.

4. Use Linq. The cool thing about Linq is that it lets you create new objects on the fly. So, you can easily query just a subset of relevant properties and end up with a light-weight object.
The downside of Linq is that it's still in beta and will be for some time.

Hope this gives you some ideas/

Imar
On Thursday, August 30, 2007 1:21:38 AM Diego said:
Hi Imar,
just a curiosity, the manager's methods are static, why is the manager class itself not static?
On Thursday, August 30, 2007 5:37:52 AM Imar Spaanjaars said:
Hi Diego,

See my comments of 7/17/2007 7:18:01 in Part 1 of this article; I could (should) have made the Manager classes static as well.

http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=416

Imar
On Friday, August 31, 2007 4:56:12 AM Muhammad Zeeshanuddin Khan said:
Hi Imar,

I have some difficulties on the Namespace Naming Conventions for Layered Application.

Can you suggest something regarding Layered Application.

Regards

Muhammad Zeeshanuddin Khan
On Friday, August 31, 2007 6:19:03 AM Imar Spaanjaars said:
Hi Muhammad,

I'll be more than happy to provide some tips. However, if I am not mistaken, you and I (mostly you) still have a few e-mails to answer to end another discussion (including a post on the Wrox forum). Would you care to respond to those messages first (by private e-mail)?

It seems quite odd to simply ignore that and continue to ask questions as if nothing happened.....

Imar
On Friday, August 31, 2007 6:58:57 AM Muhammad Zeeshanuddin Khan said:
Hi Imar,

Please send me the forum link which I have to answer and send the question again to my private email address.

I do not know what are your asking.
On Friday, August 31, 2007 1:07:27 PM s_a_200263 said:
Very Useful article,I wanted to use Ur article can u plz tell me what is Spaanjaars?!Can I use something else instead of Spaanjaars??!
On Friday, August 31, 2007 5:35:17 PM Imar Spaanjaars said:
Hi s_a_200263,

It's my last name. Feel free to substitute it with whatever seems reasonable to you.

Imar
On Thursday, September 27, 2007 1:12:05 AM cdfornal said:
Can anyone tell me what this c# code does?

[DataObjectMethod(DataObjectMethodType.Select, true)]

it is in  a couple of places in the manager classes

Thanks
On Thursday, September 27, 2007 4:52:30 AM Imar Spaanjaars said:
Hi cdfornal

This is explained in part 3 here:

http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=420

And for more detail about these attributes check out this article:

http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=390

Cheers,

Imar
On Thursday, September 27, 2007 7:06:43 PM cdfornal said:
Thanks Imar I will take a look, Keep up the great work here and on the Wrox forums :)
On Tuesday, November 06, 2007 9:52:51 AM Ian Taite said:
Thanks Imar for a great trio of articles. Just a minor comment, but when you discuss the type of child collections, e.g. EmailAddressList instead of List[EmailAddress], I think there might be a minor typo:

"Another benefit of the separate class is that it is easier to add additional methods or even change the underlying type of EmailAddress completely, to something like CollectionBase for example without breaking existing code."

should be:

"Another benefit of the separate class is that it is easier to add additional methods or even change the underlying type of EmailAddressList completely, to something like CollectionBase for example without breaking existing code."
On Tuesday, November 06, 2007 12:28:53 PM Ian Taite said:
Is it right that ContactPersonManager.Save invokes AddressDB.Save,  EmailAddressDB.Save and PhoneNumberDB.Save?

Surely ContactPersonManager.Save should instead invoke AddressManager.Save, EmailAddressManager.Save and PhoneNumberManager.Save.

As you advocate placing validation code in the *Manager classes, by allowing ContactPersonManager.Save to invoke AddressDB.Save,  EmailAddressDB.Save and PhoneNumberDB.Save directly, you bypass any validation logic that may be present in the AddressManager, EmailAddressManager and PhoneNumberManager classes.

As a rule, would it not be better to say that *Managers call on the services of other *Managers.

On Tuesday, November 06, 2007 2:13:16 PM Imar Spaanjaars said:
Hi Ian,

Yeah, that would be a lot cleaner.... Don't have the time to fix it now though.

I did fix the language mistake; thanks for bringing that to my attention.

Imar
On Monday, December 10, 2007 10:22:54 PM Anthony Tristan said:
Hello.  I have been digging into this article for an app I am developing and it has really made a huge difference in the extensibility of the app I'm working on.  Thanks Imar!  The question I have is as follows:

I am using the following line of code in my FillDataRecord method:
If Not tblsDataRecord.IsDBNull(Convert.ToInt32(tblsDataRecord.GetOrdinal("AddedBy"))) Then
                tblsCompany.AddedBy = tblsDataRecord.GetString(tblsDataRecord.GetOrdinal("AddedBy"))
            End If

This is not a required field so there will be instances where a null will be returned.  I've been pretty much following your examples but I end up with the following error:
System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.String'.

Any ideas on how to resolve this would be greatly appreciated :-).
On Tuesday, December 11, 2007 7:57:08 AM Imar Spaanjaars said:
Hi Anthony,

Glad you like the aricles....

If this is the code that's causing the erorr, then I assume that tblsCompany.AddedBy is an Integer and not a String.

Also, in the If check, you can't convert to an Integer with Convert.ToInt32. The whole point of the check for DBNull is to see if the value is null as you'll get an error when it is and you try to convert to an Integer or another data type.

Cheers,

Imar
On Tuesday, December 11, 2007 2:05:34 PM Anthony Tristan said:
Thanks again Imar for the quick response.  I really appreciate it.  In reference to the above code, you are right, there was a type-o in my code.  Here is what it should have looked like:



If Not tblsDataRecord.IsDBNull(tblsDataRecord.GetString(tblsDataRecord.GetOrdinal("AddedBy"))) Then
                tblsCompany.AddedBy = tblsDataRecord.GetString(tblsDataRecord.GetOrdinal("AddedBy"))
            End If



AddedBy is an nvarchar(155) in my SQL dbase so it should be returning a string.  This code above still generates the exception and I can't seem to figure out why.  As always, your help is greatly appreciated.
On Tuesday, December 11, 2007 2:14:43 PM Imar Spaanjaars said:
Hi Anthony,

You need to remove the conversion to a String from the If check that gets the zero based index of the column. Currently you convert it to a String before you use it as input for the IsDBNull check. Look at my original source from this article to see what I mean:

if (!myDataRecord.IsDBNull(myDataRecord.GetOrdinal("ZipCode")))

No conversion to a string; just a Null check on the column's index.....

Imar
On Tuesday, December 11, 2007 3:11:40 PM Anthony Tristan said:
Thanks Imar.  I followed your suggestions, reviewed your code and made some changes.  I am still getting the casting error.  Here is the message I get back from the asp.net error message:

<snip code cut by imar />

I hate to be a bother, but this is really bugging me, :-).  Thanks.
On Tuesday, December 11, 2007 3:16:55 PM Imar Spaanjaars said:
Hi Anthony,

Can you please take this to a forum like p2p.wrox.com? It's getting a bit too long and too much for me to support over comment posts like this. If you do post there, don't forget to post the error message which you left out in your previous post. It'll be much easier to post more code, error messages etc and get proper help there.

Cheers,

Imar
On Tuesday, December 11, 2007 3:52:58 PM Anthony Tristan said:
FYI...in case anyone else runs into this issue, it was a problem with refreshing my dll's for my BLL, BOL and DAL.  In my project I opted to make the BLL, BOL and DAL separate projects within the same solution as the web page so my web pages some how were not in sync with my BLL, etc.  Deleting my references to those projects and re-referencing fixed the problem.

Thanks :-|
On Saturday, December 15, 2007 2:15:15 PM Imar Spaanjaars said:
Hi Anthony,

Glad you got it working....

Cheers,

Imar
On Tuesday, December 25, 2007 4:53:42 PM guaneme said:
Great article Imar!

However, there a small issue regarding the Upsart/Delete functionality in the ContactPersonManager I would like find out a better response(keeping in mind that "there are not silver bullets"). I think the Delete functionality should be written different because of the following problems:

1. Upsert: It is violating the DRY principle because we are making decisions in the DAL regarding which parameter value should be set and also we are making decisions in the database regarding which statement to call(Insert o Update). I believe this decision should be written "once and only once" in the BLL and decide which service(create/update) needs to be called from the DAL.
2. Delete: The idea behind the layers distinction also includes independence, for instance, we could have the Person information stored in one database and her Address information stored in a different database(and worse). In this scenario, having the delete procedure in the database could get complicated or even useless. I believe it should have the delete logic centralized in the BLL, handling the transaction and delegation of the deletion services offered in the DAL.

I'd really appreciate your comments and admire what you have posted, it's a priceless practical piece of knowledge.
On Monday, December 31, 2007 2:20:42 PM Imar Spaanjaars said:
Hi guaneme,

With regards to 1: I agree.
With regards to number 2: that was never a design goal of the ideas presented in this article. They should serve as a starting point on which you can build and extend.

With regards to both: the idea of this article series is to get you started. Feel free to disagree, extend where necessary, or change what you see fit. Your points are valid, but may not necessarily apply to every situation.

Cheers,

Imar
On Sunday, February 10, 2008 5:53:23 PM kurt schroeder said:
In the case where you need to validate that a userid is unique i would tend to use an SP that returns true when the intended userid exists for another user. The call for this would exist in the Business Logic class under Validate. My question is would the function to call the storedproc be created in DataAccess or should an other class be created for this sort of validation?  I'd like your opinion.

Thank you!
On Sunday, February 10, 2008 6:48:03 PM Imar Spaanjaars said:
Hi kurt,

That depends on whether you think you'll reuse it. Just as relational databases are about avoiding duplication of data, object orientation is about avoiding duplication of business logic. If you only need it once, by all means, call directly from your Validate method into your DAL method.
Alternatively, you may want to create a separate validation class.

Imar
On Sunday, February 10, 2008 7:57:39 PM kurt schroeder said:
Thank You Imar,
Your help is very appreciated. You have taught me more through this artical series than I thought possible in so short a time.

KES
On Sunday, February 10, 2008 8:44:43 PM kurt schroeder said:
Would it be too much to add a name space for validation? This would be referenced in the presentation layer and/or the Business logic layer depending upon the circumstance. This would not be a new tier, but i can see the benefit of keeping a series validation for general use in both these tiers. On in presentation layer they could be used to fire off valuator controls. In the BLL exceptions would be thrown.

Forgive a spontaneous thought i just had ......
1. I've thought of an error dumb class for each business object that could be returned for a single call to validate the object. A master switch say if the object is valid or not and a series of other switches for each property needing validation
or
2. Same idea except include the switch properties on the business object itself.

I am fairly new to this way of developing so i appreciate your thoughts on these ideas

Thanks again Imar

KES
On Sunday, February 10, 2008 9:43:16 PM Imar Spaanjaars said:
Hi KES,

A namespace seems overkill to me. Personally, I would put it in the Bll layer, as it's all about business logic.

Another option is to give each object a BrokenRules collection that tells you what business rules have been broken. The Validate method could populate the collection, or simply accessing it could do the same.

The CSLA framework, referenced in these articles, implements a BrokenRules collection to keep track of what is wrong.

Cheers,

Imar
On Saturday, March 08, 2008 1:37:55 AM abuiles said:
Hi, I ran your example, but when I tried to update the email address I got this error: Updating is not supported by ObjectDataSource 'odsAddresses' unless the UpdateMethod is specified
I don't know why this happened,,
I checked and the Update is specified..

So what can I do?

Thanks
On Saturday, March 08, 2008 8:18:23 AM Imar Spaanjaars said:
Hi abuiles,

Did you make any changes to the code? AFAIK, the code is supposed to run out of the box without any problems. You are also the first to report an issue like this.

Can you explain the steps you followed to get at this error in more detail?

Imar
On Saturday, March 08, 2008 2:04:25 PM abuiles said:
Hi, Imar, I fixed the problem, it was in the ods, but now is working. But a new little problem raised, when I selected email address, and then I do click in Show email address, I can´t see anything, I have been looking for the problem but i can´t find it,  That´s the unique thing that doesn´t work.  Save e-mail Is working..

Thanks For you reply

Abuiles
On Sunday, March 09, 2008 12:58:40 AM Abuilesj said:
Hi. I just did a copy&paste of the default.aspx, and now everything is working.

Thanks For your post, It  has been very useful.

Thanks!!!!!!!!!!!
On Tuesday, March 18, 2008 10:47:39 PM Vladimir Kelman said:
Hi Imar! I'm glad to tell you that thanks to your excellent article and my  persistence our group decided to use custom BO/Bll layer approach instead of Datasets.
A short question: you use
using (SqlConnection myConnection = new SqlConnection()) {} blocks, but also explicitly call myConnection.Close() inside those blocks. The same is true for
using (SqlDataReader myReader = myCommand.ExecuteReader()) {} blocks.
I was told that C# compiler internally expands "using" blocks into try... finally blocks and calls corresponded Dispose() object inside finally clause. Books also said that for connections, data readers, etc., (which implement IDisposable interface), Close() methods are identical (are aliases) to Dispose(). So, why would we call Close() if "using" block will call Dispose() anyway?

Another thing: sure, for long lists there should be paging mechanism implemented, as you said to Rumata above. But in my experience, we usually do not display all the detail data in lists. Instead we have read-only pages with HTML tables displaying only few most important fields for each object. Clicking on a particular item opens edit page which displays all the details of a chosen item. So, it looks like that in objects contained in those generic lists, we often need few fields/properties, while the same entity represented individually on an edit page has to have much more. It sounds like we would need two types of BO objects: "short" ones, to put into generic list, and "long" ones to use individually. Sure, we can use "long" ones everywhere, but  it will take more memory. What do you think?...
Oh, I found that you told to Muhammad at 8/6/2007 about using "light" or "summary" objects... that's pretty much what I meant. Does it make sense to inherit a "full" object from its "light" counterpart? This way if we later decide that we need some PropertyX to be displayed on a list, we can move it from "full" object to "light"... Still sounds strange... according to a rule of using inheritance to implement "is a" relationship and using containment to implement "has a" relationship, it may be better for full object to contain a light one...
Please don't take it like I'm trying to take your work for free. I know, we should figure these things out by ourselves. I just feel that this situation with "light" lists and "full" objects is a pretty common pattern, and people already found appropriate ways to deal with it.
Thanks. BTW, I bought your new book.
On Tuesday, March 18, 2008 10:58:28 PM Vladimir Kelman said:
Sorry, I found your answer about closing connections explicitly after I added my comments.

In ASP code I always closed connections explicitly, but I thing that with "using" blocks we in fact close them explicitly. "using" is expanded into "try ... catch" with Dispose() call inside. We are not relying on GC, generated Dispose() does close connection explicitly.
On Wednesday, March 19, 2008 6:23:49 PM Imar Spaanjaars said:
Hi Vladimir,

I agree; a *Summary is not a *, so inheritance seems slightly awkward.

However, IMO, it's not that bad to duplicate some properties in Summary objects. These objects will be dumb objects with no behavior, validation and so on. Just a bunch of read-only properties for data display.

Imar
On Thursday, March 20, 2008 4:32:18 PM Siraj said:
Great article. hats off to you for taking the pain of explaining it with sample application.

-Siraj
On Friday, March 21, 2008 8:19:39 PM MarkB said:
Imar,

I would like to reiterate the comments of many others that this is a great article.  Having set out on my first OO architected project, this was a huge help.  Thanks!

I have created my project with the basic framework of this article and built out 15 or 20 objects with it so far.  Today I ran FxCop on the project to see what kind of comments it might have.  One of the rules it flagged was this one:

http://msdn2.microsoft.com/en-us/ms182142.aspx

I've been reading articles on this to try to get a better handle on it - but the issue is not entirely clear to me yet.  I was wondering if you could provide your thoughts on when this should be a concern and when it shouldn't and, in those cases that it is a concern, what the alternatives would be.

Thanks again for a great article!
On Sunday, March 23, 2008 3:30:39 PM Imar Spaanjaars said:
Hi MarkB,

Funny that you mention this now. A colleague of mine warned me about it the other day after he ran FX Cop on a number of our assemblies. I think the fix is pretty easy: just inherit from Collection<T> instead of List<T> as the FX Cop page suggests, like this:

using System.Collections.ObjectModel;
public class ContactPersonList : Collection<ContactPerson>
{}

The Collection class has an interface similar to the generic List class, and includes methods and properties like Add, Remove and Count.

The only thing that is missing is a Sort method. However, you can easily write your own implementation in the class. The only caveat is that you need to ensure that the underlying list of your collection is actually a List<T>. You do this by calling the base class's constructor like this:

public ContactPersonList()
    : base(new List<ContactPerson>())
{}

If you are using the custom sorting solution for the ContactManager application as explained here: http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=428 you need to implement one Sort method: the one that accepts an IComparer<ContactPerson> like this:

public void Sort(IComparer<ContactPerson> comparer)
{
  List<ContactPerson> list = this.Items as List<ContactPerson>;
  if (list != null)
  {
    list.Sort(comparer);
  }
}

This way, your public Sort routine delegates its responsibility to the underlying List class. You could implement other overloads if you wish to define different sorting behavior that the List class exposes.

You can see the complete implementation of the ContactPersonList class here:

http://imar.spaanjaars.Com/Downloads/Articles/N-LayerDesign/ContactPersonListFromCollection.txt

Obviously, you can implement the same behavior for the other lists like AddressList. Also, instead of naming them *List, you could rename them to *Collection.

Hope this helps,

Imar
On Monday, March 24, 2008 1:57:08 PM tries said:
Imar,

I came upon this series of article last week and was very impressed with how your solution.  I wrote a sample application over the weekend and everything worked great.  However, I'm struggling with where/how to implement paging.  My stored procedures are written using the ROW_NUMBER function so I do understand how to retrieve the data.  I just need to know where you suggest I maintain the page number, page size, etc. and where to manage the paging events.

Any advice and/or samples would be greatly appreciated!
On Monday, March 24, 2008 2:18:11 PM Imar Spaanjaars said:
Hi tries,

I usually create an overload of GetList that accepts the start index and the page size. The existing GetList simply calls this new overload and passes 0,0 or -1, 01, like this:

public AddressList GetList()
{
  return GetList(-1, -1);
}

public AddressList GetList(int startIndex, int pageSize)
{
  
}

You then forward these parameters to a stored procedure that gets the correct number of records. When both parameters are -1, the entire list is returned.

You can easily hook this up to an ObjectDataSource and a custom paging solution using QueryString or ViewState.

Hope this helps,

Imar
On Monday, March 24, 2008 7:34:06 PM Vladimir Kelman said:
Hi Imar!  Could you explain me how to format a text so that it won’t swallow new line characters when I post here?

1) I downloaded your code and run it on Windows Server 2008 / SQL Server 2008 machine. It works fine. I could not find Win Server 2008 settings for Microsoft Distributed Transaction Coordinator, though. How to detect if it is running? Does the fact that application runs without errors mean that MSDTC is running? Am I supposed to get errors if MSDTC is not running, or it would be silent?

2) As you mentioned in your answer to Math Random above, in case MSDTC is not available, we could pass connection object between DAL method calls inside ContactPersonManager.Save() method, allowing individual DAL methods to share a connection and to use SqlTransaction on it. What I don't like here is that we're making an internal structure of Bll method ContactPersonManager.Save() dependant on external circumstances (whether MSDTC is available and whether we need to pass SQL Server connection around). Ideally, Bll method shouldn’t care about such things not related to business logic; it only should care about integrity of its objects and for its objects being able to save/retrieve themselves consistently. It would be nice to de-couple Bll method from such external things.

I guess, we need to add a level of indirection here. I would add one more [static ?] DAL class called DataManager. This class would be responsible to things like providing connections to individual DAL objects (their methods) and managing transactions. If necessary, Bll methods would call DataManager.BeginTransaction() method to start a transaction. Internally, DataManager would either use MSDTC -> TransactionScope if it is available, or open a SQL Server connection and start SqlTransaction on it otherwise. (Bll methods wouldn't care about those details.) Then, Bll methods would call individual DAL methods the same way your ContactPersonManager.Save() calls AddressDB.Save(), EmailAddressDB.Save(), and PhoneNumberDB.Save() right now. There would be no need to pass around connection object: each DAL method would obtain a connection from DataManager using DataManager.GetConnection() method. It could be the same shared connection, or it could be a new connection every time. For example, in case we're using SqlTransaction, DataManager would provide DAL methods with the same connection which was opened during DataManager.BeginTransaction() method call.

DAL methods would not call myConnection.Close() methods directly, as they do in your code. Instead, they will call ContactPersonManager.CloseConnection(myConnection). ContactPersonManager object would then close connection or keep it open, depending on if connection is still needed (for a pending SqlTransaction).
We also can wrap SqlConnection into our CustomConnection class overriding Close() and Dispose() methods in such a way, that they will ask ContactPersonManager object if connection should be really closed. That would allow us to work with "using" blocks.

It looks like such a design would allow us to decouple Bll layer from the specifics of a particular server (like availability of MSDTC transactions or using SQL Server or Oracle transactions instead.
What do you think? Am I re-creating a wheel here? I have a feeling that such solution already exists, but I don’t know it because of my ignorance.
On Monday, March 24, 2008 7:56:17 PM Imar Spaanjaars said:
Hi Vladimir,

The article is not necessarily about MSDTC, so if you need help on that. please use a search engine or the Windows help file.

I am not sure I understand / envision yet what you are saying or proposing. If you're saying you want to introduce a separate class to avoid duplication and make a single class responsible for handling transactions: that makes a lot of sense. Remember though: one of the main goals of my article series was a clean and simple design that many would understand and be able to use. In many cases, you don't need stuff like MSDTC. If you do, by all means, implement separate classes to deal with that.

Imar
On Monday, March 24, 2008 8:39:00 PM Vladimir Kelman said:
Imar,
I understand that your article is intended to teach people how to implement layer design. It works very well. I just tried to think how to modify your code so Bll methods will be independent on having or not having MSDTC available and will not need to pass connections between DAL methods if MSDTC is not available.
As you pointed out, I would add a separate class to handle connections and transactions. Bll methods would use it for transactions, DAL methods would use it to get connections.
On Monday, March 24, 2008 10:05:54 PM Vladimir Kelman said:
Hi Imar! Sorry for putting so many comments here.

I found your conversation with Edmund Ward (Friday 4/13/2007 5:21:30 PM and Saturday 4/14/2007 9:38:21 AM very interesting. I was thinking about pros and cons of storing look-up data in tables and/or/versus in enums many times. It is a little bit off topic here, please add your comments in my blog at http://pro-thoughts.blogspot.com/2008/03/because-vbscript-does-not-have-named.html.

(Instead of "using a generics class called NameValue (just an example) with a generic K key and a generic V value" we could probably use Dictionary[int, string].)
On Tuesday, March 25, 2008 9:42:55 PM Vladimir Kelman said:
Imar and Mark,
Thank you for sharing information on FX Cop and that MS good style rule of exposing Collection[T] instead of List[T]. It was just on time, because my application dealing with this stuff as well. I found a good page explaining this stuff: http://blogs.msdn.com/fxcop/archive/2006/04/27/585476.aspx.
On Tuesday, March 25, 2008 10:27:32 PM MarkB said:
Thanks for the reply, Imar.  I'll probably switch over to the .ObjectModel flavor.  
On Friday, March 28, 2008 10:38:01 PM Vladimir Kelman said:
Hi Imar!
You declare EmailAddress.FillDataRecord(IDataRecord myDataRecord) and pass SqlDataReader object into it. Karl Seguin in http://dotnetslackers.com/articles/net/FoundationsOfProgrammingPersistence.aspx uses similar DataMapper.CreateUpgrade(IDataReader dataReader) method and also passes SqlDataReader object into it.
Why do you think it is better to treat parameter as IDataRecord, rather than  IDataReader?
On Friday, March 28, 2008 10:49:31 PM Imar Spaanjaars said:
Hi Vladimir,

It doesn't matter much. IDataReader implements IDataRecord so both would work. In my case, I chose IDataRecord as it's the least restrictive of the two.

Imar
On Monday, April 07, 2008 2:32:11 PM Vladimir Kelman said:
Hi Imar!
You use SqlParameterCollection.AddWithValue(String, Object) method above. Is SqlParameterCollection.Add(String, SqlDbType) method or SqlParameterCollection.Add(String, SqlDbType, Int32) method generally safer (in terms of SQL injection attack, etc.)?
On Monday, April 07, 2008 6:50:55 PM Imar Spaanjaars said:
Hi Vladimir,

No, I don't think it matters in terms of SQL injection. Under the hood you still get the same SqlParameter object.

Imar
On Monday, April 07, 2008 7:01:11 PM Vladimir Kelman said:
Thanks! I got it. SQL injection cannot go through SqlParameter object anyway. Maybe longer method like SqlParameterCollection.Add(String, SqlDbType, Int32) just makes a little bit better control over programmer's errors...

I just found an interview Scott Hanselman have done with Rockford Lhotka. It explains in a brilliant way what is better about custom DAL (with or without O/RM tools) over using DataSets. It's here: http://www.hanselminutes.com/default.aspx?showID=123.
On Monday, April 07, 2008 7:04:26 PM Imar Spaanjaars said:
You're not guaranteed safe with a SqlParameter. You could still have dynamic SQL in a procedure for example that is vulnerable to attacks.

Imar
On Sunday, April 13, 2008 11:00:22 PM Kim said:
I have a comment to your list classes. First of all you shouldn't use the name List in the naming of the classes. Use a more generic name like Collection, E.g. EmailAdressCollection instead of EmailAdressList. This is better if you want to change what the class inherit from. Using the List in the name creates an expectation that it has something to do with List.

Second you should use System.Collection.ObjectModel and either use Collection, KeydCollection or ReadOnlyCollection for the contract collections. These are designed to be used in the object model of a reusable library.
On Monday, April 14, 2008 5:44:27 AM Imar Spaanjaars said:
Hi Kim,

You are absolutely right, but slightly too late with your comments ;-) Check out the original post from MarkB on 3/21/2008 9:19:39 PM and my reply to it on 3/23/2008 4:30:39 PM earlier in this list of comments.

Cheers,

Imar
On Saturday, April 19, 2008 2:58:31 PM Chuck said:
Imar,
In Marco Bellinaso's book ASP.NET 2.0 Website Programming: Problem - Design - Solution, he uses a DAL with methods that have Parameters.Add() vs. your Parameters.AddWithValue(). Why would you use one over the other? Is there any benefit using his method over yours? I'm not questioning the validity of you using your method I am just curious. Thanks Imar!

- Chuck
On Saturday, April 19, 2008 3:18:46 PM Imar Spaanjaars said:
Hi Chuck,

Check out my reply to Vladimir Kelman  on 4/7/2008 4:32:11 PM as he asked the same questions.

Main point is: it doesn't really matter....

Imar
On Friday, April 25, 2008 3:43:06 PM Vladimir Kelman said:
Imar,

Do you think that it is better in ContactPerson.GetItem() method to call AddressDB.GetList(), EmailAddressDB.GetList(), and PhoneNumberDB.GetList(), then to call these three methods in ContactPersonDB.GetItem() ?
(You could add bool getContactRecords parameter to ContactPersonDB.GetItem().)
On Friday, April 25, 2008 4:19:54 PM Vladimir Kelman said:
Suppose, I have class Employee and class Project. Each Employee belongs to one and only one Project (one to many relationship). It sounds natural to include into BO class Employee a member of type Project. But it is also a legitimate question of what Employees belong to this Project. Doesn't it mean that class Project should include a member of type List[Employee]? But it sounds like a circular reference... Is there a common pattern / common answer on my question?
On Friday, April 25, 2008 5:29:51 PM Imar Spaanjaars said:
Hi Vladimir,

Personally, I would prefer to have this decision in the Business Layer as it's that layer that can determine what it needs to load, based on some (optional) criteria. But, either way it will work.

Yes, it could be a circular reference. You could do this:

myEmployee.Project.Employees[0].Project.Employees[0].Project...

You typically solve this by keeping some state that determines whether or not to load sub objects. Additionally, you could lazy load them, so they are only loaded when needed which usually means you can limit loading stuff to one level.

Imar
On Thursday, May 01, 2008 3:18:55 AM Vladimir Kelman said:
Hi Imar!
We're porting an existing application with already defined business logic and a database with numerous tables and complex relationships. We don't want to reinvent the wheel.
What articles / books would you recommend to get better understanding of common "real-life" patterns / practices for designing domain objects?
On Thursday, May 01, 2008 7:04:19 AM Imar Spaanjaars said:
Hi there,

I don't have any solid recommendations. You may want to take a look at Amazon for that. There are many books available on the subject, but I think you'll have to look there yourself and read the comments from readers to see if they suit you.

Cheers,

Imar
On Thursday, May 01, 2008 7:10:08 AM Imar Spaanjaars said:
Oh, I do have one recommendation

.NET Domain-Driven Design with C# Problem - Design - Solution
http://www.wrox.com/WileyCDA/WroxTitle/productCd-0470147563.html

Imar
On Thursday, May 01, 2008 4:29:22 PM Phi said:
Can you explain to me what the follow line do?

[DataObjectFieldAttribute(true, true, false)]

It is in all of your BO classes.
On Thursday, May 01, 2008 9:30:18 PM Imar Spaanjaars said:
Hi Phi,

Please read part three of this series; it's briefly explained and demonstrated there and contains a link to a more in-depth article.

Imar
On Friday, May 02, 2008 9:48:21 AM Greg said:
Hi Imar,

Great tutorial!

You use enum for ContactTypes. The values are fixed which is ok.  What about if the ContactTypes is user managed?  i.e. the list of values can grow.  Would you still use enum?  is not, what would you do instead?

thanks.
On Friday, May 02, 2008 10:44:26 AM Imar Spaanjaars said:
Hi Greg,

In that case, an Enum wouldn't work as you would need to recreate the Enum's .cs file on the fly so it can be compiled.

Instead, store the values in the database in a simple "domain table" with an Id and a Description.

HtH,

Imar
On Monday, May 05, 2008 7:13:55 PM Greg said:
thanks for replying Imar.

I assume "domain table" is just a fancy name for a lookup table?  After creating this table, i would need to create another BO class.

Is the general rule; for every table in the database, you create a BO class?
On Monday, May 05, 2008 8:58:42 PM Imar Spaanjaars said:
Hi Greg,

Yep, just another name.

While it's not a rule, you often find that most classes have at least one table. However, for stuff like relationships or inheriting classes you may need more tables.

Cheers,

Imar
On Monday, May 05, 2008 9:05:44 PM Vladimir Kelman said:
Greg,
It would be nice if we could automatically generate enums from look-up tables, because it would give us IntelliSense... but such code would be fragile (what if you use literal name of enum member in code, but someone deleted corresponding look-up record?)
On Tuesday, May 13, 2008 12:22:27 PM Vasantha said:
Great article Imar. I just have one quick question though.
The private helper method FillDataRecord() accepts IDataRecord as an argum,ent. From your comments in the article, you do that to make it easier for an implementation of different database. But in my case, i need to first check if a certain column exists before i assign the value to one of the properties of the object. Is there a way to check for the existance of a column using IDataRedord or should i just pass the SQLDataReader to FillDataRecord() method(assuming i don't change the underlying database for any reason).
Thanks,
V.
On Wednesday, May 14, 2008 5:48:11 PM Imar Spaanjaars said:
Hi Vasantha,

I wrote a short article on this subject here: http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=446

Cheers,

Imar
On Sunday, May 18, 2008 11:59:23 PM Vladimir Kelman said:
Hi Imar!
Is it true, that while you reserved a possibility to populate ContractPerson together with its associated Addresses/EmailAddresses/Phones and to save ContactPerson together with associated lists (using TransactionScope), in this particular application you never populate ContractPerson with associated lists (you never call public static ContactPerson GetItem(int id, bool getContactRecords) method with getContactRecords = true) ?

In what situations you envision populating associated Addresses/EmailAddresses/Phones lists together with their "host" ContractPerson?
On Monday, May 19, 2008 7:58:03 AM Imar Spaanjaars said:
Hi Vladimir,

You can use that version in many scenario's where you use the API directly. For example when importing contact persons from a different source, or when exposing your data through a web service where a consuming client can offer an entire contact person which can be saved in one fell swoop.

Imar
On Monday, May 19, 2008 8:42:00 AM Edmund Ward said:
Hi Imar

I can't express enough thanks for these articles and the endless comments you make here. I have built a beautiful (in my eyes!) application based on these priciples and it works fine. Now, however, I'm being asked to add a load of reports using SSRS to the application. At first this seems fine, but I'm beginning to find that I'm starting to write business rules into the stored procedures that feed these reports. I'm not comfortable with putting business logic there *and* in the BLL. I'd prefer to have it all in the BLL. Is this something I'm just going to have to live with, or is there a trick to getting SSRS to feed off the BLL?

Cheers
Edmund
On Monday, May 19, 2008 10:26:00 AM Vladimir Kelman said:
By the way, Edmund, you could turn things around and [dynamically] generate necessary SQL Server stored procedures from BLL. That way your stored procedures would get business logic from BLL and would always stay consistent. Maybe it's a crazy idea, though.

Imar, thanks again for your articles and answers. We're currently building application using your approach.
On Tuesday, May 20, 2008 4:08:53 AM abuiles said:
Hi Imar, I have a little doubt, I fill my gridview with a x object type, Can I get this object without use a Dal, I mean, If I do click in the select option in one of the rows,so, get the object that contain that rows.
something like this

  protected void gridViewX_RowCommand(object sender, GridViewCommandEventArgs e)
    {
        switch (e.CommandName.ToLower())
        {
            case "select":
                 myTipe x= gridViewX."SelectMyDataType"
                 //"SelectMyDataType" is ficticious..I'm only supposing that I              
                     want :)
                break;        
}
    
    }

I know that using Dal I can get this , but I'm curious if this option is available..Im have been looking for this but I don't find anything yet, for that reason I decided to write you..

Best Regards

Adolfo Builes
On Tuesday, May 20, 2008 6:19:09 AM Imar Spaanjaars said:
Hi abuiles,

Probably not, but it depends on the data type. With simple types, like an int, the value can be stored in ViewState. However, with complex objects this won't work and you need to recreate the object server side with its primary key.

Imar
On Tuesday, May 20, 2008 1:22:15 PM Abuiles said:
Thanks for your answer, and thanks again for this document, It have been very useful to me and I'm sure to other people too. Right now I'm doing an app, based in this. but Im not going to publish it because I don't pay a hosting service, I just a final work for my database systems class.



On Thursday, May 22, 2008 3:47:17 PM Chaumette said:
Imar,

With the Dataset approach, if you wanted to filter a DataTable you could use a DataView to say filter by Type='A1', etc...

How do you filter a list object? for example:

Code_List lstCode  =  Code.GetList();
and if you wanted to filter by Type in ('A','B','C')?

Thanks,

Chaumette
On Thursday, May 22, 2008 4:41:40 PM Vladimir Kelman said:
Chaumette,
http://msdn.microsoft.com/en-us/library/fh1w7y8z.aspx or Linq to objects
On Thursday, May 22, 2008 5:46:42 PM Imar Spaanjaars said:
Hi Chaumette,

It depends on how you want to filter and where.

One solution is to let filtering take place in the database. This is fast and efficient as you only get the data that you really need. You can create overloads of GetList that expect filter criteria, or create methods like GetListByState. You then need to forward these criteria to a stored procedure.

Alternatively, you can filter a list of objects using Find and FindAll methods on the generic List class. If you're using the generic Collection class (as recommended by FX Cop and discussed in the comments earlier), you can use the Where extension method.

Finally, a simple LINQ query (requires .NET 3.5) could work as well:

var typesOfB = from p in people
                  where p.Type == "B"
                  select p;

Cheers,

Imar
On Tuesday, June 03, 2008 12:20:15 PM Manish said:
Hi, I am yet to explore your articles completely, but believe me at first view only any buddy can understand the depth of your knowledge and the pain you have taken for coming up with these good quality articles.

Best Wishes!!
Thanks for contributing such article.
Manish
On Wednesday, June 11, 2008 11:30:52 AM Emad Suria said:
Thanks for such a good article that focuses on practical implementation, rather than just theoretical concepts.

I'm working on a design where I have derived classes. Inheritance is the part missing from your example. It seems that dumb objects become infeasible with design involving inheritance.
For example, I have ContactPerson as base class and Employee and ExternalRepresentative as derived classes. To achieve polymorphism I'll have to put the Save() and Delete() methods in ContcatPerson class and override them in their derived classes. So my Save() method takes ContactPerson, so depending on the type of the actual object, appropriate over-ridden method gets called.
With this scenario My DLL will directly be used by the BOs and I don't have any idea where to fit BLL now, or do I still need it?
Please correct me if my direction is wrong or suggest any other appropriate approach for my problem.
On Wednesday, June 11, 2008 9:32:05 PM Imar Spaanjaars said:
Hi Emad,

Please search the comments on all three articles for inheritance. It's been brought up a number of times.

I don't understand what you mean with "My DLL will directly be used by the BOs" and I don't really see why inherticance would mess up the model.

Cheers,

Imar
On Friday, June 13, 2008 9:42:47 PM Ryan Hutcherson said:
Great article!  
One concern I have though is how you have your ID properties set for your BO objects.  Because they are Public and are not set to ReadOnly the Presentation layer can not only read its value but can also set its value.  It's fine that the Presentation Layer can get the value of the ID but Setting the value of the ID is the responsibility of the DAL.  The Save function in the DAL assumes that if the ID > 0 then an Update is needed, otherwise it performs an Insert.  It's also under the assumption that it's value came from the Database.

Unfortunately, I am having difficulties coming up with an alternative approach that fits within the guidlines of an n-layer solution.  

If we set the public Id property to ReadOnly (so Presentation Layer can't set the value) then how will our DAL object be able to set it?  We could add another property with writeonly privileges and set it's scope to Friend but then our DAL would have to be in the same project/assembly that our BO objects are defined in.  

What are your thoughts on this?
On Sunday, June 15, 2008 5:08:31 PM Imar Spaanjaars said:
Hi Ryan,

You can make the property internal (Friend in VB.NET) which limits its scope to objects within the same assembly (a .DLL)

However, you'll run into problems with data binding when the ID is read-only (as the controls try to set all properties) so you need to take that into account as well.

Cheers,
Imar
On Tuesday, June 17, 2008 7:17:57 AM Emad Suria said:
Sorry to create such a confusion!

"My DLL will directly be used by the BOs"

its actually

"My DAL will directly be used by the BOs" i.e.
If I choose to use smart classes having the methods along with the properties, then BO methods now directly use DAL methods, do I need *Manager classes now?

While in your design DAL methods are used by BLL!

With smart classes role of manager classes seems to diminish as business logic merges with the business objects. Is there any problem with this approach? If yes, what are the drawback?

Thanks
On Tuesday, June 17, 2008 7:33:59 AM Imar Spaanjaars said:
Hi Emad,

You could do that too: merge BO and Bll and have the combined classes talk to the DAL.

Then you don't necessarily need *Manager classes and make the main entities responsible for the access directly. This is what I called Smart Classes earlier.

Imar
On Wednesday, June 18, 2008 7:28:00 PM Ryan Hutcherson said:
Hi Imar,

Thanks for the heads up on the data binding issue with read-only properties!  To avoid that I will just throw an error if the set method is called (for the public property) instead of just declaring the property as read-only.

As for the Property that WILL be able to set the value of the ID I came up with - what I think is - a slightly better solution than what you had suggested.  The problem I have with just setting the scope of the property to Internal (Friend in VB) is that our BO will then need to be in the same Project/Assembly as our DAL in order for the DAL to have access to the internal property.   What I was looking for something similar to what C++ terms "Friend".  I wanted to be able to specify what friends the object had  (not just limiting it to the ones in its own assembly).  Unfortunately, .NET does not have an equivalent to C++'s Friend.  There is, however, an attribute you can use that emulates this relationship.   Using the InternalsVisibleTo attribute you specify the external assembly that is allowed access to anything with "Internal" scope.  Unfortunately, only C# code can take advantage of this.  Here is the url at MSDN for more on Friend Assemblies in C#:  

http://msdn.microsoft.com/en-us/library/0tke9fxk.aspx




On Tuesday, July 22, 2008 5:52:34 PM Christian Glaude said:
Hi, really nice and clear article!

This is my first OO project and this article is really helping me!

I have a question.

Why is that the method GetList in the ContactPersonManager did not pass by the GetItem of that same class in order to have the same object definition.

Is there something I missed!?

Thanks
Chris
On Tuesday, July 22, 2008 6:33:14 PM Imar Spaanjaars said:
Hi Chris,

GetItem gets a single item using its own SQL Server connection. So, getting a list by executing GetItem repeatedly would mean a lot of database overhead.

However, both methods *do* already share the same object definition: both call FillDataRecord to fill a new instance based on the data from the reader. So, code to get an item from a datareader into a single instance only needs to be written and maintained once: in FillDataObject.

Hope this helps,

Imar
On Thursday, July 31, 2008 7:19:12 PM Judith said:
I appreciate your articles very much. They are extremely helpful. I have one question about the ContactPersonManager.  The save method uses transactionScope.  Theoretically shouldn't that belong in the DAL as opposed to the business layer.
Thank you so much
Judith
On Saturday, August 02, 2008 9:44:03 AM Imar Spaanjaars said:
Hi Judith,

Transaction management is typically handled in the business logic layer. Your Dal could be spread over multiple tables or even databases, so only the Bll is able to oversee them all. That way, you can do (pseudo) stuff like this:

using (myTransactionScope)
{
    SomeDatabase.UpdateCustomers();
    SomeCompleteDifferentDatabase.UpdateOrders();
}

If any of the two updates failes, the transaction can be rolled back in *both* databases, something you cannot do when you have the transaction in each of the Dals. Of course if you have code that updates data in a single transaction in a single database, you can still use transactions in the Dal or even in the database itself (inside a stored procedure for example).

However, as soon as multiple data layers / databases are involved, the Bll is the best place for transaction management.

Hope this helps,

Imar
On Monday, August 11, 2008 9:37:22 PM Vladimir Kelman said:
Hi Imar!
I tried to use TransactionScope object in Bll of my application and got "Cannot access a disposed object. Object name: 'Transaction'" error. Did you ever encounter it?

Vladimir Kelman
http://pro-thoughts.blogspot.com/
On Thursday, August 21, 2008 8:22:48 AM Imar Spaanjaars said:
Hi Vladimir,

Hard to tell without seeing your code. Can you post this on a forum like http://p2p.wrox.com?

Cheers,

Imar
On Thursday, August 21, 2008 10:39:55 AM Vladimir Kelman said:
Hi Imar! Never mind, it was just my stupidity. Could you remove my last messages please? Thanks
On Wednesday, August 27, 2008 3:38:07 PM kurt schroeder said:
thanks again for this artical series,

How would one create a list that contains a list of addresses. I know it can be done by calling get item consecutively, but i think there must be a better way. What do you recomend?
On Wednesday, August 27, 2008 4:03:16 PM Imar Spaanjaars said:
Hi kurt,

You can fire multiple select statements in a stored procedure and read them using the NextResult method of a DbDataReader. You could then pass the reader to the constructor of the collection classes to fill the lists.

Hope this helps

Imar
On Wednesday, September 03, 2008 5:46:08 AM DotNetLeo said:
Sir I have read your artical, I found it the most informative full of knowledge for a programmer who wants to switch to object oriented development in layers.

BUT here I have a problem, I am looking for the solution which has diffrent projects for P layer, BO later, DAL, I mean different projects in one solution, and I wanted to know how the objects and data are moving from one project to other. I am really very confused to know about this.

Can you help me in this.

It will be really helpful.

Thanks & regards,

Raj
On Wednesday, September 03, 2008 6:16:26 AM Imar Spaanjaars said:
Hi DotNetLeo,

It's not so difficult. Try something like this:

1. Add a new Class Library project to the solution. Move the Dal code into that library.

2. Add a new Class Library project to the solution. Move the Bll code into that library.

3. Add references to the Dal and Bll projects in your Web Application / Web Site.

From there, things are pretty much the same.

For more info, may I suggest you get a good book on C# or Visual Studio where these topics are discussed in depth.

Cheers,

Imar
On Wednesday, September 24, 2008 3:45:48 PM Fredrik said:
Superb articles! I love it!

I have a suggestion to make the layered web application better. Instead of having a constructor with an extra argument that specifys if child objects should be loaded one can just load a list with a specific child object when the user tries to reach it. Then we dont load unneccessery objects. Eg:

public List[ChildObject] LstChildObjects {
  get {
  if(_lstChildObjects == null) {
     _lstChildObjects = ChildObjectDB.GetList();
    }
    return _lstChildObjects;
  }
  set { ... }
}

This way everytime someone tries to reach a list with child object to the "main" object it checks if its loaded or not.

Any comments?
On Wednesday, September 24, 2008 9:27:25 PM Imar Spaanjaars said:
Hi Fredrik,

Good suggestion. I mentioned this a couple of times in the comments as "lazy loading". You're right in that it's useful if you don't need them or you're unsure you need them. However, if you *do* need them, you now need four connections and four query executions, rather than one.

So, it's a tradeoff between speed / performance, API discoverability and run-time behavior that you need to think of when designing an application like this. There's no "golden bullet" or "correct way" to do it, unfortunately.

Cheers,

Imar
On Wednesday, September 24, 2008 9:39:38 PM Vladimr Kelman said:
Imar and Fredrik,
I guess another implication with this implementation of "lazy loading" is that BO becomes more "smart" / less "dumb"... but maybe in this case it isn't a bad thing. We can also keep "a constructor with an extra argument that specifys if child objects should be loaded " AND add lazy functionality as well. So, when it is appropriate, we would be able to explicitly populate child lists an duse less connections. At the same time, it will guarantee, that even if we didn't call it explicitly, it is always populated on demand...
On Friday, September 26, 2008 9:02:44 AM Fredrik said:
Good point Vladimr! That is the "golden bullet". :)
On Tuesday, November 11, 2008 2:17:51 PM shalan said:
Fantastic series Imar! thanks for making this available.  Just 2 questions regarding MSDTC.  Im developing on Vista Business and deploying on Windows Server 2003.  Ive added a reference to System.Transactions in my project - do I have to do anything else? Also should I worry at all about this on Server 2003 side or should I query this with my hosting provider?

thanks
On Thursday, November 13, 2008 8:16:43 PM Imar Spaanjaars said:
Hi shalan,

Take a look at this article to see how to enable MSDTC on Vista:

http://technet.microsoft.com/en-us/library/cc753510.aspx

For Server 2003: it depends on the configuration. Try it out and see if it works. If not, you may need to have your host follow similar procedures on the web server and the database server.

Cheers,

Imar
On Thursday, November 13, 2008 9:21:03 PM Vladimir Kelman said:
BTW, we suggessfully set up DTC on WinXP. Server 2003 and Server 2008 machines with database and application being hosted on different servers.
Look at http://pro-thoughts.blogspot.com/2008/11/configuring-ms-dtc-on-winxp-server-2003.html
On Friday, November 14, 2008 1:47:43 PM shalan said:
Hi Imar, thanks for that.  Im just curious about something - On my dev Vista machine, all I've done is added a reference to System.Transactions namespace, with no other configuration, and everything seems to be working...except for inserts (I'll take a look why thats happening). If I have these working, should I concern myself with following DTC configuration?
On Friday, November 14, 2008 8:12:41 PM Imar Spaanjaars said:
If MSDTC is on, and you only access localhost sources (e.g. SQL Server is on the same box as the web site) you don't need to do anything.

However, as soon as you access remote sources, MSDTC must be configured to allow netwok access.

Cheers,

Imar
On Saturday, November 15, 2008 6:54:11 AM shalan said:
Thanks a bunch for that, Imar. Now I just need to check with my hosting provider if they have it configured on their side.
Take care!
On Sunday, November 16, 2008 1:41:14 PM gita said:
Excellent set of articles Imar! Could I just get your opinion on using Microsoft DAAB versus your method of custom objects outlined in these 3 articles? I have a really obnoxious colleague who swears entirely by using DAAB. However, we work primarily with Oracle.
thanks!
On Sunday, November 16, 2008 7:22:22 PM Imar Spaanjaars said:
Hi gita,

DAAB only replaces the data access part, not the entire custom objects architecture.

So, you could use DAAB in the Dal and still use the same principles outlined in these articles.

Cheers,

Imar
On Thursday, December 11, 2008 7:08:51 AM Kevin McHugh said:
Good morning, Imar -

These, plus the .Net 2.0 version, have helped immensely as I'm trying to learn OO/tiered development.  Best $15 I've ever spent!

I've run into a problem I can't find a way out of.  I need to store the data returned by the datareader in a table instead of a list and I can't get it to work; I keep getting an "ObjectDataSource 'odsXXX' could not find a non-generic method 'GetTable' that has no parameters."

There's so little information that I can't find the source to fix this up.

Might you have any suggestions on how to return a DataTable instead of a List (or how to fill a DataTable from the list)?

Excellent work, Imar.

Kevin
On Thursday, December 11, 2008 10:38:20 PM Imar Spaanjaars said:
Hi Kevin,

Difficult to say without seeing the source.... Can you post the code on a forum like http://p2p.wrox.com and send me the link so I can take a look at it?

Cheers,

Imar
On Friday, December 12, 2008 7:07:22 AM Kevin McHugh said:
Good morning, Imar -

wrox has been down for a bit more than a day for maintenance (I went there to look for an answer, first).  As soon as they are back up and running, I'll post and let you know.

Thanks!

Kevin
On Friday, December 12, 2008 7:15:18 AM Imar Spaanjaars said:
Hi Kevin,

They are up and running again.....

Imar
On Friday, December 12, 2008 3:55:55 PM Kevin McHugh said:
Yes they are, but the ObjectDataSource paramters problem has been fixed.

When I was moving from .Net 2 to follow this excellent new example, VS did not at first add the OldValuesParameterFormatString="original_{0}" to the ODS declaration(?).

In fact , it didn't add that until the third time I had deleted and re-created the ObjectDataSource for the GridView.  I'm not doing CRUD, just a READ, and it never occurred to me I still had to set an OldValuesParameter.

I live and I learn, Imar.  Thank you for following up.

Kevin
On Friday, December 12, 2008 7:25:40 PM Imar Spaanjaars said:
Hi Kevin,

You're welcome and thank you for following up too.

Cheers,

Imar
On Friday, January 02, 2009 10:32:42 PM Robin Simmons said:
Imar, I've read your Building Layered Web Application series with great interest.  I would be interested in your comments about the best practice for handling numerous lookup tables.  My application has about 25 lookup tables.  My UI currently uses Dropdown boxes and ObjectDataSources, but I would like to make it more generic.  Some tables have an integer ID and others have a String ID.

I'm making modifications to my application based on your articles.  Where would the code go for a generic lookup table object?

Thank you,
Robin Simmons
On Friday, January 02, 2009 11:22:32 PM Imar Spaanjaars said:
Hi Robin,

You have a few ways to do this, including:

1) Create a generic data type, like NameValue that is able to contain a key and a value. If you use Generics, you can make the Key generic to support the differences in the type of the key in the database.
Create a method on this new class that returns a collection of NameValue items (a List, a Collection, whatever you need) based on some criteria you pass in (a string to denote the type, or an enum for example). Forward the calls to the Dal that gets the relevant items from the database based on the criteria, sorts them and finally returns them.

2. Create specific types in the BO and Bll and give them properties like Key and value. Additionally, give them a GetList method that returns a strongly typed list (or collection). This Getlist method could call a Generic Dal method that is able to find out what items to retrieve. For example:

public StateCollection GetList()
{
  return LookupDal.GetList<State>();
}

This option requires some generic type parameters on the lookup items like State (they could implement something like ILookup that defines a Key and a Value) and a constructor constraint so the Dal is able to construct them. Inside the Dal method, you could look at the type of T and "infer" the correct stored procedure or in-line SQL to call. This requires some conventions between type names and sproc names, but it offers you a lot of flexibility.

Hope this gives you some ideas.

Cheers,

Imar
On Monday, February 02, 2009 6:48:51 PM Jorge L. Cotarelo said:
Hi Imar,

I really thank you for this great article!

Just a little question: Why you created the private method "FillDataRecord" instead of write this code inside of each "GetItem" method?

Thanks in advance and best regards.

Jorge L. Cotarelo
On Monday, February 02, 2009 7:21:55 PM Imar Spaanjaars said:
Hi Jorge,

It's because of the DRY principle: Don't Repeat Yourself. The code from FillDataRecord is used in GetItem and in GetList so it makes sense to abstract it to a single private method so you only need to write the code once. If you're a big fan of typing and maintaining the same code over and over again, feel free to move it to the GetItem and GetList methods instead... ;-)

Imar
On Monday, February 02, 2009 7:28:49 PM Jorge L. Cotarelo said:
Thanks Imar for your quick response.

Best regards,

Jorge L. Cotarelo
On Tuesday, February 24, 2009 12:21:26 AM Jeremy said:
Imar,
This is a great series that I have used to rethink my architectures for more complex systems, I thank you for the time you have put into writing such a useful article.

I am working on one application that uses this architecture (more or less) yet I have come to realize something and wanted to get your thoughts on a performance concern I have.

In your GetItem method for ContactPerson, you have produced some flexibility by adding an optional boolean parameter to determine if the related tables should be called.  If the developer passes true, it does 3 more GetList calls, so we are doing 4 calls to the database, each with their own connection opening and closing.  My DBA mentor engrained in me the belief that you should limit the number of times you open/close a database connection per action as much as possible, what are your thoughts on this?  Also, because they each use their own connection, how can we transact these 4 calls so they are atomic?

I'm trying to put my head around a way to open a DB connection, begin a transaction, work with the Access Layer, then close it and commit when all operations are done for a total of 1 connection to the DB being generated per use.
On Tuesday, February 24, 2009 7:37:32 AM Imar Spaanjaars said:
Hi Jeremy,

There are a few ways to do this:

1. Make the Dal classes instance based, without static methods. Then give them a property of type DbConnection, and use that in your Get* methods. Then, when getting the contact person, create a new connection, new up the other instances and set the connection property.

2. Pass around the connection in (new) Get* methods. You may want to make these extra overloads internal to hide the connection from the public interface.

3. Create a stored procedure that selects all relevant data. Then call NextResult on the DataReader to go from result set to result set so you can load contact people, addresses, email addresses and phone numbers in one fell swoop.

BTW: all they are doing is a SELECT; there is no need for a transaction here.

Cheers,

Imar
On Tuesday, February 24, 2009 12:55:14 PM Vladimir Kelman said:
... On the other hand, I saw an opposite Microsoft's recommendation: to open a connection as late as possible and to close it as soon as possible, instead of passing the same connection object around between methods. All that because IIS (?)  is capable of connection pulling, so that the same connection object instance is quietly shared / reused behind the scenes.
On Tuesday, February 24, 2009 1:01:13 PM Imar Spaanjaars said:
Hi Vladimir,

Yes, correct. That's what I generally refer to as the Bankrobber's 3G rule: Go in, Get what you want and Get out.

Connection pooling is indeed a great feature that enables you quickly get an available connection from the pool without much of the overhead of creating one.

However, that generally refers to keeping connections open for a "long" time, say, the life time of a page. In this case, each data access method uses the connection and doesn't do much more, so the multiple calls over the same connection will be made in rapid succession. IMO, that will outperform the other option of manually creating and opening a new connection, pooling or not.

Cheers,

Imar
On Tuesday, February 24, 2009 4:35:10 PM Jeremy said:
Imar,

Thank you for the quick response, this is very helpful.

After thinking on it some more yesterday, I did come up with options 1 and 2 that you have provided as possible solutions.  I was thinking of using option 1 more so that the passing of connections was transparent to the end developer.  I may still use the option of making the DAL instance-based if I find that this becomes a more common situation.

I do realize that transactions aren't necessary, but in some of my situations, I do have selects and updates happening sequentially.  However, using option 1 will also allow for the use of transactions as well, so creating an instance class may be the solution I need.

Thank you for the consult, it's nice to discuss architecture with talented developers.  I'm the sole developer in my office, so consults are a rarity.

Jeremy
On Tuesday, February 24, 2009 4:45:27 PM Imar Spaanjaars said:
Hi Jeremy,

You're welcome.

I have been in that situation before, and I know from experience that it isn't always easy to work on your own. If you're interested in more talented colleagues, you can always come work for us: www.designit.nl ;-)

Cheers,

Imar
On Thursday, March 05, 2009 3:47:16 PM Vladimir Kelman said:
@Imar,
We actually run into a serious issue with this model and MS DTC transactions. We're planning to use database mirroring with some type of automatic failover. As MSDN says at http://technet.microsoft.com/en-us/library/ms366279.aspx, "Database mirroring is not supported with either cross-database transactions or distributed transactions."

It's a pretty typical scenario, when it is necessary to update simultaneously two business objects. In a current approach which we got from your article, a BLL Update() method makes two calls to DAL Update() methods for these two business objects, surrounding them by DTC transaction. It's simple and clear.
I don't really want to move both calls into a DAL method or SP, because this would put business logic into DAL (SP), compromising N-Layer design.

So, what alternatives we have? I guess... in BLL Update() method:
- open "try" block
- get an open SQL connection from some helper DAL class,
- start SQL Server transaction,
- call both DAL Update() methods and pass existing connection object to them,
- commit SQL transaction  and close a connection.
- close "try" block.

Sure, it could be wrapped into some helper methods. And it will be compatible with database mirroring.

Any better ideas?
On Thursday, March 05, 2009 9:39:18 PM Imar Spaanjaars said:
Hi Vladimir,

Difficult to say without seeing more of your code, setup, network architecture, and scenarios. Also, reasonably off-topic and more something for a consultancy job from your local tech company.... ;-)

Your idea sounds good, but I am sure there must be a 100 alternatives, as there are always a 100 alternatvies.

Cheers,

Imar
On Thursday, March 05, 2009 10:03:59 PM Vladimir Kelman said:
@Imar,
I didn't expect you to be solving my problems :)  I just wanted double-sure I'm not missing something obvious.

Thank you!
On Tuesday, March 31, 2009 12:53:48 PM Amanda Myer said:
Hello Imar,

First, I want to thank you for taking the time to composes such helpful articles!  You have a knack for putting concepts in terms that the average joe can understand.  I've tried reading the microsoft articles regarding n-tier development, and they are so lofty and wordy that I find myself rereading passages over and over just to get the gist of what they are saying.  Your articles are so much more clear and well-written. Thank you so much!

I have two questions for you.

1)  If the data for an object is actually composed of several different tables in the database, what layer (BLL or DAL) is considered the best-practice for "conglomerating" the object together?

2)  When dealing with many-to-many relationships between objects, how do you prevent the object retrieved from the database from getting huge?  For instance, in your contact manager application, I could foresee a scenario where there is a many-to-many relationship between contacts and addresses.  It is reasonable that a contact can have multiple addresses, and that an address could have multiple contacts.
In the database, this relationship would be representated by a ContactPerson table, and Address table, and a ContactPerson_2_Address relationship table.  The ContactPerson_2_Address table would contain the columns "ID", "ContactID", "AddressID", and "TypeID" where "TypeID" refers to the relationship type (i.e "Home, Business, etc").
If you used the GetItem(id, true) method on the ContactPerson object to populate the contact data, and if the Address object's getItem method also populated it's own "extra data" which included the  ContactPerson's that address was associated with, wouldn't it eventually keep looping until it had retrieved all related ContactPersons and Addresses in the tables?
I'm not sure if I am explaining this very well.  But basically, if a ContactPerson object contains a collection of Address objects, which contain a collection of ContactPerson objects, which contain a collection of Address objects....  etc.. etc...  this would eventually pull all data from the two tables.
So how do you prevent this?...

After typing all this it occurred to me that you probably would just use the boolean value on the "GetItem" method to restrict it getting the additional records...
DUH!

Well, the first question still stands.
On Tuesday, March 31, 2009 7:06:43 PM Imar Spaanjaars said:
Hi Amanda,

Thank you for kind feedback... You are aware of the Delete button, aren't you? ;-)

I would join the related tables in a stored procedure so I could get only those records and columns I need.

Cheers,

Imar
On Friday, December 11, 2009 4:34:59 PM Sven G said:
Excellent article!
Thx
On Tuesday, December 29, 2009 10:50:58 PM Ginger said:
Literally, unreal how easy this is to understand when you explain it. Thanks SO freaking much.
On Sunday, March 07, 2010 6:52:41 PM Alex said:
Hi,

Thank you for the great articles.

So with every query you're creating a SqlConnection object. Won't the system be overloaded?

Thank you
On Monday, March 08, 2010 6:00:51 PM Imar Spaanjaars said:
Hi Alex,

Why would it? If you're using using blocks the SqlConnection object is destroyed as soon as you're done with it. This scales much better than keeping an open connection around somewhere.

Cheers,

Imar
On Tuesday, April 06, 2010 11:19:33 AM Mike said:
Hello Imar, Excellent article. I have been following it really closely and made my way till article 2, one thing is confusing me now

    1) Why are you saving addresses,phoneNumbers and Email addresses on Contact Person Save ? Sure they are part of ContactPerson but

    2) I cant see on the sample code that you add these 3 collections anywhere on the save ContactPerson page so why are we saving them ? & saving them from where, cahce records ?

Thank you, Mike.
  
On Tuesday, April 06, 2010 11:52:09 AM Imar Spaanjaars said:
Hi Mike,

They are being saved in case you added them manually, or got a collection of them using an overload of GetItem that also loads the contact details. This isn't shown in the sample app, but you can use it to save an entire object graph.

Cheers,

Imar
On Tuesday, April 06, 2010 6:28:15 PM Mike said:
That's just what I needed, thank you so much !
On Wednesday, April 07, 2010 7:08:01 AM Mike said:
Hello Imar,

1) You have used TransactionScope. Are they database dependant?

2) Would you recommend an approach to save all associated objects(object graph) with a separate manager class ?

SaveContactPersonObj(MyContactPerson,MyAddressList MyEmailAddressList,MyPhoneNumberList)

& wrap them inside a single transaction object with tran.commit() and tran.rollback() ?

Thanks, Mike.
On Wednesday, April 07, 2010 8:02:45 AM Imar Spaanjaars said:
Hi Mike,

1) Google for TransactionScope and you'll find it's not database dependent.

2) Not exactly sure what you're suggesting...

Imar
On Wednesday, April 07, 2010 11:46:24 AM Mike said:
Hi Imar, thanks for the reply, I will look into the TransactionScope. I was suggesting to just insert all the related objects in a single DB class.

I have another little question, maybe I missed something.  Why are we not initializing a new class for the email object through presentation layer and passing that to the business logic layer on Email Save just as we do with the Contact Person?

Also, in which layer we are mapping the data to fields of EmailAddress class ?  I debugged the code and its working fine am unable to understand the logic where its fetching the values from the formview in code.

Thanks, Mike.
On Wednesday, April 07, 2010 11:51:41 AM Imar Spaanjaars said:
Hi Mike,

That's just to show different solutions / options.  The page AddEditContactPerson.aspx shows you how to manually manage an entity through a custom page and direct API calls.

Managing contact data is done with FormView and ObjectDataSource controls. They take care of the mapping. You could, if you wanted to, implement the same custom page / controls as is done with the ContactPerson.

Cheers,

Imar
On Monday, May 03, 2010 10:51:44 AM Mohammed Shameem said:
Hi Spanjaars,

Thank you for the excellent "3-series article" on n-teir app. design. Infact, I do believe that the coding practice you explained are indeed the base for RAD code-generation tools like NextGeneration.

However, the bottom-line is that the coding style and how you implement a real-life solution, all the more depends on the given time & budget at hand. The n-tier concept of .Net apps depends purely on your business scenarion, its complexity and the level of scalability you expect later on.

I hope I am right.

Thank you again.
On Monday, May 03, 2010 11:00:17 AM Imar Spaanjaars said:
Hi Mohammed,

Yes, correct. Such a design would be overkill in a typical "Hello World" application.

Correct spelling, on the other hand - especially with author names - is never overkill and can be used everywhere.... ;-)

Cheers,

Imar
On Wednesday, May 12, 2010 5:17:14 PM Mike said:
Hello Imar, thanks for the reply. I am starting to understand your article much better now after building it 3 times, now I can plan on paper and not peek into much of your code :)

Couple of questions.

1) You have used Enums. Say if I don't use Enums at all and fill dropdownlist on pages through DB via the 3 layers. I am ending up with many business objects/collections and DB classes for just dropdownlists alone. What would you recommend and why ?

2) Can we make a FillDataRecord method inside a helper class like AppConfiguration which returned the connection string ? So I don't write the fillDataRecord again and again in each and every DB class ?

On Thursday, May 13, 2010 9:01:31 AM Imar Spaanjaars said:
Hi Mike,

1) Yes, you would end up with many different classes. If you use a code generator, this isn't too bad. Alternatively, you could write a generic list class that is able to query all kinds of lists using dynamic SQL.

2) You could, but since each FillDataRecord is table sepcific, you need to use reflection to get data from the reader into the class.

Cheers,

Imar
On Thursday, May 13, 2010 11:02:51 AM Mike said:
Thanks Imar.

If you were having a situtaion to pass some values to the BLL and get a result through a complex formula (without any database work), would you place that formula inside the business logic layer namespace or just inside a helper class ? I tried making a utility class inside the business logic layer namespace but the pages wont pick it up without using 'using' statement.
On Thursday, May 13, 2010 12:05:49 PM Imar Spaanjaars said:
Hi Mike,

It's a broad question, but I would probably put it in the business layer and optionally move some of it to reusable helper classes.

>> I tried making a utility class inside the business logic layer namespace but the pages wont pick it up without using 'using' statement.

That makes perfect sense to me. If they are in their own namespace, you need a using statement.

Imar
On Thursday, May 13, 2010 1:05:41 PM Mike said:
That cleared my confusion, thanks so much.
Mike.
On Thursday, May 27, 2010 10:19:16 AM Mike said:
Hi Imar a little question,
If I don't want to use an object datasource in some situtation, Like
   1) If I want to get selected items for a customer out of total items
   2) How do I load the collection returned by business layer into presentation layer ?
   3) Would that mean referencing the business logic layer in my page ?
   4) How would I got about this situation ?

Thanks, Mike
On Thursday, May 27, 2010 10:22:41 AM Imar Spaanjaars said:
Hi Mike,

Yes, you would access your BLL in Code Behind and use the API to get whatever you need:

ContactPerson myContact = ContactPersonManager.GetItem(23);
AddressCollection addresses = myContact.Addresses;
etc

Hope this helps.

Cheers,

Imar
On Thursday, May 27, 2010 10:46:53 AM Mike said:
Hi Imar, thanks for the reply. With MVC model of site the replies are also lightning quick now :)

You said
ContactPerson myContact = ContactPersonManager.GetItem(23);
AddressCollection addresses = myContact.Addresses;

How can I do this in my presentation layer ? I can only access the ContactPersonManager in my presentation layer which returns a ContactPerson List.

So how do I use this list for custom checking/looping through for each in my presentation layer? I also find this article fairly close to yours http://www.dotnetfunda.com/articles/article472-passing-data-between-layers-using-generic-list-collection-.aspx
On Thursday, May 27, 2010 10:50:52 AM Imar Spaanjaars said:
>>  I can only access the ContactPersonManager in my presentation layer which returns a ContactPerson List.

That's not true. The ContactPersonManager also has a public GetItem that returns a single ContactPerson.

>> So how do I use this list for custom checking/looping through for each in my presentation layer?
Not sure what you mean by that.

Cheers,

Imar
On Thursday, May 27, 2010 10:54:48 AM Mike said:
I meant presentation layer only accesses Business Logic Layer whom accesses business objects and DataAccess layers.

1) Is it a rule of thumb ?
2) I saw in your API, you used BLL and BO both inside the presentation layer
3) If I do reference them inside my presentation layer, wont it just lost its cause of encapsulation ?

Thanks, Mike.
On Thursday, May 27, 2010 11:08:55 AM Imar Spaanjaars said:
The Spaanjaars.ContactManager.Bll *is* in the Business Logic Layer so I don't see the problem.

Imar
On Friday, May 28, 2010 6:01:14 AM Mike said:
Is it ok to access the business objects layer from presentation layer?

You are always so helpful. Thanks,
Mike.

On Friday, May 28, 2010 7:18:36 AM Imar Spaanjaars said:
Hi Mike,

Just think about it for a second. How would you be able to display, say, someone's name if you can't access a Business Object in the presentation layer? And how would you get hold of the ContactPerson in the first place if it wasn't OK to access the Business Logic Layer?

Tip of the day: instead of refreshing this page until you see my response appear, try subscribing to this topic so you get e-mail when new replies are posted. Your address won't be sold or used commercially.

Cheers,

Imar
On Friday, May 28, 2010 7:27:31 AM Mike said:
I am sorry I did not mean to increase overhead on your site. I will look into what you said.

Thanks, Mike.

On Friday, May 28, 2010 7:32:44 AM Imar Spaanjaars said:
Hi Mike,

It's not about overhead for my site, but about convenience for you. Why poll when you can be notified. Of course other than entering a valid e-mail address, you still need to check the "Notify me of replies" option... ;-)

Imar
On Thursday, July 01, 2010 9:27:17 PM Chris said:
I really enjoyed the article.  One question, and anyone in this thread may feel free to answer, is the use of static methods bad?  I had read somewhere else that it wasn't recommended...I don't remember why, but I remember it being said.

Any help would be greatly appreciated.  Thank you.

Best regards...Chris
On Saturday, October 30, 2010 5:40:43 PM Vishal Marya said:
The DAL classes in the article use the "Open Late Close Early" methodology for Connection objects. You are opening the connection just before a "ExecuteNonQuery or ExecuteReader" command, and closing it immediately

This means that inside a transaction where a number of business objects have to be updated ( using different DAL classes) , the connection will be opened and closed many times.

In that case the transaction will be escalated to a distributed transaction.  Now we can’t assume that MSDTC will be available\running every where. The administrator might not allow it for some reason and certainly most of the shared web hosting service does not allow it. What do you suggest for such a situation?
On Sunday, October 31, 2010 9:26:42 AM Imar Spaanjaars said:
Hi Vishal,

I typically talk to my administrator and explain why I need MSTC ;-)

Alternatively, you could create a SqlTransaction, attach it to a SqlConnection object and pass that around to new, overloaded versions of the Save methods that accept a SqlConnection.

Cheers,

Imar
On Sunday, October 31, 2010 12:04:59 PM Vishal Marya said:
Thank you for the reply.

As You suggested, For using SqlTransaction and SqlConnection.BeginTransaction, I have to open the connection object much before it is required, and it has to remain opened till the commit is called. Isn't it?

After reading such exhaustive articles, I have a few other questions.

1. I know about the connection pooling done by SQL Server. But still, is opening a connection once for a transaction not better than opening and closing it every time a command is executed inside that transaction?

2. If open late close early is a better method for a web application, what about a windows application where concurrent users will not be more than 20 at a time.

3. I mostly implement the Foreign key, referential integrity and unique constraints inside my database. Should these be implemented inside Business layer. What do you suggest?

4.  Is DAL using stored procedures for update and reading of data is better performance wise as compared to using command and adaptor objects?
On Sunday, October 31, 2010 12:11:32 PM Imar Spaanjaars said:
>> As You suggested, For using SqlTransaction and SqlConnection.BeginTransaction, I have to open the connection object much before it is required, and it has to remain opened till the commit is called.

No, Yes. You can open it just before you need it.

Re: 1 It all depends on your SQL statements, time between entries and so on.

Re 2: Windows apps are a bit beyond the scope of this article, but in those apps you want to minimize open connections to the database as well in order to avoid concurrency issues.

Re 3: As I've done in this article, I define them in the database and replicate some of the behavior in my objects.

Re 4: It all depends, and on a lot of factors.... Measure your own code to find out the truth for your specific scenario.

Cheers,

Imar
On Wednesday, May 25, 2011 10:28:32 AM Tulsi said:
Thank you for providing such a nice explanation on using the form view for inserting, updating, and deleting data.  I've been successfully using this model in another application.

I had a question about the update funtionality and the formview.  For example, the phone numbers section, the Edit link allows you to update the details of the phone number directly on the grid view row.  In my case, I have other fields that are not displayed on the grid view that also may need to be updated.

Can I have the edit link button go to the formview for "Create New Phone Number" and allow the update to be done there?  If so, what is the correct way to do this?

Thank you

Tulsi
On Friday, May 27, 2011 8:43:53 AM Imar Spaanjaars said:
Hi Tulsi,

You can display a Panel with the FormView on the click of a button. Alternatively, and a lot easier, you can link to a details page as is done with the contact person.

Cheers,

Imar
On Saturday, August 13, 2011 3:51:10 AM Sudarshan said:
Dear Spaanjaars,
   Dude you rock!.I am a newbie to this world of programming.But your article is helping me to survive in this part of the world.Thing is even I can understand about what some of you guys are talking.I know this post is not relevant.thanks guys to all of you who are contributing to kill ignorance.
On Saturday, August 13, 2011 4:39:19 AM Imar Spaanjaars said:
Hi Sudarshan,

You're welcome. One of the design goals for this article series was to make it as accessible as possible. Good to hear I succeeded... ;-)

Cheers,

Imar
On Monday, November 14, 2011 8:11:23 PM Michael W. said:
This guide is great.  Thanks a lot, especially for making it freely available.  This should really be required reading for anyone who is thinking about creating an application with a back end data source.  I've only been programming for about a year (without a CS degree or anything), and I had no problem following this guide, so you really hit the nail on the head with making it accessible.  I've successfully used this guide to refactor our spaghetti monster of an intranet site we use for managing employees and such.
On Tuesday, November 15, 2011 4:49:56 PM Imar Spaanjaars said:
Hi Michael,

Perfect; good to hear. Have you considered buying the PDF version of the follow up series: http://imar.spaanjaars.com/482/first-part-of-new-article-series-on-n-layer-design-in-aspnet-35-published  ;-)

Cheers,

Imar
On Wednesday, November 16, 2011 8:52:10 PM Michael W. said:
Awesome, I will definitely buy this first thing next paycheck (December).  Will get back to you then!
On Friday, November 18, 2011 11:12:29 PM Bob Eggles said:
Hello Imar,

Thanks for a great series.  I had been looking for an article that just covered the basic concepts without using all the latest and greatest features.  I am also quite impressed that you are still replying to comments.  So impressed that I went and ordered your follow up series. :-)  Thanks again for providing a much needed article.

Regards,

Bob
On Saturday, November 19, 2011 3:58:14 AM Imar Spaanjaars said:
Hi Bob,

Thanks for that. Did you already order it? Haven't seen a confirmation yet....

Cheers,

Imar
On Monday, April 02, 2012 11:04:30 AM swarna said:
how to do add the items to cart using three tier (including middle tier)
On Monday, April 02, 2012 2:36:00 PM Imar Spaanjaars said:
Hi swarna,

I guess that depends on the implementation of the cart. This is a bit too off-topic for me to answer here. May I suggest you take it to a forum such as http://p2p.wrox.com where it's easier for us to discuss this in detail?

Cheers,

Imar

Talk Back! Comment on Imar.Spaanjaars.Com

I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.

When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.

If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the QuickDocId of the document.

For more information about the Talk Back feature, check out this news item.