Mindscape Mindblog

Archive for August, 2007

Validation with LightSpeed

tag icon Tagged as LightSpeed

Validation is an important part of most applications, it helps ensure data integrity and, if implemented well, guide the user on how to use applications correctly in a friendly manner.

A common query about how to achieve such validation is where the validation should occur and how it should be applied. Many developers elect to place such validation code close to the view, for example in ASP.NET you may use the Validator controls and have any custom validation in the code behind of the page. Modern software development is moving to place field level validation in the domain model to provide a single instance of these rules to avoid duplication. LightSpeed provides a rich framework to help create these validations.

LightSpeed ships with several common validators (found under the Mindscape.LightSpeed.Validation namespace).

  • Presence validator: ensure a field has a value
  • Unique validator: ensure a field is unique in the database
  • Uri validator: ensure a field is a valid URI
  • Format validator: ensure a field conforms to a provided regular expression
  • Email validator: ensure a field is a valid email address
  • Comparison validator: compares a value to another value

More than one validator can be applied to a single field which enables richer validation if required.

Applying a validation

Validators in LightSpeed are applied as attributes to the fields in the domain classes. For example, to apply the presence validation to a firstname field you would write the following:

[ValidatePresence]
private string _firstname;

To apply multiple validators:

[ValidateUnique]
[ValidateEmailAddress]
private string _emailAddress;

Validating objects

Once validation attributes have been applied you will no longer be able to persist objects that fail validation. The exception thrown will provide details of what validation failed. That works well for ensuring data consistency however this does not help create an enjoyable experience for end users. Thankfully, you can validate domain objects by calling the .Validate() method on the object.

Once validation has occurred, if any have failed, the Errors collection on the object will be populated with friendly error messages about why the object is invalid. This provides a elegant approach to validating forms such as a sign-up web page:

  1. Create the sign-up form
  2. On post back assign each field to the properties of the user object
  3. Call Validate() on the object
  4. If validation fails bind the Errors collection to a repeater or similar on the web page
  5. If validation passes, persist the object

Further to this, every LightSpeed object implements the IDataErrorInfo interface which most .NET controls can use to show in real time if an object that is bound to it is valid. For example, if binding LightSpeed objects to a Grid control the red error icon will display in the field if the object is in an invalid state and provide a tool-tip describing the fault.

Create your own validator

LightSpeed makes the ValidateAttribute class available for developers to create their own validators (by creating new ValidationRule objects). This can be useful if a custom validator is needed in several model classes, allowing reuse of your custom validation code.

Alternatively, if custom validation is required for a given object, LightSpeed allows developers to override the OnValidate() method of the class:

protected override void OnValidate()
{
  if ((Contributor == null) && (ApprovedBy == null))
  {
    Errors.AddError("Must have one or the other");
  }
}

For more information regarding Validation in LightSpeed please read the Validation section of the user guide.

I hope this quick post about domain model validation has been useful in helping you write more robust solutions using LightSpeed. If you feel anything has been left out or have any questions please leave a comment.

John-Daniel Trask

kick it on DotNetKicks.com

We’re part of the Summer of Code 2.0

tag icon Tagged as Events, News

I’m pleased to announce that Mindscape is taking part in the Summer of Code 2.0 program this year.

This program allows university students to get experience working for a Wellington based IT company over the summer and provides the opportunity to learn about what is happening in the industry. Students can still sign up to take part on the site for another few days, if you’re a student doing IT in Wellington then check out the site and take part!

Events such as these don’t just happen and we would like to extend a thank-you to John Clegg who has been the driver behind this initiative. It is useful not just to the companies involved but also to the students who get opportunities that didn’t exist several years ago.

John-Daniel Trask

LightSpeed Identity Generation

tag icon Tagged as LightSpeed

How you identify your entities is often based on attributes of the business problem your solving with some personal preferences applied as well. For example, if you have a desire for ease of generation and don’t care about aesthetics you may choose GUIDs. Alternatively, if you want to use database generated integers, Sequence or Identity column may be the most appropriate.

Fortunately, LightSpeed supports several of the most popular identity generation methods. Our of the box we support:

  • KeyTable (default)
  • Sequence number
  • GUID (Globally Unique Identifier)
  • Identity column

KeyTable Identity Generation

KeyTable identity generation (Fowler PoEAA) uses a single table in your database that stores the current identity value. This allows LightSpeed to secure a block of identities (the default size being 10 and is configurable) and use them to set the identity value of newly created identities in your system. This works because every identity in the database, even in different tables, is unique.

