Archives

Archives / 2004 / November
  • Retina.NET (my open source ORM) is now ALPHA3

    Well, today i have uploaded the Retina.NET alpha3 into the GotDotNet workspace, including source code, binaries, documentation and a new sample application (the well known ASP.NET sample from Microsoft "IbuySpy Portal" ported to Retina.NET).

    This version includes the following:

    *- Several bug-fixes and other small changes in Retina.Core.
    *- Fixes in the MetadataCache to support loading of circular entity references.
    *- Fixes in the transaction management to allow manual control of transaction lifetime.
    *- All EntityPersist classes now generates fully qualified column names in queries.
    *- A new demo application is included that is a port of the well known Microsoft ASP.NET sample IbuySpy.

    Read more...

  • Framework.Messaging & WS-ReliableMessaging

    Well, our new messaging subsystem has reached "Release Candidate" status. After some initial stress and stability testing we have a messaging subsystem that is far superior to what we have in production now, and we have some more work to do yet.

    Read more...

  • Retina.NET QuickStart (alpha3)

    Retina.NET ORM 

     

    Quick Start

     

     

     


    Table of Contents

    Retina.NET ORM... 1

    Table of Contents. 2

    Overview.. 3

    Using Retina.NET. 3

    Key Retina.NET Concepts. 4

    Entities. 4

    Data Types. 4

    Constraints. 4

    Triggers. 4

    Criteria. 4

    Hello Retina.NET. 5

    Creating the entity. 5

    Persisting the entity. 5

    Using Retina.NET Attributes. 9

    Implementing Constraints. 12

    Overview.. 12

    Creating Constraints. 12

    Adding Constraints. 13

    Conditional Constraints. 13

    Summary. 13

    Implementing Triggers. 14

    Overview.. 14

    Creating Triggers. 14

    Adding Triggers. 14

    Summary. 15

    Implementing Criteria. 16

    Overview.. 16

    Creating Criteria. 16

    Using Criteria. 17

    Summary. 17

    Implementing Child Collections. 18

    Hello Retina.NET Code. 21

    Implementing Attributes Code. 22

    Implementing Constraints Code. 23

    Implementing Triggers Code. 25

    Implementing Criteria Code. 27

     

     


    Overview

    The aim of Retina.NET is to greatly simplify the reoccurring problem of how to validate and persist objects. In every project we see how much code is repeated for the same tasks, and as much as we want to reuse, we have to keep rolling our own code again and again to do the same tasks. These tasks include persisting objects, validating objects, editing objects and searching for objects.

     

    Retina.NET is a set of classes that makes these tasks, amongst others, really straightforward. If you create an object, it will create the database table for you. If you say an object has a collection of children then Retina.NET will maintain that for you. If you specify that an object has certain constraints then Retina.NET will ensure that those constraints are always checked. The aim is to free up the developer to concentrate on the domain problem and to let Retina.NET take care of the mundane tasks.

     

    Using Retina.NET

    At its simplest, all a developer has to do to take advantage of Retina.NET is follow the instructions below:

     

    1. Your persistent object must be a Subclass from “Retina.Core.Entity” class.
    2. Model object values as primitive public fields or properties
    3. Provide a parameter less constructor
    4. Define the Primary Key for the Entity marking it with an attribute.

     

    If the above conditions are met then the object is ready to be persisted via Retina.NET. For a full example see the “Hello Retina.NET” tutorial.

     


    Key Retina.NET Concepts

    Entities

    An entity represents a business domain object, e.g. a User type of entity has a set of fields. The values in these fields represent the state of the business object. An entity maps onto a single table in the store. The name of the table is either specified using an attribute or inferred from the entity type name.

     

    Data Types

    Each field of an entity has an associated data type which defines what values that field may contain. If any field contains an invalid value then the entity cannot be saved to the data store.

    The data type of a field is either specified using an attribute or inferred from type of the field. Retina.NET comes with a set of in built data types covering primitives, enumerations and nullable types. If necessary, custom data types can easily be created, e.g. postal code.

     

    Constraints

    Each entity type has a set of constraints that must be met for the entity to represents a valid business object, e.g. inception date must be before the expiration date. An entity cannot be saved to the data store if any of these constraints are broken.

    Constraints are implemented within the entity class as delegates. These delegates are added to the base object in the entity constructor as can be seen in the constraints tutorial.

     

    Triggers

    Each entity type has a set of triggers which represent business logic that is called when certain actions occur, e.g. a welcome email is sent when a new user is added.

    Triggers are implemented within the entity class as delegates. These delegates are added to the base object in the entity constructor, as can be seen in the triggers tutorial.

     

    Criteria

    A criteria object defines a set of entities of a certain type. This set can be parameterized by passing arguments to the constructor of the criteria object. Criteria object instances can be used to retrieve and delete sets of entities, as can be seen in the criteria tutorial.

     


    Hello Retina.NET

    The following tutorial shows how to create, persist and retrieve a simple entity using Retina.NET. The full listing can be found at the end of this document.

    Creating the entity

    Import the Sisyphus namespace:

    using Retina.Core;

    Subclass Entity:

    public class MyEntity : Entity {

    Model object values as primitive public fields:

      public Guid Id; 
      public bool BoolField; 
      public int IntField; 
      public double DoubleField; 
      public string StringField; 
      public DateTime DateTimeField; 

    Provide a parameter less constructor:

      public MyEntity() {}

    Mark the primary key with an attribute:

      [FieldStorage(IsKey = true, IsIdentity = true, IdGeneratorType = typeof(IdGeneratorGuid))]
      public Guid Id;

     

    This attribute tells Retina.NET that the “id” field is the Primary Key for the Entity, that is automatically generated (identity = true) and the class used to generate the value for the field is “IdGeneratorGuid” (A built in Retina.NET class that generates Guids).

    Persisting the entity

    The entity is now ready to be persisted so now we need to create a test class. First we import the Retina namespaces we will be using:

    using Retina.Core;
    using Retina.Core.Department;
    using Retina.DataStore;
    using Retina.DataStore.MsSql;

    For now we will gloss over the purposes of the namespaces and move on to the test code itself. We will create a new class and a Main method:

    public class MyEntityTest {
     public static void Main() {}
    }

    Next we will instantiate an IDataStore implementation, in this case one for talking to SQL Server:

        IDataStore store = new SqlDataStore("localhost", "Retina");

    This example assumes you have a database called "Retina" already created on the local machine and we are connecting to the MS SQL Server located on the local machine (localhost) using integrated security (we aren’t supplying any credentials to connect). These arguments to the constructor may have to be changed for your environment.

    We could go into SQL Server and create a table for the entity. However, for this example we will let Retina.NET do the work for us by using a data store builder:

        IDataStoreBuilder storeBuilder = new SqlDataStoreBuilder(store);
        storeBuilder.CreateTable(typeof(MyEntity), true);

    The above lines of code create a data store builder for SQL Server and then use it to create a table in which to store instances of our entity. The Boolean value indicates whether or not to drop any existing tables first.

    We now have a table in which to store our entities, if you go into SQL Server you will find a table called 'MyEntity' has been created. 

    You will see that Retina.NET has created a column for each field and has created a primary key column called 'Id'. In Retina.NET all entities must have a Primary Key, in this case a GUID ID field.

    The next step is to create an instance of our entity and populate it with some values:

        MyEntity myEntity = new MyEntity();
        myEntity.BoolField = true;
        myEntity.IntField = 666;
        myEntity.DoubleField = 3.1415926;
        myEntity.StringField = "Hello Retina.NET";
        myEntity.DateTimeField = DateTime.Now;

    There is nothing special about this stage and you may well want to add constructors to make this process somewhat less verbose.

    In Retina.NET, one way to do client data store interaction is done through an IDataStoreBroker implementation. There are two provided depending whether or not you want to use COM+ or not. These are found in the Retina.Core.Enterprise and Retina.Core.Department namespaces respectively. In our example we will use the departmental broker because we don’t need the advanced transaction management that COM+ provides:

       IDataStoreBroker broker = new DataStoreBroker(store);

    We can now use the broker to persist the entity in the store specified:

       broker.Persist(myEntity); 

    If we look in SQL Server database we should see new row created.

    We can also use the broker we created to retrieve entities from the store. The following line retrieves a copy of the original entity:

        MyEntity myRetrievedEntity = (MyEntity)broker.Retrieve(typeof(MyEntity), myEntity.Id);

    As you can seem, the broker needs to be told the type and ID of the entity you want to retrieve. If the entity cannot be found then null is returned. The method returns value needs to be cast as the method returns the base of all entities, Entity.

    To test whether we managed to retrieve any values we shall add a simple test:

        Console.WriteLine("myRetrievedEntity.StringField = '{0}'", myRetrievedEntity.StringField);

    With a bit of luck and the wind in a favorable direction, the following output should appear:

    myRetrievedEntity.StringField = 'Hello Retina.NET'   

    That concludes this tutorial, in which we took advantage of the default behavior of Retina.NET. The default behavior can be override to specify table and column names, field data types, entity constraints and business logic.

     


    Using Retina.NET Attributes

    You will come across parent-child relationship in all but the most trivial of systems. Retina.NET supports such relationships through the use of ChildCollection objects.

    To show how this is done we will turn to the tried and trusted Order and Order Line example.

    First we create an OrderLine entity with nothing out of the ordinary:

            [TypeStorage(TableName = "tbOrderLines")]
            public class OrderLine : Entity {
                   [FieldStorage(IsKey = true, IsIdentity = true, IdGeneratorType = typeof(IdGeneratorGuid))]
       public Guid Id;
                   public Guid OrderId;
                   public string Description = "";
                   public double Price = 0;
                   public int Quantity = 0;
             }

    Next we create an Order object and we specify that it contains a collection of order lines by having a field of type ChildCollection as follows:

    [TypeStorage(TableName = "tbOrders")]
    public class Order : Entity {
            [FieldStorage(IsKey = true, IsIdentity = true, IdGeneratorType = typeof(IdGeneratorGuid))]
            public Guid Id;
            public string Description = String.Empty;
            [ChildStorage(ChildType = typeof(OrderLine), 
            ChildPosition = "OrderLineIndex")]
            [ForeignKey(ParentFieldName = "OrderId", ChildFieldName = "Id")]
            public ChildCollection OrderLines;
     }

    The child collection must have a corresponding ChildStorage attribute which specifies the type of entity that the collection contains and a foreign key attribute that describe the field or fields that relates the child entity with his parent.

    The following code shows an order being created with 10 order lines:

            Order order = new Order();
            order.Description = "A special treat";
            for (int i = 0; i < 10; i++) {
                   OrderLine orderLine = new OrderLine();
                   orderLine.Description = "OrderLine " + i;
                   orderLine.Price = i + 1.99;
                   orderLine.Quantity = 11 - i;
                   order.OrderLines.Add(orderLine);
             }
             order = (Order)broker.Persist(order);

    As you would expect, retrieving an object with children by default retrieves all the children and grandchildren and so on. This is not always the desired behavior and so you can specify a maximum depth when doing entity retrieval. For example the code below retrieves the order, but not the order lines:

            order = (Order)broker.Retrieve(typeof(Order), order.Id, 0);

    A runtime exception is thrown if any attempt is made to access any property of the order lines collection apart from IsLoaded. This can be used to implement lazy loading as follows:

            if (!order.OrderLines.IsLoaded) {
                   order.OrderLines.Load(broker);
            }

    The order lines collection can now be used as the following example shows:

            ArrayList removedOrderLineList = new ArrayList();
            foreach (OrderLine orderLine in order.OrderLines) {
                  if (orderLine.Quantity % 2 == 0) {
                         removedOrderLineList.Add(orderLine);
                   }
                   else {
                           orderLine.Price *= 100;
                   }
            }
             foreach (OrderLine removedOrderLine in removedOrderLineList) {
                   order.OrderLines.Remove(removedOrderLine);
            }
             order = (Order)broker.Persist(order);

    If the quantity is even then the line is removed, otherwise the price is multiplied by 100. When the order is persisted then the removed lines are deleted from the store and the updated items are saved.

    Deleting an entity causes all their children to be deleted and, of course, their grandchildren too and so on:

            m_DeptBroker.Delete(order);

    We have now seen how to declare and use a child collection in Retina.NET. We have also seen that you can have hierarchies of arbitrary depth and that the application code can take advantage of lazy loading.


    Implementing Constraints

    Overview

    For an entity to be saved to a data store all its fields must contain valid values and all constraints on the object must hold. A constraint is any method that implements the signature defined by the Constraint delegate shown below:

    public delegate BrokenConstraint 
        Constraint(ITxnContext txnContext, object appContext);

    The full code for this tutorial can be found at the end of this document.

    Creating Constraints

    Continuing the user example from the attributes tutorial, if we wanted to ensure that the password entered matched the confirmation password then we would need to create a method with a signature matching Constraint, i.e.:

        public BrokenConstraint PasswordIsConfirmed(ITxnContext txnContext, object appContext) {

    The first parameter is the current transaction context. This is created by Retina.NET when a method is invoked on a broker and is passed through to all business logic. It provides the methods to interact with the data store and any interactions are automatically enlisted in the current transaction.

    The second parameter is an application specific object that is passed through from the code which invoked the broker method. This can be used to store application specific context information, e.g. the current user, session ID etc.

    Next we need to code the test we wish to perform:

          if (this.Password == this.PasswordConfirmation) {
            return null;
          }
          else {
            return new BrokenConstraint("Password and confirm not match");
          }

    As you can see, the two password values are compared and null is returned if they are equal. This indicates that the constraint holds. If the two password values are not equal then a new broken constraint object is returned containing an explanatory message.


    Adding Constraints

    To ensure the constraint is called, it must be added to the entity's set of constraints via the AddConstraint method on the Entity base class. This is done in the constructor as follows:

        public User() {
          AddConstraint(new Constraint(PasswordIsConfirmed));
        }

    Now a Validation exception will occur when an attempt is made to save the user and the password fields do not match.

    Conditional Constraints

    By default a constraint will be checked when an entity is inserted or updated. This is not always the required behavior. For example, say we wanted to check that a username was not taken. This only applies when an entity is being inserted, as it would be true for an update if the username was not changed. This can be done by specifying the conditions using the PersistenceAction flags, e.g.:

          AddConstraint(new Constraint(UsernameIsUnique), PersistenceAction.Insert);

    Summary

    We have seen how we can create constraints within the entity class and how to add them so Retina.NET will check them when an entity is persisted. In addition, we also saw how we can tell Retina.NET when a constraint should be checked.


     

    Implementing Triggers

    Overview

    Triggers are pieces of business logic associated with actions occurring on an entity, e.g. a welcome email being sent when a new user is created. A trigger is any method that implements the signature defined by the Trigger delegate shown below:

    public delegate void Trigger(ITxnContext txnContext, object appContext);

    The full code for this tutorial can be found at the end of this document.

    Creating Triggers

    Continuing with the user example used in the previous tutorials, we can use triggers to populate and maintain the field values representing the created and modified dates: first we need to add a couple of methods with the delegate signature:

        public void PopulateDateCreated(ITxnContext txnContext, object appContext) {
          this.DateCreated = DateTime.Now;
        }
        public void PopulateDateLastUpdated(ITxnContext txnContext, object appContext) {
          this.DateModified = DateTime.Now;
        }

    The parameters are the same as those for constraints, the first being the current transaction context and the second being an application specific context object.

    Remember that all interactions through the transaction context occur under the same transaction as the current action. This means that if the business logic in a trigger causes an exception, for example, it tries to persist an entity with invalid values the current action will also be rolled back.

    Adding Triggers

    Once the triggers have been created, Retina.NET must be told to when to invoke them. Triggers fall into two categories, pre-triggers and post-triggers. Pre-triggers are called before and entity is inserted or updated, while post-triggers are called after and entity is inserted, updated or deleted.

    In our example we want our triggers to be called before the entity is persisted and we want the date created trigger to only being called when the entity is inserted. The following code is placed in the constructor:

          AddPreTrigger(new Trigger(PopulateDateCreated), PersistenceAction.Insert);
          AddPreTrigger(new Trigger(PopulateDateLastUpdated));

    Now whenever our entity is persisted the triggers will fire and ensure the date fields are populated.

    Summary

    In this tutorial we saw how business logic can be encapsulated in the entity class in the form of triggers. We saw how we can specify both the action and the time that a trigger is called.


    Implementing Criteria

    Overview

    A criteria object defines a set of entities of a certain type. Criteria objects encapsulate how Retina.NET is to retrieve the set of entity IDs that match the criteria. These criteria can be parameterized by having arguments in the criteria constructor. The full code for this tutorial can be found at the end of this document.

    Creating Criteria

    There is no restriction on where criteria classes are declared. In this example we will declare the class within the scope of the class to which it relates. With this approach all code relating to the entity remains in the same place:

    The following declares a criteria object that defines a set of users with a specific user name:

        public class ByUsername : Criteria {
          public ByUsername(string username) : base(
            typeof(User), "Username", Criteria.Equal, username ) {}
        }

    In this case the base constructor takes three parameters (the Criteria class has many more constructors to use for more complex criteria). The first is always the type of entity in the set and the other parameters are the comparison and value to use that will return a set of matching entity IDs.

    Another way to including Criteria objects inside the Entity definition is using static methods, as showed below:

    public static Criteria ByUserName(string username)

          {

                return new Criteria(typeof(User), "Username", Criteria.Equal, username);

          }

    More complex Criteria can be defined also by combining more Criteria objects, as showed in the following example:

     

    public static Criteria ByNameAndClass(string Name, string Class)

          {

                return new Criteria(

                      typeof(User),

                      new Criteria("Name", Criteria.Equal, Name),

                      Criteria.And,

                      new Criteria("Class", Criteria.Equal, Class));

          }

    Using Criteria

    We shall use our new criteria to enforce the constraint that all user names should be unique. As mentioned previously, criteria objects can be used to retrieve and delete sets of objects. In this case all we want to know is if there is at least one match, we don't want the overhead of instantiating any entities:

        public User() {
           AddConstraint(new Constraint(UsernameIsUnique), PersistenceAction.Insert);
         }
         public BrokenConstraint UsernameIsUnique(ITxnContext txnContext, object appContext) {
            bool usernameExists = txnContext.RetrieveMatchExists(appContext, new User.ByUsername(this.Username));
           if (usernameExists) {
              return new BrokenConstraint(
               "The username " + this.Username + " is not unique", "Username");
          }
             else {
               return null;
          }
         }

    The constraint uses the current transaction context to see if there are any users that have the same name. If there is such a user then we return a broken constraint saying what was wrong.

    Summary

    We have seen how to create and use criteria objects. The example showed how they can be created within the same class to which they relate, thereby keeping all relevant code together. We also saw how criteria objects can be used to implement a constraint.


    Implementing Child Collections

    You will come across parent-child relationship in all but the most trivial of systems. Retina.NET supports such relationships through the use of ChildCollection objects.To show how this is done we will turn to the tried and trusted Order and Order Line example.

    First we create an OrderLine entity with nothing out of the ordinary:

            [TypeStorage(TableName = "tbOrderLines")]
            public class OrderLine : Entity {
                 [FieldStorage(IsKey = true, IsIdentity = true, IdGeneratorType = typeof(IdGeneratorGuid))]
     public Guid Id;
                 public Guid OrderId;
                 public string Description = "";
                 public double Price = 0;
                 public int Quantity = 0;
           }

    Next we create an Order object and we specify that it contains a collection of order lines by having a field of type SpfChildCollection as follows:

    [TypeStorage(TableName = "tbOrders")]
    public class Order : Entity {
           [FieldStorage(IsKey = true, IsIdentity = true, IdGeneratorType = typeof(IdGeneratorGuid))]
           public Guid Id;
           public string Description = String.Empty;
           [ChildStorage(ChildType = typeof(OrderLine), ChildPosition = "OrderLineIndex")]
           [ForeignKey(ParentFieldName = "OrderId", ChildFieldName = "Id")]
           public ChildCollection OrderLines;
    }

    The child collection must have a corresponding ChildStorage and ForeignKey attributes which specifies the type of entity that the collection contains and the foreign key field on the child entity.

     

    The following code shows an order being created with 10 order lines:

            Order order = new Order();
            order.Description = "A special treat";
             for (int i = 0; i < 10; i++) {
                   OrderLine orderLine = new OrderLine();
                   orderLine.Description = "OrderLine " + i;
                   orderLine.Price = i + 1.99;
                   orderLine.Quantity = 11 - i;
                   order.OrderLines.Add(orderLine);
             }
             order = (Order)broker.Persist(order);

    As you would expect, retrieving an object with children by default retrieves all the children and grandchildren and so on. This is not always the desired behavior and so you can specify a maximum depth when doing entity retrieval. For example the code below retrieves the order, but not the order lines:

            order = (Order)broker.Retrieve(typeof(Order), order.Id, 0);

    A runtime exception is thrown if any attempt is made to access any property of the order lines collection apart from IsLoaded. This can be used to implement lazy loading as follows:

            if (!order.OrderLines.IsLoaded) {
                   order.OrderLines.Load(broker);
            }

    The order lines collection can now be used as the following example shows:

            ArrayList removedOrderLineList = new ArrayList();
            foreach (OrderLine orderLine in order.OrderLines) {
               if (orderLine.Quantity % 2 == 0) {
                   removedOrderLineList.Add(orderLine);
               }
               else {
                   orderLine.Price *= 100;
              }
           }
             foreach (OrderLine removedOrderLine in removedOrderLineList) {
                   order.OrderLines.Remove(removedOrderLine);
             }
             order = (Order)broker.Persist(order);

    If the quantity is even then the line is removed, otherwise the price is multiplied by 100. When the order is persisted then the removed lines are deleted from the store and the updated items are saved.

    Deleting an entity causes all their children to be deleted and, of course, their grandchildren too and so on:

            m_DeptBroker.Delete(order);

    We have now seen how to declare and use a child collection in Retina.NET. We have also seen that you can have hierarchies of arbitrary depth and that the application code can take advantage of lazy loading.

     


    Hello Retina.NET Code

    using System;
     
    using Retina.Core;
    using Retina.Core.Department;
    namespace MyNameSpace {
    public class MyEntity : Entity {
        [FieldStorage(IsKey = true, IsIdentity = true, IdGeneratorType =  typeof(IdGeneratorGuid))]
        public Guid Id;
        public bool BoolField;
        public int IntField;
        public double DoubleField;
        public string StringField;
        public DateTime DateTimeField;
        public MyEntity() {
        }
      }
     
      public class MyEntityTest {
         public static void Main() {
           IDataStore store = new SqlDataStore("localhost", "Retina");
           IDataStoreBuilder storeBuilder = new SqlDataStoreBuilder(store);
           storeBuilder.CreateTable(typeof(MyEntity), true);
           MyEntity myEntity = new MyEntity();
           myEntity.BoolField = true;
          myEntity.IntField = 666;
           myEntity.DoubleField = 3.1415926;
           myEntity.StringField = "Hello Retina.NET";
          myEntity.DateTimeField = DateTime.Now;
           IDataStoreBroker broker = new DataStoreBroker(store);
           broker.Persist(myEntity);
           MyEntity myRetrievedEntity = (MyEntity)broker.Retrieve(typeof(MyEntity), myEntity.Id);
           Console.WriteLine("myRetrievedEntity.StringField = '{0}'", myRetrievedEntity.StringField);
         }
      }
    }
    Implementing Attributes Code
    using System;
    using Retina.Core;
    namespace Examples {
    public enum UserTitle {
        Unknown = 0,
        Mr = 1,
        Miss = 2,
        Mrs = 3
      }
     
      [TypeStorage(TableName = "tbUsers")]
      public class User : Entity {
       [FieldStorage(IsKey = true, IsIdentity = true, IdGeneratorType =  typeof(IdGeneratorGuid))]
       public Guid Id;
       [StringDataType(16)]
       public string Username;
      [StringDataType(16)]
      public string Password;
      [FieldStorage(IsLoaded = false, IsSaved = false)]
       public string PasswordConfirmation;
       [FieldStorage("UserTitle")] 
       public UserTitle Title;
      [StringDataType(32)]
       public string Forename;
       [StringDataType(64)]
       public string Surname;
       [StringDataType(256)]
       public string AddressLine1;
      [StringDataType(256, true)]
       public string AddressLine2;
       [StringDataType(256, true)]
       public string AddressLine3;
       public DateTime DateCreated;
       public DateTime DateModified;
       public User() {}
      }
    }

    Implementing Constraints Code

     
    using System;
    using Retina.Core;
     
    namespace Examples {
      public enum UserTitle {
        Unknown = 0,
        Mr = 1,
        Miss = 2,
        Mrs = 3
      }
     
      [TypeStorage(TableName = "tbUsers")]
      public class User : Entity {
     
        [FieldStorage(IsKey = true, IsIdentity = true, IdGeneratorType =  
          typeof(IdGeneratorGuid))]
        public Guid Id;
     
        [StringDataType(16)]
        public string Username;
     
        [StringDataType(16)]
        public string Password;
     
        [FieldStorage(IsLoaded = false, IsSaved = false)]
        public string PasswordConfirmation;
     
        [FieldStorage("UserTitle")]
        public UserTitle Title;
     
        [StringDataType(32)]
        public string Forename;
     
        [StringDataType(64)]
        public string Surname;
     
        [StringDataType(256)]
        public string AddressLine1;
     
        [StringDataType(256, true)]
        public string AddressLine2;
     
        [StringDataType(256, true)]
        public string AddressLine3;
     
        public DateTime DateCreated;
     
        public DateTime DateModified;
     
     
     
        public User() {
          AddConstraint(new Constraint(PasswordIsConfirmed));
        }
     
        public BrokenConstraint PasswordIsConfirmed(ITxnContext txnContext, object appContext) {
     
          if (this.Password == this.PasswordConfirmation) {
            return null;
          }
          else {
            return new BrokenConstraint(
                   "Password and confirmation do not match");
          }
        }
      }
    }

     


    Implementing Triggers Code

     

    using System;
     
    using Retina.Core;
     
    namespace Examples {
     
      public enum UserTitle {
        Unknown = 0,
        Mr = 1,
        Miss = 2,
        Mrs = 3
      }
     
      [TypeStorage(TableName = "tbUsers")]
      public class User : Entity {
     
        [FieldStorage(IsKey = true, IsIdentity = true, IdGeneratorType =  
          typeof(IdGeneratorGuid))]
        public Guid Id;
     
        [StringDataType(16)]
        public string Username;
     
        [StringDataType(16)]
        public string Password;
     
        [FieldStorage(IsLoaded = false, IsSaved = false)]
        public string PasswordConfirmation;
     
        [FieldStorage("UserTitle")]
        public UserTitle Title;
     
        [StringDataType(32)]
        public string Forename;
     
        [StringDataType(64)]
        public string Surname;
     
        [StringDataType(256)]
        public string AddressLine1;
     
        [StringDataType(256, true)]
        public string AddressLine2;
     
        [StringDataType(256, true)]
        public string AddressLine3;
     
        public DateTime DateCreated;
     
        public DateTime DateModified;
     
        
     
     
     
    public User() {
          AddConstraint(new Constraint(PasswordIsConfirmed));
     
          AddPreTrigger(new Trigger(PopulateDateCreated), 
                   PersistenceAction.Insert);
     
          AddPreTrigger(new Trigger(PopulateDateLastUpdated));
     
        }
     
        public BrokenConstraint UsernameIsUnique(ITxnContext txnContext, 
            object appContext) {
     
          bool usernameExists 
              = txnContext.RetrieveMatchExists(appContext, 
                   new User.ByUsername(this.Username));
     
          if (usernameExists) {
           return new BrokenConstraint(
            "The username " + this.Username + " is not unique", "Username");
          }
          else {
            return null;
          }
        }
     
        public void PopulateDateCreated(ITxnContext txnContext, 
            object appContext) {
          this.DateCreated = DateTime.Now;
        }
     
        public void PopulateDateLastUpdated(ITxnContext txnContext, 
            object appContext) {
          this.DateModified = DateTime.Now;
        }
     
      }
     
    }

    Implementing Criteria Code

    using System;
     
    using Retina.Core;
     
    namespace Examples {
     
      public enum UserTitle {
        Unknown = 0,
        Mr = 1,
        Miss = 2,
        Mrs = 3
      }
     
      [TypeStorage(TableName = "tbUsers")]
      public class User : Entity {
     
        [FieldStorage(IsKey = true, IsIdentity = true, IdGeneratorType =  
          typeof(IdGeneratorGuid))]
        public Guid Id;
     
        [StringDataType(16)]
        public string Username;
     
        [StringDataType(16)]
        public string Password;
     
        [FieldStorage(IsLoaded = false, IsSaved = false)]
        public string PasswordConfirmation;
     
        [FieldStorage("UserTitle")]
        public UserTitle Title;
     
        [StringDataType(32)]
        public string Forename;
     
        [StringDataType(64)]
        public string Surname;
     
        [StringDataType(256)]
        public string AddressLine1;
     
        [StringDataType(256, true)]
        public string AddressLine2;
     
        [StringDataType(256, true)]
        public string AddressLine3;
     
        public DateTime DateCreated;
     
        public DateTime DateModified;
     
     
        public class ByUsername : Criteria {
          public ByUsername(string username) : base(
            typeof(User), Criteria.Equal, username ){}
        }
     
        public User() {
     
          AddConstraint(new Constraint(UsernameIsUnique), PersistenceAction.Insert);
     
        }
     
        public BrokenConstraint UsernameIsUnique(ITxnContext txnContext, object appContext) {
     
          bool usernameExists 
              = txnContext.RetrieveMatchExists(appContext, 
                   new User.ByUsername(this.Username));
     
          if (usernameExists) {
           return new BrokenConstraint(
            "The username " + this.Username + " is not unique", "Username");
          }
          else {
            return null;
          }
        }
      }
    }

    Read more...