You can define read-only calculated values and links in your MDriven model when a value or relationship must always reflect other domain-layer data.
Overview
A derived attribute is a calculated attribute. A derived association is a calculated association (also called a link). Both are read-only: MDriven evaluates their derivation expression instead of storing a value that you set directly.
For example, an Order can expose a derived ShippingCost based on its order items and the customer's country. A derived association can expose a shortcut to an object reached through a longer navigation path.
Derived values are not recalculated on every access. MDriven tracks the domain-layer values read by the derivation. When one of those values changes, the derived value is marked out of date. MDriven recalculates it when it is next read. This keeps the value current while avoiding unnecessary calculations.
When to use a derivation
Use a derived attribute or association when all of the following are true:
- The result is determined by other values or links in the model.
- You want one central definition that user interfaces and other model expressions can reuse.
- The value or link must be read-only.
| Need | Use | Example |
|---|---|---|
| A calculated scalar value | A derived attribute | Order.ShippingCost is calculated from item weights and country shipping rate.
|
| A calculated object link or collection | A derived association | lastSubPart returns the last object in subParts.
|
| A value that can be entered but translates into updates to stored values | Derived settable attribute | Enter an end time and update either the start time or duration. |
| A link that accepts assignment and performs additional updates | Derived settable association | Assign a selection and append an entry to a selection log. |
Create a derived attribute with OCL
OCL (Object Constraint Language) is the model expression language used to describe a derivation.
- Create an attribute on the class that should expose the calculated value. For this example, create
ShippingCostonOrder. - Set the attribute's AttributeMode to Derived.
- Enter the calculation in the attribute's DerivationOCL property.
- Use the derived attribute wherever a normal read-only attribute is needed, including in another derivation.
For an order with OrderItems, where some items are ProductThatNeedsShipping, the shipping cost can be expressed as:
self.OrderItems->FilterOnType(ProductThatNeedsShipping).Weight->sum
* self.Customer.Country.CostToShipHerePerKilogramThis expression totals the weights of products that need shipping, then multiplies that total by the shipping rate for the customer's country.
You can build another derived value from it without repeating the shipping calculation:
self.OrderItems.Product.Price->Sum() + self.ShippingCostIn this example, a change to an order item, a product weight, the customer, the customer's country, or the country's shipping rate can make ShippingCost out of date. When TotalCost is read after such a change, it uses the current ShippingCost result.
Create a derived association
A derived association uses the same principle as a derived attribute, but its expression returns an object or a collection of objects rather than a scalar value. Use it to create a named shortcut through your object graph or to isolate a reusable navigation.
For example, a derived single-link association named lastSubPart can return the last item in the subParts association:
self.subParts->lastKeep derived associations focused on navigation. When an expression becomes difficult to read, split it into clearly named derived associations rather than repeating a long navigation in every expression.
Gotcha: derived links and let
In OCL and EAL, let temporarily holds a reference, not a fixed object value. A derived association can therefore be reevaluated after a change to data on which it depends.
This code does not preserve the original last subpart:
let lp = self.lastSubPart in
(
self.subParts.add(newPart);
newPart.Name = 'copy of ' + lp.Name
)After newPart is added, lastSubPart evaluates to the new object, so lp.Name does not refer to the prior last subpart. Convert the derived single link to a set and take its first object before changing the collection:
let lp = self.lastSubPart->first in
(
self.subParts.add(newPart);
newPart.Name = 'copy of ' + lp.Name
)For a fuller explanation of this behavior, see Documentation:Let and Derived associations.
How MDriven keeps derived values current
A derivation subscribes to the domain-layer values it reads. When a subscribed value changes, MDriven marks the derived result out of date. The next read causes evaluation and establishes the subscriptions needed for the new result.
This behavior is important for user interfaces. For example, a ViewModel showing Order.ShippingCost can show the updated cost after a user changes an item weight or customer without placing the calculation in the UI.
Derived attributes can themselves be used by other derivations. Define reusable business concepts, such as ShippingCost, once in the model and then use their names in later expressions.
Implement a derivation in generated-code projects
In MDriven for Visual Studio, you can implement a derived attribute in C#, VB.NET, or Delphi.NET instead of entering OCL.
- Set AttributeMode to Derived.
- Leave DerivationOCL empty.
- Generate code.
- Find the generated derivation partial-method stub in the
.eco.csfile. - Implement that partial method in your non-generated class file, such as
Order.cs.
Do not edit files named *.eco.cs; code generation replaces them. For a numeric ShippingCost, an implementation can calculate the total shipping weight and apply the customer's country rate:
partial void ShippingCostDerive(ref double res)
{
res = 0;
double totWeight = 0;
foreach (OrderItem i in OrderItems)
{
if (i is ProductThatNeedsShipping)
totWeight += (i as ProductThatNeedsShipping).Weight;
}
if (Customer != null && Customer.Country != null)
res = totWeight * Customer.Country.CostToShipHerePerKilogram;
}
MDriven's auto-subscription mechanism observes domain-layer access while the code derivation runs and uses those accesses to determine what can invalidate the derived value. You do not need to manually describe those subscriptions.
Limits and design guidance
Do not derive from passing time
MDriven cannot subscribe to time values such as DateTime. A value based on the current time does not become out of date merely because time passes. Treat time-dependent calculations separately and ensure they are refreshed by an explicit application action when required.
Keep the dependency in the domain layer
MDriven can track changes that are part of the domain layer and that the derivation reads. Put the business calculation in the derivation rather than duplicating it in a ViewModel or UI control.
Use read-only derivations for safe transformations
A derived attribute does not alter persisted data. You can use one to test a new type or transformation while retaining the original attribute and its data. For example, derive a decimal price from an existing integer price before making a permanent data-type change. See Documentation:Attribute or Data Type Conversion for the conversion workflow.
Use a settable derivation when input must update stored data
A standard derived attribute or association is read-only. If a user must assign a value to the derived member and that assignment must update attributes or links, use derived settable attributes or derived settable associations.
