Skip to content

Commit

Permalink
✨ 70 sentinel values (#78)
Browse files Browse the repository at this point in the history
* Added SentinelValues.csproj

* Added sentinel values
  • Loading branch information
danielmackay authored Dec 28, 2023
1 parent 5c3fbf8 commit d65678e
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 0 deletions.
6 changes: 6 additions & 0 deletions EfCoreSamples.sln
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnhancedBulkUpdateAndDelete
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HierarchyIds", "HierarchyIds\HierarchyIds.csproj", "{B18EB327-BF8D-4D61-B9F1-8392A9678F55}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SentinelValues", "SentinelValues\SentinelValues.csproj", "{EFE525A3-2D62-4A00-B354-12E1D0BE247D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -188,6 +190,10 @@ Global
{B18EB327-BF8D-4D61-B9F1-8392A9678F55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B18EB327-BF8D-4D61-B9F1-8392A9678F55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B18EB327-BF8D-4D61-B9F1-8392A9678F55}.Release|Any CPU.Build.0 = Release|Any CPU
{EFE525A3-2D62-4A00-B354-12E1D0BE247D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EFE525A3-2D62-4A00-B354-12E1D0BE247D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EFE525A3-2D62-4A00-B354-12E1D0BE247D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EFE525A3-2D62-4A00-B354-12E1D0BE247D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
22 changes: 22 additions & 0 deletions SentinelValues/Account.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace SentinelValues;

public class Account
{
public int Id { get; private set; }

// One way to use sentinel values is to use a nullable backing field
private int? _balance;
public int Balance
{
get => _balance ?? 100;
set => _balance = value;
}

// Another way to use sentinel values is to use a sentinel value that's been configured in ApplicationDbContext
public int Credits { get; set; } = -1;

public override string ToString()
{
return $"Id: {Id}, Balance: {Balance}, Credits: {Credits}";
}
}
20 changes: 20 additions & 0 deletions SentinelValues/ApplicationDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Common;
using Microsoft.EntityFrameworkCore;

namespace SentinelValues;

public class ApplicationDbContext : DbContext
{
public DbSet<Account> Accounts => Set<Account>();

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(DbConnectionFactory.Create("SentinelValues"));
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>().Property(a => a.Credits).HasDefaultValue(10).HasSentinel(-1);
}
}
24 changes: 24 additions & 0 deletions SentinelValues/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore;
using SentinelValues;

Console.WriteLine("Sentinel Values Sample");

using var db = new ApplicationDbContext();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();

var accounts = new List<Account>
{
new(),
new() { Balance = 0, Credits = 0 }, // NOTE: Without Sentinel values this would use the defaults of Balance=100, and Credits=10.
new() { Balance = 100, Credits = 10 },
};

db.Accounts.AddRange(accounts);
db.SaveChanges();

var allAccounts = db.Accounts.ToList();
Console.WriteLine("All Account");
allAccounts.ForEach(Console.WriteLine);

Console.ReadLine();
10 changes: 10 additions & 0 deletions SentinelValues/SentinelValues.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
15 changes: 15 additions & 0 deletions SentinelValues/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Sentinel Values and Database Defaults

EF Core can configure SQL Server to use Database defaults. For this to work, EF needs to know when NOT to send a value to the DB so that the DB can use the default value. It does this by using the `default` value of the .NET CLR type This works well for reference types, but not value types.

However, in some cases the CLR default value is a value valid to insert. For example, a default makes sense when creating a record. But what if you want to create a record with the default CLR value? Previously you couldn't. This is where Sentinel Values come in.

## Use Cases

- Inserting rows with default CLR values when the DB has a default value
- Correct EF Core behavior when using boolean `default` and `enum` default values
- Overriding defaults for other value types such as `int`, `DateTime`, etc.

## Resources

- [EF Core Docs | Sentinel Values and Database Defaults](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#database-defaults-for-enums)

0 comments on commit d65678e

Please sign in to comment.