Language preference:

Phase 2 / CalculationDescriptorAttribute

You can add properties into the Entity class that are not in the actual database. These custom DataFields usually have code that determines there value. When that code involves a numeric calculation, add the CalculationDescriptorAttribute. That tells the user interface how to make the DataField's value change interactively as the user edits other DataFields involved in the calculation.

Describe the calculation in the Expression property using a hybrid syntax of C# and VB.

Here are CalculationDescriptorAttributes associated with the Product Entity class. Notice that these properties are in the Entity class itself, not in the Entity Metadata class. The Entity Metadata class can still host CalculationDescriptorAttributes whose calculated value is determined by a View in the database.

public partial class Product : DESDA.ICustomizeDataField
   [DESDA.CalculationDescriptor("If (Not Discontinued) Then {UnitPrice * UnitsInStock} Else novalue")]
   public decimal ValueOfStock
         if (!Discontinued)
            if (UnitPrice.HasValue && UnitsInStock.HasValue)
               return UnitPrice.Value * UnitsInStock.Value;
         return 0.0M;

   [DESDA.CalculationDescriptor("if (!Discontinued) then {UnitPrice * (UnitsInStock + UnitsOnOrder)} else novalue")]
   public decimal ValueOfAvailableUnits
         if (!Discontinued)
            int vStock = 0;
            if (UnitsInStock.HasValue)
               vStock = UnitsInStock.Value;
            if (UnitsOnOrder.HasValue)
               vStock = vStock + UnitsOnOrder.Value;

            if (UnitPrice.HasValue)
               return UnitPrice.Value * vStock;

The Source Code Browser shows completed DataAnnotations. The CalculationDescriptorAttributes have been highlighted.

In the next topic, you'll learn how to show a subset of records when picking from a list of records in a foreign key.

Open the Source Code Browser (C# only)
Source Code Browser
/* ------------------------------------------------
 * Describes the Entity class for: Order_Detail
 * Classes:
 *    Order_Detail - Entity class. Edit it for validation and to customize metadata at runtime
 *    Order_DetailMetadata - Entity Metadata class. It contains the DataAnnotations.
 *    Order_DetailDAO - BLD DataAccessObject format version of a Data Access Object.
 * Requires .net 4.0 and these references:
 *    System.ComponentModel.DataAnnotations
 *    PeterBlum.DES
 *    PeterBlum.DES.DataAnnotations
 * Generated: 7/8/2011 4:19:12 PM
 * ------------------------------------------------*/
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using PeterBlum.DES.DAO.Attributes;
using PeterBlum.DES.DAO.EntityDAO;
using PeterBlum.DES.DAO.EntityFilters;
using PeterBlum.DES.DAO;
// Some members of this namespace have identical names to those in System.ComponentModel.DataAnnotations
// making it easy to switch from one to the other by adding the "DESDA." prefix
using DESDA = PeterBlum.DES.DataAnnotations;

namespace PeterBlum.WithDataAnnotations
   // --- ENTITY CLASS --------------------------------------------------
   /// <summary>
   /// Entity class. Edit it for validation and to customize metadata at runtime
   /// </summary>
   public partial class Order_Detail
/// <summary>
/// Calculated field showing the price after discount is applied.
/// The CalculationDescriptorAttribute lets the UI know how the calculation is
/// created so it can provide an interactive calculation as the user edits.
/// </summary>
      [DESDA.DisplayName("Order Price", ShortDisplayName="Price")]
      [DESDA.CalculationDescriptor("(UnitPrice * Quantity) * (1.0 - Discount)")]
      public decimal OrderPrice
            return (UnitPrice * Quantity) * Convert.ToDecimal(1.0 - Discount);

   }  // class Order_Detail

   // --- ENTITY METADATA --------------------------------------------------
   /// <summary>
   /// Entity Metadata class.
   /// Companion to the Order_Detail Entity class that contains the DataAnnotations
   /// on properties with the same names as those in the actual Entity class.
   /// These properties do not require their types to match those in the Entity class.
   /// An Entity Metadata class allows the Entity class to be regenerated without
   /// overwriting DataAnnotations.
   /// </summary>
   [DESDA.DisplayName("Order details")]
   [DESDA.TableRestriction("Admin", DESDA.DenyAccess.None)]
   [DESDA.TableRestriction("Customer", DESDA.DenyAccess.Edit | DESDA.DenyAccess.Delete | DESDA.DenyAccess.Insert)]
   public class Order_DetailMetadata
      [DESDA.DisplayName("Unit Price")]
      public object UnitPrice { get; set; }

      public object Quantity { get; set; }

/// <summary>
/// The Discount value is a percentage which is normally represented as 
/// an integer with 0 to 100, but in this case, its a decimal with
/// a range of 0 to 1. Make sure calculations are aware of this. (See Order_Detail.OrderPrice).
/// </summary>
      [DESDA.Range(0.0, 1.0)]
      public object Discount { get; set; }

   }  // class Order_DetailMetadata

   // --- BLD DATAACCESSOBJECT  --------------------------------------------------
   /// <summary>
   /// BLD DataAccessObject class for the Order_Detail Entity class.
   /// It provides CRUD actions. The parent class already has default
   /// methods for Update(), Insert(), Delete() and several queries.
   /// </summary>
   /// <remarks>
   /// <para>For documentation, see the BLD DataAccessObject section of the Business Logic User's Guide.</para>
   /// </remarks>
   [TableName("Order Details")]
   public class Order_DetailDAO : LINQtoSQLEntityDAO<Order_Detail>
      public Order_DetailDAO() : base(typeof(NorthWindDataContext)) { }
      public Order_DetailDAO(object pDataContext) : base(pDataContext) { }

/// <summary>
/// The Order Details table has two primary keys. The default SelectOne method
/// only takes one primary key. SelectOne is the default method name used
/// to retrieve a single record.
/// </summary>
/// <param name="pOrderID"></param>
/// <param name="pProductID"></param>
/// <param name="pArgs"></param>
/// <returns></returns>
      [SelectArgs(Filters=false, SortExpression=false, Paging=false)]
      [SelectMethodParameter(ActualName="orderID", AliasNames="Order",
         Description="The primary key to a record in the Orders table.")]
      [SelectMethodParameter(ActualName="productID", AliasNames="Product",
         Description="The primary key to a record in the Products table.")]
      public IEnumerable<Order_Detail> SelectOne(int orderID, int productID, SelectArgs args)
         args.AddEntityFilter(new CompareToValueEntityFilter("OrderID", DES.ConditionOperator.Equal, orderID));
         args.AddEntityFilter(new CompareToValueEntityFilter("ProductID", DES.ConditionOperator.Equal, productID));

         return ExecuteSelectCommand(args);

   }  // class Order_DetailDAO