Entities File Settings

Settings for scaffolding out an entity for your Web API.

Entities

A list of database entities in your project. These entities are added as dbsets to your database context, so they translate 1:1 to tables in your database. With that said, if you want to create an aggregate, you can scaffold out an entity and update it accordingly to reflect that aggregate yourself (e.g. remove the db set).

Entity Properties

NameRequiredDescriptionDefault
NameYesThe name of the entityNone
PropertiesYesA list of properties assigned to your entity described in the entity properties section.None
FeaturesYesThis is a list of features that you want to add for a particular entity.None
PluralNoThe plural of the entity name, if needed (e.g. Cities would prevent Citys)_Entity Name pluralized with Humanizer.
LambdaNoThe value to use in lambda expressions for this entity.First letter of the entity name.
TableNameNo (Yes if you want to set a Schema)The name of the table in the database.Defaulted to the singular Name.
SchemaNoThe schema to use in the database.None, which will equate to dbo in SqlServer, public in Postgres, or mydb in MySQL

TableName is required if you want to set a schema

Properties

A list of properties that can be assigned to an entity.

NameRequiredDescriptionDefault
NameYesThe name of the propertyNone
TypeNoThe data type for the property. These are not case sensitive and they can be set to nullable with a trailing ?. All standard C# data types can be used here. String arrays are also supported string[].string
IsRequiredNoWhen true, the property will be set as required in the database.false
true for primary key
ColumnNameNoThe database field name for the given property.None
CanManipulateNoWhen set to false, you will not be able to update this property when calling the associated endpoint. When set to false, the property will be able to be established when using the POST endpoint, but will not be able to be updated after that. This is managed by the DTOs if you want to manually adjust this.true
false for primary key
DefaultValueNoAllows you to add a default value to a property. Note that it should be entered exactly as you'd want it, so a string would be something like "My Default Value", a bool might be true, and an int might be 0None
ForeignEntityNameNoCaptures the name of the entity this property is linked to as a foreign key.None
ForeignEntityPluralNoThe plural value for the foreign entity, if applicable.Entity Name appended with an s
IsChildRelationshipNoOnly used when establishing a relationship. Designates whether or not a relationship property is being set from the child entity.false
SmartNamesNoA list of strings used to create a smart enum property. Each string denotes an option for the smart enum. See the smart enum example below for details. Property attributes like filtering and sorting can be used if desired.None
IsLogMaskedNoCan be used to mark a property as masked when logged.false
AsValueObjectNoCan be used to designate a property as a specific kind of value object. See more below.None
ValueObjectNameNoThe name of the value object. This is only used if designating a value object.None
ValueObjectPluralNoThe plural of the value object. This is only used if designating a value object.None

Features

