Entity mapping and CRUD

How an entity type maps to a table and its columns, and the methods that insert, update and delete entities — one at a time or in bulk, with optimistic concurrency.

Entity Mapping

You can configure how entity types are mapped to database tables and columns using either the fluent API or data annotation attributes.

Note

Mapping configured via the fluent API takes precedence over mapping configured via data annotation attributes. When a fluent mapping exists for an entity type, the data annotations on this entity type are ignored. When a fluent mapping exists for an entity property, the data annotations on this property are ignored.

Fluent API

You can use the fluent API to configure how entity types are mapped to database tables and columns.

DbConnectionExtensions.Configure(config =>
{
    config.Entity<Product>()
        .ToTable("Products");

    config.Entity<Product>()
        .Property(a => a.Id)
        .HasColumnName("ProductId")
        .IsIdentity()
        .IsKey();

    config.Entity<Product>()
        .Property(a => a.DiscountedPrice)
        .IsComputed();

    config.Entity<Product>()
        .Property(a => a.IsOnSale)
        .IsIgnored();

    config.Entity<Product>()
        .Property(a => a.Version)
        .IsRowVersion();

    config.Entity<User>()
        .Property(a => a.ConcurrencyToken)
        .IsConcurrencyToken();
});
Method Configures
Entity<TEntity>() Starts configuring the mapping for the entity type TEntity.
ToTable(tableName) The table where entities of that type are stored.
Property(propertyExpression) Starts configuring the mapping for one property.
HasColumnName(columnName) The column where the property is stored.
IsKey() The property is part of the key by which entities are identified.
IsIdentity() The property is generated by the database on insert.
IsComputed() The property is generated by the database on insert and update.
IsRowVersion() The property is a native database-generated concurrency token.
IsConcurrencyToken() The property is an application-managed concurrency token.
IsIgnored() The property is not mapped to a column.

Data annotation attributes

Entity mapping can also be configured with the standard attributes from System.ComponentModel.DataAnnotations and System.ComponentModel.DataAnnotations.Schema:

[Table("Products")]                                     // Table name; defaults to the type name
class Product
{
    [Key]                                               // Identifies the entity (usually the primary key)
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long Id { get; set; }

    [Column("ProductName")]                             // Column name; defaults to the property name
    public string Name { get; set; }

    [Timestamp]                                         // Native database-generated concurrency token
    public byte[] Version { get; set; }

    [ConcurrencyCheck]                                  // Application-managed concurrency token
    public byte[] ConcurrencyToken { get; set; }

    [NotMapped]                                         // Never read from or written to the database
    public decimal TotalPrice => this.UnitPrice * this.Quantity;
}
Attribute Effect
TableAttribute The table where entities of the type are stored. Without it, the entity type's name (excluding its namespace) is used.
ColumnAttribute The column where the property is stored. Without it, the property name is used.
KeyAttribute The property (or properties) by which entities of the type are identified.
DatabaseGeneratedAttribute The property is generated by the database. Unless DatabaseGeneratedOption.None is used it is skipped when inserting and updating, and its value is read back from the database onto the entity afterwards.
TimestampAttribute The property is a native database-generated concurrency token: it is checked during update and delete, which fail if the database value no longer matches the original, and it is read back after insert and update.
ConcurrencyCheckAttribute The property is an application-managed concurrency token, checked during update and delete the same way.
NotMappedAttribute The property is ignored entirely - never read from and never written to the database.

Entity manipulation methods

The examples below use these entity types:

class Product
{
    [Key]
    public long Id { get; set; }
    public long SupplierId { get; set; }
    public string Name { get; set; }
    public decimal UnitPrice { get; set; }
    public int UnitsInStock { get; set; }
    public bool IsDiscontinued { get; set; }
}

enum UserState { Active, Inactive, Suspended }

class User
{
    [Key]
    public long Id { get; set; }
    public DateTime LastLoginDate { get; set; }
    public UserState State { get; set; }
}

InsertEntities / InsertEntitiesAsync

Inserts a sequence of new entities into a database table.

connection.InsertEntities(GetNewProducts());

InsertEntity / InsertEntityAsync

Inserts a new entity into a database table.

connection.InsertEntity(GetNewProduct());

UpdateEntities / UpdateEntitiesAsync

Updates existing entities in a database table based on their keys.

var usersWithoutLoginInPastYear = connection.Query<User>(
    """
    SELECT  *
    FROM    Users
    WHERE   LastLoginDate < DATEADD(YEAR, -1, GETUTCDATE())
    """
);

foreach (var user in usersWithoutLoginInPastYear)
{
    user.State = UserState.Inactive;
}

connection.UpdateEntities(usersWithoutLoginInPastYear);

UpdateEntity / UpdateEntityAsync

Updates an existing entity in a database table based on its key.

if (user.LastLoginDate < DateTime.UtcNow.AddYears(-1))
{
    user.State = UserState.Inactive;
    connection.UpdateEntity(user);
}

DeleteEntities / DeleteEntitiesAsync

Deletes a sequence of entities from a database table based on their keys.

connection.DeleteEntities(products.Where(a => a.IsDiscontinued));

DeleteEntity / DeleteEntityAsync

Deletes an entity from a database table based on its key.

if (product.IsDiscontinued)
{
    connection.DeleteEntity(product);
}

Enum support

Enum values are sent to the database either as their string representation or as integers, controlled by EnumSerializationMode. Reading maps both representations back to the enum value automatically.

enum UserRole
{
  Admin = 1,
  User = 2,
  Guest = 3
}

class User
{
    [Key]
    public long Id { get; set; }
    public string UserName { get; set; }
    public UserRole Role { get; set; }
}

var user = new User { Id = 1, UserName = "adminuser", Role = UserRole.User };

connection.InsertEntity(user);
// Column "Role" contains the string "User" with EnumSerializationMode.Strings (the default),
// and the integer 2 with EnumSerializationMode.Integers.

The column type has to match the mode - NVARCHAR(200) for Strings, INT for Integers:

CREATE TABLE Users
(
    Id BIGINT,
    UserName NVARCHAR(255),
    Role NVARCHAR(200)   -- INT when EnumSerializationMode.Integers is used
)