Model Class Best Practices and Suggestions - adamfoneil/Dapper.CX GitHub Wiki

When using Dapper.CX, here are some practices I recommend. The SampleApp.Models project in this repo has examples of things I'll go into below:

Solution Architecture

  • Have a BaseTable class that your models inherit from. Use this to ensure your models have a consistent Id property everywhere as well as to implement any system-wide conventions, such as audit tracking columns that you want to ensure are always updated. I like to put this in a Conventions namespace. Also, if you use Model Sync, you should make your BaseTable class abstract so that Model Sync won't try to create it.

  • The table in your app that stores user account info should implement IUserBase. See UserProfile. You can see in my example, I use the [Table] attribute to indicate that my class maps to the dbo.AspNetUsers table. Since that table uses a Guid Id property, I add my own int identity column UserId because I hate working with Guids. Subsequently, I refer to that UserId column throughout the rest of my model. This is the one case where I have to use the [Identity] attribute to override the Id convention. Note also I don't include every column from dbo.AspNetUsers -- only the ones that are relevant in my app. Note also I use this Timestamp to get correct local times based on the user's time zone.

  • Put your model classes in a separate .NET Standard project whose name ends with .Models and make sure that project has no dependencies other than AO.Models. Example: SampleApp.Models. There are a couple reasons for this. One, if you ship your model classes with an API client project, clients will want a minimal dependency footprint. Two, if you use my Model Sync app to implement your SQL Server tables, that will work only if your model class DLL has no other dependencies.

  • Many interfaces in AO.Models accept IDbConnection arguments, such as IValidate and IGetRelated. To take advantage of these without adding unwanted dependencies at in your .Models project, create your model classes as partial, then add your IDbConnection-dependent implementations in a .Services project. Then link your model classes into your .Services project. (Link source in Visual Studio by right-clicking a folder in Solution Explorer, click Add Existing, and in the dialog box, click Add As Link.) Example: SampleApp.Services. See in particular, the Models folder, which has IValidate implementations of model classes. Splitting your models into partials in this way gives you the best of both worlds -- a lightweight models project along with a full-featured service layer. Your .App project should then refer only to the .Services project, not the .Models project.

Primary Keys, Foreign Keys, and Unique Constraints

I use Model Sync to implement my database tables, and it recognizes certain attributes to create primary and foreign keys and unique constraints. The following guidance assumes you're using Model Sync:

  • Use the [Key] attribute (from the standard System.ComponentModel.DataAnnotations namespace) on one or more properties to define the model's primary key. Example with composite key: WorkspaceUser. Example with a single property: Workspace. If you omit the [Key] attribute, then the model's Id property or whatever is marked with the [Identity] attribute will be the primary key.

  • To create foreign keys, add the [References] attribute to the foreign key property, passing the type of the primary (that is, the referenced) model class. Example: Item.WorkspaceId and WorkspaceUser.UserId. The foreign key property type must match referenced model class's identity type. This will either be int or long since those are the supported identity types. (Nullable versions of those are okay.)

  • To create a unique constraint, add the [UniqueConstraint] attribute to the class, passing names of the properties that form the constraint. Unfortunately, I don't have an example of this in use in the Sample App at the moment.