KeyTable is a great identity method if you need high performance from you database as LightSpeed does not need to flush new entities to obtain identity values. That means less round trips to the database and therefore a more speedy application.

A small setup step is required to use KeyTable, you will need to run the KeyTable.sql schema file for your database to create the necessary table. This script can be found in your LightSpeed install directory.

This method uses an integer under the covers and therefore your entities should be implemented as using an integer:

public class MyModelClass : Entity<int>

Sequence Identity Generation

Sequence identities are similar to the KeyTable identity generation but are natively supplied by the database engine. This means using sequence identity generation is limited to databases that support it: Oracle and PostgreSQL.

As with the KeyTable identity generation method, Sequence provides a high performance mechanism for creating identities. And like the KeyTable identity generation, some setup is required. You need to run the Sequence.sql schema file for your database to create the necessary schema changes. This script can be found in your LightSpeed install directory.

This method uses an integer and therefore your entities should be implemented as using an integer:

public class MyModelClass : Entity<int>

Guid Identity Generation

Guid identities are great for systems that require strong uniqueness (think replication etc.) and are also extremely practical from a generation perspective.

However, while not an issue for many systems, GUIDs can have an impact on application aesthetics. If you need to place an identity as a query string parameter for instance you may not want a Guid displayed as they are reasonable ugly.

No surprises here, this method uses a Guid and therefore your entities should be implemented as using a Guid:

public class MyModelClass : Entity<Guid>

Identity Column Identity Generation

Identity Column identity generation supports auto-incrementing identity columns such as common in SQL Server. This approach, while supported, is not a recommended method for identity generation. Using this identity type means that LightSpeed cannot do optimized batching as well as it can with other identity methods. This is because LightSpeed needs to obtain the new identity value and update any in-memory associated objects before continuing the flush process.

This method also uses an integer and therefore your entities should be implemented as using an integer:

public class MyModelClass : Entity<int>

Configuring identity generation

Identity generation is set on the LightSpeed context, either in code or by using the LightSpeed configuration section in the .config file for your application.

LightSpeedContext.IdentityMethod = IdentityMethod.KeyTable;

or

LightSpeedContext.IdentityMethod = IdentityMethod.Guid;

or

LightSpeedContext.IdentityMethod = IdentityMethod.Sequence;

or

LightSpeedContext.IdentityMethod = IdentityMethod.IdentityColumn;

When using the KeyTable or Sequence types you can also fine tune the IdentityBlockSize. The default value is 10 and in most applications you will not need to change this default.

LightSpeedContext.IdentityBlockSize = 20;

For information about configuring these values using the config file for your application check out the online documentation for LightSpeed configuration.

If you have any questions or queries regarding identity generation with LightSpeed either post a comment or post into the LightSpeed forums and we will happily provide the answers you need.

Hope that helps,

John-Daniel Trask

Using LightSpeed from VB

tag icon Tagged as LightSpeed

A nice feature of .NET is the great interop between components written in different languages. LightSpeed is no exception and can be used just as easily from VB.NET. Here is a simple example modeling the concept of a Task that has an associated Status.

First we declare the Status entity. The VB code is almost exactly the same as it’s C# equivalent – with one minor difference; we need to manually call the LightSpeed Initialize method from our constructor. This is due to a difference in when field initializers are run in VB. In VB field initializers are run after base class constructors. :-S

' Status Entity
 
Imports Mindscape.LightSpeed
Imports Mindscape.LightSpeed.Validation
 
Public Class Status
  Inherits Entity(Of Integer)
 
  <ValidatePresence()> _
  Private _statusName As String
 
  Private ReadOnly _tasks As New EntityCollection(Of Task)()
 
  ' Need to explicitly initialize in VB
  Public Sub New()
    MyBase.New(False)
    Initialize()
  End Sub
 
  Public Property StatusName() As String
    Get
      Return _statusName
    End Get
    Set(ByVal value As String)
      [Set](_statusName, value, "StatusName")
    End Set
  End Property
 
  Public ReadOnly Property Tasks() As EntityCollection(Of Task)
    Get
      Return [Get](_tasks)
    End Get
  End Property
 
End Class

Next we can create the Task model:

' Task Entity
 
Imports Mindscape.LightSpeed
 
