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 aConventions
namespace. Also, if you use Model Sync, you should make yourBaseTable
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 thedbo.AspNetUsers
table. Since that table uses a GuidId
property, I add my ownint
identity columnUserId
because I hate working with Guids. Subsequently, I refer to thatUserId
column throughout the rest of my model. This is the one case where I have to use the[Identity]
attribute to override theId
convention. Note also I don't include every column fromdbo.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 aspartial
, then add yourIDbConnection
-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 hasIValidate
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 standardSystem.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'sId
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 beint
orlong
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.