Benevia Core AI Documentation Index

This directory (docs/public) is the canonical documentation source for Benevia Core.

Start here

  1. Top-level documentation index - Follow relative links recursively. Prefer documents in docs/public over package README files.
  2. First create the entity model.
  3. Then create write event driven logic.

Topic map

Guidance for creating a model

  • Properties that should not be set by users should only have a getter and be computed.
  • When extending the functionality of an existing entity, use partial classes in your feature's folder. Do not just add the property to a class in another feature. See Feature in a box
  • Create properties for anything you expect the user to see so that computations are done on the server rather than on the client.
  • Mark properties Virtual if they do not have a setter and can be computed deterministically. See Virtual properties
  • Only use OppositeSideCollection when you need a collection on the other side. If the collection could be large, then use paged. When in doubt, use paged. See Relationships
  • Most entities should have a NaturalKey attribute that specifies one unique property. See Entity basics
  • Consider adding an Index attribute for dates or other properties that will tune database filtering. Indexes are automatically generated for reference properties and GUIDs. Entity Framework index attributes are used. See Properties
  • Whenever a property is added, consider whether a database upgrade subscriber needs to be written. See DatabaseUpgrade/README.md

Guidance for which events to use when writing logic on the server

Where to write the business logic

  • Logic is written in a separate class suffixed with BL. Write logic for a single feature in one class. This could include logic for multiple entities.
  • Almost all business logic should be written by subscribing to an event.
  • Avoid controllers and helper classes.

Defaulting properties

  • As much as possible, try to default required properties.
  • Static values should be defaulted with the DefaultValue attribute on a property. See Properties
  • When a property is defaulted on entry of another property, then use the Default event. See Default event
  • When a property is defaulted based on settings or the current date or time, then use the Added event. See Added event
  • There may be times when defaulting the value should not be done until saving. In this case, use the PreSave event. Check to see if it is still empty and set the value. See PreSave event

React to a value that was set on a property

  • Use Compute when it is clear what properties affect another property's value. See Compute event
  • Use Compute even when the property has a setter.
  • Properties that should not be changed by the user should only have a getter and should use the Compute event.
  • Prefer Compute over Changed. Use Changed when creating new entities or setting many properties. See Changed event
  • Use PreSave if side effects should occur only at the time of saving. Most times immediate side effects should occur, so use this sparingly. See PreSave event

Validating data

  • For required values, set the Required attribute on the property. Do not write logic. See Properties
  • Avoid validation if you can automatically correct the value using the AutoCorrect event. See AutoCorrect event
  • When validating the value of one property, use the property Validate event. See Validate event
  • When validating the combination of multiple properties, use the entity ValidateOnSave event. See Entity validate event
  • When validating a data type, use the data type Validate event. See Data type validate event

When to use methods

  • Use Methods when there is a button on the client that the user needs to run. See Methods
  • Use Methods when you want to expose functionality to other modules.

Similar logic in multiple entities

  • Consider using interfaces when there is significant duplicate logic between two entities or when there is a trait that should be applied to multiple entities.
  • Avoid helper classes to accomplish this. See Interfaces

Properties that are read-only based on other properties

  • Properties like this must have a setter but should use ReadOnly to determine when they are writable. See ReadOnly event
  • The expression in the read-only condition should avoid using args.Context.GetEntities() or any other non-performant operation.