Skip to content

Entity registration

MoonStorm edited this page Jun 3, 2024 · 60 revisions

Table of Contents

You have several options for registering your entities with the library, setting the table name and describing the properties mapped to db columns.

Code first

This is the preferred method for registering your database entities and relationships. FastCrud is using the well known attributes from the System.ComponentModel.DataAnnotations assembly and, for the most part, adheres to their official meaning.

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

[Table("Employees")]
public partial class EmployeeDbEntity
{
  [Key]
  [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  [Column("Id", Order = 1)]
  public int UserId { get; set; }

  [Key]
  [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  [Column(Order = 2)  
  public Guid EmployeeId { get; set; }

  [Dapper.FastCrud.DatabaseGeneratedDefaultValue]
  public Guid KeyPass { get; set; }

  public string LastName { get; set; }

  public string FirstName { get; set; }

  [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
  public string FullName { get; set; }

  public DateTime? BirthDate { get; set; }

  [NotMappedAttribute]
  public string IgnoreMe { get; set; }

  [ForeignKey(nameof(Workstation)]
  public long? WorkstationId { get; set; }		

  public WorkstationDbEntity? Workstation {get; set;}
}
  • The Table attribute specifies the mapping to the db table.
  • The Key attribute is used to mark the properties that represent the primary key, and in this particular case, we're dealing with a composite key. Update, Get, Delete will always use the properties decorated with a Key attribute to identify the entity the operation is targeting. The library also makes the assumption that the primary keys are unique.
  • The Column attribute maps a property to a different db column name. It can also be used to specify an order number in case of a composite key, when the entity is referenced by other entities. In this case both the key properties and the foreign properties must specify the same order.
  • The ForeignKey attribute is used to set up a child-parent relationship between the EmployeeDbEntity and the WorkstationDbEntity. Notice how it targets a property on the current entity. FastCrud uses this information to figure out what foreign entity to target. More on this in the JOINs section.
  • NotMapped marks properties the library should ignore altogether.
  • These are all the attributes that control the inclusion or exclusion of properties from various operations:
INSERT Post-INSERT UPDATE Post-UPDATE
regular mapped property used ignored used ignored
NotMapped ignored ignored ignored ignored
DatabaseGenerated (DatabaseGeneratedOption.Identity) ignored updated ignored ignored
DatabaseGenerated (DatabaseGeneratedOption.Computed) ignored updated ignored updated
DatabaseGeneratedDefaultValue ignored updated used ignored

All the attributes are classic data model annotations, however DatabaseGeneratedDefaultValue attribute was introduced by FastCrud, to allow for columns to be ignored if they're being set by default by the database. It is our recommendation though that the non-identity type default values coming from the database to be ignored, and have these values set directly on the entities prior to their insertion. If you still insist on using this attribute and you're using our T4 template, set IgnoreColumnDefaultValues to false in your template configuration and re-generate your entities. Just for this scenario you'll need the Dapper.FastCrud installed as well as a NuGet package in the project that contains the template (for referencing the custom attribute). We'll talk about the T4 template later.

If you want to use other attributes, or change the default behavior, you can always override the default library conventions used by the library.


Code first using external metadata classes (not available in .NET Standard 2.0)

You can provide additional attributes for your entities in separate metadata classes using the MetadataType attribute.

In the following example, attributes are added in a separate class, leaving the original database entity completely intact.

EmployeeDbEntity.cs

public partial class EmployeeDbEntity
{
  public int UserId { get; set; }
}

EmployeeDbEntityMetadata.cs

[MetadataType(typeof(EmployeeMetadata))]
public partial class Employee
{
  private class EmployeeMetadata
  {
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("Id")]
    public object UserId { get; set; } // just a marker, the type is not important
  }
}

Fluent registration

You can also register the entities at runtime.

OrmConfiguration.RegisterEntity<BuildingDbEntity>()
    .SetTableName("Buildings")
    .SetProperty(building => building.BuildingId, 
                 propMapping => propMapping.SetPrimaryKey()
                                           .SetDatabaseGenerated(DatabaseGeneratedOption.Identity)
                                           .SetDatabaseColumnName("Id"))
    .SetProperty(building => building.Name, 
                 propMapping => propMapping.SetDatabaseColumnName("BuildingName"))
    .SetProperty(building => building.Description)
    .SetParentChildrenRelationship( building => building.Workstations,
                                    workstation => workstation.BuildingId);

Or, if you want the library to give you a hand:

    OrmConfiguration.GetDefaultEntityMapping<Building>()
        .SetTableName("Buildings")
        .SetProperty(building => building.BuildingId,
             prop => prop.SetPrimaryKey().SetDatabaseGenerated(DatabaseGeneratedOption.Identity));

The default auto-mapping already contains the registrations for the simple typed properties. In many cases you won't even need to set up your entities at all. Don't forget that you can also override the default library conventions.

Here are a few examples for the most common scenarios.

Database Column Fluent Registration
primary key SetPrimaryKey()
identity column SetDatabaseGenerated(DatabaseGeneratedOption.Identity)
computed column SetDatabaseGenerated(DatabaseGeneratedOption.Computed)
different column name SetDatabaseColumnName(column_name)
trigger on UPDATE RefreshOnUpdates(true)
trigger on INSERT RefreshOnInserts(true)
default value IncludeInInserts(false).RefreshOnInserts(true

Database first approach via a T4 template from a database schema (limited to LocalDb and SQL Server)

UPDATE: It is currently impossible to run the T4 generator under Rider (see https://youtrack.jetbrains.com/issue/RIDER-112510). Visual Studio 2022 seems to be running fine for the time being, however the support for T4 templates is dwindling in there as well. It's very difficult to maintain the model generator in the current form, reason why we might be looking at dropping this feature soon. We recommend switching to a code-first approach, the cleaner and most intuitive way to describe your entities out of all the methods we support.

Entity generation can be performed by installing the NuGet package Dapper.FastCrud.ModelGenerator and by creating your own (*Config.tt) files that use the generic template provided in this package. Use the sample config template for inspiration. Do not modify the GenericModelGenerator.tt as that will prevent future upgrades via NuGet. For this approach, you'll need a LocalDb or an MsSql server that contains the schema. By default the script looks into the app.config file for a connection string, but we would strongly advise creating a separate .config file and adjusting your *Config.tt accordingly.

For example, let's assume we want a class library project to hold our db entities.

  • Create a new class library project
  • Install the Dapper.FastCrud.ModelGenerator package into your project. That will create a folder named Models, containing a GenericModelGenerator.tt file and a SampleModelGeneratorConfig.tt file.
  • Make a copy of the SampleModelGeneratorConfig.tt config file. Rename it as you wish. Let's say we've named it MyModelGeneratorConfig.tt and we've placed it in the same location as the sample config file. Make sure that the copied file has BuildAction set to None and CustomTool set to TextTemplatingFileGenerator in the properties of that file.
  • Remove the TextTemplatingFileGenerator value of the Custom Tool property for the SampleModelGeneratorConfig.tt file. That is just a sample and we don't want for it to be active.
  • Add a new Application Configuration File inside the Models folder through the IDE's Add New Item. Let's assume we named it MyModels.config. Open it up and add a new connection string to its configuration section, same as you'd do in a regular app.config or web.config file.
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <connectionStrings>
          <add name="EntityGeneration"  
               providerName="System.Data.SqlClient"   
               connectionString="Data Source=LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\TestDatabase.mdf;Initial Catalog=TestDatabase;Integrated Security=True" />
        </connectionStrings>
    </configuration>
  • Adjust the parameters inside the MyModelGeneratorConfig.tt file:
...
    ConnectionStringName = "EntityGeneration"; // if only one conn string is present in the .config file, this can also be blank
    ConfigPath = @"MyModels.config"; // relative path
...
  • Save the MyModelGeneratorConfig.tt file. This action will trigger the generation of the entities. A new file MyModelGeneratorConfig.cs will be automatically created and linked to the .tt file. Errors are also reported here. Another way to trigger the same action would be by right clicking on the file and selecting Run Custom Tool.
  • You should never modify manually the cs file generated. In case you've made changes to the database design, just re-run the entity generation and then use a refactoring tool of you choice to fix the rest of your code. If FastCrud was used correctly throughout, you'll encounter compilation errors rather than running into nasty issues at runtime.

Keep in mind that you can have multiple class libraries or even different namespaces inside the same library holding your entities, each with their own Config.tt file, thus helping you split the database layer based on functionality or on the contrary, can bring under the same roof entities mapped to tables living in different databases.