NameRequiredDescriptionDefault
TypeYesThe type of feature you want to add. The accepted values are AdHoc, Job, GetRecord, GetList, GetAll, DeleteRecord, UpdateRecord, AddRecord, and CreateRecord (same as AddRecord but available as an alias in case you can't remember!)None
IsProtectedNoDetermines whether or not the feature is protected with an authorization policy attribute.false
PermissionNameNoA named permission to check against before executing a particular feature.None

GetRecord, GetList, GetAll, DeleteRecord, UpdateRecord, AddRecord, and AddListByFk are all standard CRUD features along with their endpoints and associated tests.

ℹ DTOs, validators, profiles and functional test routes associated to all features will be added regardless of whether or not a given feature was added. This is due to the complexity of all the minute management around handling these so dynamically. Feel free to delete whatever you're not using.

AddListByFk Feature

The AddListByFk will let you add a list or batch of records using a foreign key. For example, you can add a list of ingredients for a recipe, based on the id of a recipe.

NameRequiredDescriptionDefault
BatchPropertyNameYesThe name of the property on the foreign entity you are doing a batch add on.None
BatchPropertyTypeYesThe data type of the the property you are doing the batch add on. This is for a FK property, so probably a Guid or int.None
ParentEntityYesThe name of the parent entity that the FK you using is associated to. For example, if you had a FK of EventId, the parent entity might be Event. Leave null if you're not batching on a FK.None
ParentEntityPluralYesThe plural of the FK entity. Leave null if you're not batching on a FK.None

Hangfire Jobs

You can also add features that will be setup for a Hangfire job instead of a MediatR handler using the Job feature type.

NameRequiredDescriptionDefault
NameYesThe name of the feature. This is the name of the feature's method and would generally be something like AddCustomer or GetCustomer.None
EntityPluralNoThe plural name of the entity that the feature will be added to (should match the name in the Domain directory). You can leave this blank to add it to the Domain directory directly.[ENTITYNAME]s

Ad Hoc Features

You can also add ad hoc skeletons with the AdHoc type. For AdHoc features, there's a bit more that needs to be done. In addition to Type above, you'll want to provide the below:

NameRequiredDescriptionDefault
NameYesThe name of the feature. This is the name of the feature's method and would generally be something like AddCustomer or GetCustomer.None
EntityPluralNoThe plural name of the entity that the feature will be added to (should match the name in the Domain directory). You can leave this blank to add it to the Domain directory directly.[ENTITYNAME]s
ResponseTypeNoIs the type of response that this command will return. This could be any C# type that you want, or even a custom type if you want, but in that case, you'll need to add a using statement for it manually.bool

You can also add any of the above features after the fact using the add feature command.

Value Objects

Craftsman can handle a few different types of value objects directly on your properties. Let's look at the different types of value objects and how they work.

Built In Value Objects

Craftsman has a few built in value objects below:

  • Email
  • MonetaryAmount
  • Percent

Note that Address is also a built in value object, but is a complex value object that must be handled manually post-scaffolding for the time being.

To use these value objects, you can simply add the value above in the AsValueObject property to your property and set it to the value object you want to use. For example:

Entities:
  - Name: Author
    Features:
      - Type: GetList
    Properties:
      - Name: Name
      - Name: Ownership
        AsValueObject: Percent
      - Name: Email
        AsValueObject: Email
      - Name: Ingredient
        Features:
          - Type: GetList
        Properties:
          - Name: Name
          - Name: AverageCost
            AsValueObject: MonetaryAmount

Simple Value Objects

You can also create your own simple value objects. These are value objects that are a single property of whatever type you have designated for that property.

For example, a Rating value object might be a single int? property. To create a simple value object, you can use the AsValueObject property and set it to Simple to great a value object called Rating that will back your property. For example:

Entities:
  - Name: Recipe
    Features:
    #...
    Properties:
      - Name: Title
      - Name: Rating
        Type: int?
        AsValueObject: Simple

You can also set the name of the value object by using the ValueObjectName and ValueObjectPlural properties. ValueObjectPlural will try to pluralize for you automatically and get it right in most cases, but you can override it if you want. For example:

Entities:
  - Name: Recipe
    Features:
    #...
    Properties:
      - Name: Title
      - Name: Rating
        Type: int?
        AsValueObject: Simple
        ValueObjectName: UserRating
        ValueObjectPlural: UserRatings

If you want to re-use an existing simple value object, you can do so by adding it as you did originally.

Smart Value Objects

You can also use the SmartNames property to create value objects backed by smart enum options like so:

Entities:
  - Name: Recipe
    Features:
    #...
    Properties:
      - Name: Title
      - Name: Visibility
        SmartNames:
          - Public
          - Friends Only
          - Private

You can customize these with a custom name as well:

Entities:
  - Name: Recipe
    Features:
    #...
    Properties:
      - Name: Title
      - Name: Visibility
        SmartNames:
          - Public
          - Friends Only
          - Private
        ValueObjectName: RecipeVisibility
        ValueObjectPlural: RecipeVisibilities

If you want to re-use an existing smart enum value object, you can do so by adding it as you did originally. You can use the same bulleted options or just do a single placeholder value. For example:

Entities:
  - Name: New Concept
    Features:
    #...
    Properties:
      - Name: Visibility
        SmartNames:
          - Placeholder
        ValueObjectName: RecipeVisibility # only needed if you did a custom name
        ValueObjectPlural: RecipeVisibilities # only needed if you did a custom name

Keys and Relationships

There are a couple of important pieces of information to know about settings keys for scaffolding:

  1. As v0.12+, you do not need to add a primary key property to your entities as they will automatically be inherited from the BaseEntity.
  2. Foreign keys do not need to be added. See how relationships are managed below.

Relationships can be set up by adding a special property that looks something like this:

- Name: Product
  Relationship: manyto1
  ForeignEntityName: Product
  ForeignEntityPlural: Products

The different type of relationships are:

  • 1tomany where the principal entity is the 1 and the foreign entity is the many. For example, a Customer can have many Orders.
  • manyto1 where the principal entity is the many and the foreign entity is the 1. For example, a Product can have many Categories.
  • manytomany where the principal entity is the entity that the property is added to. For example, a Product can have many Tags.
  • 1to1 where the principal entity is the entity that the property is added to. For example, a Customer can have one CustomerSettings.
  • self where the principal entity is the same as the foreign entity. For example, a Category can have many SubCategories.

Note that child entities are managed from the principal entity. So for example, if you have a Customer that manage Order entities through something like Customer.AddOrder(), the Customer would be the principal and you would use a 1tomany on the Customer. If you wanted to manage the Customer from an Order entity with something like Order.SetCustomer(), you would use a manyto1 on the Order entity. The main question you should ask yourself when adding a relationship and picking which entity to put it on should be 'where should this concept be managed from?'. This will help you establish the principal owner of the relationship.

If you're doing an add entities command and your principal entity is already in the project, you can still add the relationship by adding an IsChildRelationship: true.

💡 Note that you'll need to flip the relationship type when adding a child relationship. So if you would normally add a 1tomany Order relationship on a Customer, when adding it as a child on Order, you'll want to make the Customer relationship a manyto1 relationship.

Notice the difference in the examples below, with the first being the principal based example and the second being the child based example.

- Name: Customer
  Features:
    - Type: GetList
    - Type: GetAll
    - Type: GetRecord
    - Type: AddRecord
    - Type: UpdateRecord
    - Type: DeleteRecord
    - Type: Job
      Name: PerformCustomerMigration
  Properties:
    - Name: FirstName
    - Name: LastName
    - Name: Email
    - Name: DateJoined
      Type: DateOnly?
    - Name: LoyaltyPoints
      Type: int?
    - Name: Orders
      Relationship: 1tomany
      ForeignEntityName: Order
      ForeignEntityPlural: Orders
- Name: Order
  Features:
    - Type: GetList
    - Type: GetRecord
    - Type: AddRecord
    - Type: UpdateRecord
    - Type: DeleteRecord
  Properties:
    - Name: OrderDate
      Type: DateOnly?
    - Name: Status
      CanManipulate: false
    - Name: Customer
      Relationship: manyto1
      IsChildRelationship: true
      ForeignEntityName: Customer
      ForeignEntityPlural: Customers

Entity Example

Here's a larger example that has all the different kinds of relationships.

DomainName: ECommerceApi
BoundedContexts:
  - ProjectName: ECommerce
    Port: 5300
    DbContext:
      ContextName: ECommerceDbContext
      DatabaseName: ECommerceDB
      Provider: Postgres
    Entities:
      - Name: Customer
        Features:
          - Type: GetList
          - Type: GetAll
          - Type: GetRecord
          - Type: AddRecord
          - Type: UpdateRecord
          - Type: DeleteRecord
          - Type: Job
            Name: PerformCustomerMigration
        Properties:
          - Name: FirstName
          - Name: LastName
          - Name: Email
          - Name: DateJoined
            Type: DateOnly?
          - Name: LoyaltyPoints
            Type: int?
          - Name: Orders
            Relationship: 1tomany
            ForeignEntityName: Order
            ForeignEntityPlural: Orders
          - Name: Settings
            Relationship: 1to1
            ForeignEntityName: CustomerSettings
            ForeignEntityPlural: CustomerSettings
      - Name: Product
        Features:
          - Type: GetList
          - Type: GetRecord
          - Type: AddRecord
          - Type: UpdateRecord
          - Type: DeleteRecord
        Properties:
          - Name: ProductName
          - Name: Description
          - Name: Price
            Type: decimal?
          - Name: Stock
            Type: int?
          - Name: Category
            Relationship: manyto1
            ForeignEntityName: Category
            ForeignEntityPlural: Categories
          - Name: Tags
            Relationship: manytomany
            ForeignEntityName: Tag
            ForeignEntityPlural: Tags
      - Name: Category
        Plural: Categories
        Features:
          - Type: GetList
          - Type: GetRecord
          - Type: AddRecord
          - Type: UpdateRecord
          - Type: DeleteRecord
        Properties:
          - Name: CategoryName
          - Name: ParentCategory
            Relationship: self
            ForeignEntityName: Category
            ForeignEntityPlural: Categories
      - Name: Order
        Features:
          - Type: GetList
          - Type: GetRecord
          - Type: AddRecord
          - Type: UpdateRecord
          - Type: DeleteRecord
        Properties:
          - Name: OrderDate
            Type: DateOnly?
          - Name: Status
            CanManipulate: false
          - Name: OrderItems
            Relationship: 1tomany
            ForeignEntityName: OrderItem
            ForeignEntityPlural: OrderItems
      - Name: OrderItem
        Features:
          - Type: GetList
          - Type: GetRecord
          - Type: AddRecord
          - Type: UpdateRecord
          - Type: DeleteRecord
        Properties:
          - Name: Quantity
            Type: int
          - Name: Product
            Relationship: manyto1
            ForeignEntityName: Product
            ForeignEntityPlural: Products
      - Name: Review
        Features:
          - Type: GetList
          - Type: GetRecord
          - Type: AddRecord
          - Type: UpdateRecord
          - Type: DeleteRecord
        Properties:
          - Name: Rating
            Type: int
          - Name: Comment
          - Name: Product
            Relationship: manyto1
            ForeignEntityName: Product
            ForeignEntityPlural: Products
          - Name: Customer
            Relationship: manyto1
            ForeignEntityName: Customer
            ForeignEntityPlural: Customers
      - Name: Tag
        Features:
          - Type: GetList
          - Type: GetRecord
          - Type: AddRecord
          - Type: UpdateRecord
          - Type: DeleteRecord
        Properties:
          - Name: Name
          - Name: Description
      - Name: CustomerSettings
        Plural: CustomerSettings
        Features:
          - Type: GetList
          - Type: GetRecord
          - Type: AddRecord
          - Type: UpdateRecord
          - Type: DeleteRecord
        Properties:
          - Name: PreferredDayOfArrival

Composite Keys

For the time being, composite keys can not be created using Craftsman commands. With that said, you are more than welcome to scaffold out an entity with one of the keys and then modify the generated entity, repository, tests, etc. to accommodate that composite key.

Policies

If using auth, you can protect features with a particular permission. In the example below, I have a permission for doing read overall and default permissions for each destructive feature.

Entities:
  - Name: Recipe
    Features:
      - Type: GetList
        IsProtected: true
        PermissionName: CanReadRecipes
      - Type: GetRecord
        IsProtected: true
        PermissionName: CanReadRecipes
      - Type: AddRecord
        IsProtected: true
      - Type: UpdateRecord
        IsProtected: true
      - Type: DeleteRecord
        IsProtected: true
    Properties:
      - Name: Title
        Type: string
      - Name: Directions
        Type: string