Public Class Task
  Inherits Entity(Of Integer)
 
  Private _statusId As Integer
 
  Private ReadOnly _status As New EntityHolder(Of Status)()
 
  ' Need to explicitly initialize in VB
  Public Sub New()
    MyBase.New(False)
    Initialize()
  End Sub
 
  Public Property Status() As Status
    Get
      Return [Get](_status)
    End Get
    Set(ByVal value As Status)
      [Set](_status, value)
    End Set
  End Property
 
  Public Property StatusId() As Integer
    Get
      Return _statusId
    End Get
    Set(ByVal value As Integer)
      [Set](_statusId, value, "StatusId")
    End Set
  End Property
 
End Class

Our database schema will of course look something like this:

 
CREATE TABLE STATUS
(
  Id INT NOT NULL PRIMARY KEY IDENTITY,
  StatusName NVARCHAR(32) NOT NULL
)
 
CREATE TABLE Task
(
  Id INT NOT NULL PRIMARY KEY IDENTITY,
  StatusId INT NOT NULL FOREIGN KEY REFERENCES STATUS(Id)
)
 
GO
 
INSERT INTO STATUS (StatusName) VALUES ('Completed')
INSERT INTO Task(StatusId) VALUES (SCOPE_IDENTITY())
INSERT INTO STATUS (StatusName) VALUES ('Pending')
INSERT INTO Task(StatusId) VALUES (SCOPE_IDENTITY())

Now we can go ahead and start using our model – In C# unit tests this time :-)

namespace Tests
{
  [TestFixture]
  public class UnitTests
  {
    // initialize LightSpeed
 
    static UnitTests()
    {
      LightSpeedContext.ConnectionString
        = @"Data Source=ORMapper;Initial Catalog=LightSpeedVBExample;Integrated Security=SSPI";
 
      LightSpeedContext.IdentityMethod = IdentityMethod.IdentityColumn;
      LightSpeedContext.Logger = new ConsoleLogger();
    }
 
    // transactional tests - all database changes rolled back after test
 
    private TransactionScope _transactionScope;
 
    [SetUp]
    public void SetUp()
    {
      _transactionScope = new TransactionScope();
    }
 
    [TearDown]
    public void TearDown()
    {
      Repository.CompleteUnitOfWork(false);
 
      _transactionScope.Dispose();
    }
 
    // unit tests against model
 
    [Test]
    public void FindTasks()
    {
      IList<Task> tasks = Repository.Find<Task>();
 
      Assert.AreEqual(2, tasks.Count);
 
      Task task = tasks[0];
 
      Assert.IsNotNull(task);
      Assert.IsNotNull(task.Status);
    }
 
    [Test]
    public void FindStatuses()
    {
      IList<Status> statuses = Repository.Find<Status>();
 
      Assert.AreEqual(2, statuses.Count);
 
      Status status = statuses[0];
 
      Assert.IsNotNull(status);
      Assert.AreEqual(1, status.Tasks.Count);
    }
 
    [Test]
    public void NewTask()
    {
      Task task = new Task();
      task.Status = Repository.FindOne<Status>(
        Entity.Attribute("StatusName") == "Pending");
 
      Repository.Add(task);
      Repository.SaveChanges();
    }
 
    [Test]
    public void NewStatus()
    {
      Status status = new Status();
      status.StatusName = "New Status";
 
      Repository.Add(status);
      Repository.SaveChanges();
    }
 
    [Test]
    public void UpdateTask()
    {
      IList<Task> tasks = Repository.Find<Task>();
 
      Task task = tasks[1];
 
      Assert.AreEqual("Pending", task.Status.StatusName);
 
      task.Status = Repository.FindOne<Status>(
        Entity.Attribute("StatusName") == "Completed");
 
      Repository.SaveChanges();
    }
 
    [Test]
    public void UpdateStatus()
    {
      IList<Status> statuses = Repository.Find<Status>();
 
      Status status = statuses[0];
 
      Assert.AreEqual("Completed", status.StatusName);
 
      status.StatusName = "Complete!";
 
      Repository.SaveChanges();
    }
 
    [Test]
    public void DeleteTask()
    {
      IList<Task> tasks = Repository.Find<Task>();
 
      Task task = tasks[1];
 
      Repository.Remove(task);
      Repository.SaveChanges();
    }
 
    [Test]
    public void DeleteStatus()
    {
      IList<Status> statuses = Repository.Find<Status>();
 
      Status status = statuses[0];
 
      Repository.Remove(status);
      Repository.SaveChanges();
    }
  }
}

Demos & Decks for Opinionated Frameworks Presentation

tag icon Tagged as General

Thanks to everyone who came to our Tech Ed Code Camp session on Sunday. Here’s the slide deck and online store demo from the presentation.