diff --git a/readme.md b/readme.md
index afcac020..26fe90fd 100644
--- a/readme.md
+++ b/readme.md
@@ -658,6 +658,89 @@ Will result in the following verified file with all data in the database:
+## Model diagram
+
+An `IModel` can be verified as a [Mermaid ER diagram](https://mermaid.js.org/syntax/entityRelationshipDiagram.html), using [DbToMermaid](https://github.com/SimonCropp/DbToMermaid). EF Core only.
+
+
+
+```cs
+await Verify(data.Model);
+```
+snippet source | anchor
+
+
+Result: [ModelDiagramTests.Markdown.verified.md](/src/Verify.EntityFramework.Tests/ModelDiagramTests.Markdown.verified.md)
+
+The diagram can be verified as markdown, svg, png, or any combination. One file is verified for each format.
+
+
+
+```cs
+await Verify(data.Model)
+ .ModelAsDiagram(
+ ModelDiagramFormat.Markdown |
+ ModelDiagramFormat.Svg |
+ ModelDiagramFormat.Png);
+```
+snippet source | anchor
+
+
+Result:
+
+ * [ModelDiagramTests.Formats.verified.md](/src/Verify.EntityFramework.Tests/ModelDiagramTests.Formats.verified.md)
+ * [ModelDiagramTests.Formats.verified.svg](/src/Verify.EntityFramework.Tests/ModelDiagramTests.Formats.verified.svg)
+ * [ModelDiagramTests.Formats.verified.png](/src/Verify.EntityFramework.Tests/ModelDiagramTests.Formats.verified.png)
+
+
+
+Available values:
+
+
+
+```cs
+namespace VerifyTests.EntityFramework;
+
+///
+/// The files produced when a model is verified as a diagram. Can be combined.
+///
+[Flags]
+public enum ModelDiagramFormat
+{
+ ///
+ /// A Mermaid ER diagram in a markdown code block. Uses the `md` extension.
+ ///
+ Markdown = 1,
+
+ ///
+ /// The diagram rendered to an image. Uses the `svg` extension.
+ ///
+ Svg = 2,
+
+ ///
+ /// The diagram rendered to an image. Uses the `png` extension.
+ ///
+ Png = 4,
+
+ All = Markdown | Svg | Png
+}
+```
+snippet source | anchor
+
+
+A rendered png may not be pixel identical across machines, so compare using [ssim](https://github.com/VerifyTests/Verify/blob/main/docs/comparer.md):
+
+
+
+```cs
+VerifierSettings.UseSsimForPng();
+```
+snippet source | anchor
+
+
+The svg and png are rendered by [Naiad](https://github.com/Papyrine/Naiad). Its build check requires a sponsorship, or exemption, property to be set in the consuming project. The build error lists the options.
+
+
## IgnoreNavigationProperties
`IgnoreNavigationProperties` extends `SerializationSettings` to exclude all navigation properties from serialization:
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 16801d16..9bcb0592 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -2,7 +2,7 @@
CS1591;CS0649;CS8632;EF1001;NU1608;NU1109
- 16.0.0
+ 16.1.0
preview
1.0.0
EntityFramework, Verify
@@ -13,5 +13,7 @@
true
OpenSource
2027-08
+ OpenSource
+ 2027-08
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 25f242a4..1c2cf93d 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -6,6 +6,7 @@
+
diff --git a/src/Verify.EntityFramework.Tests/ModelDiagramTests.Formats.verified.md b/src/Verify.EntityFramework.Tests/ModelDiagramTests.Formats.verified.md
new file mode 100644
index 00000000..421a1171
--- /dev/null
+++ b/src/Verify.EntityFramework.Tests/ModelDiagramTests.Formats.verified.md
@@ -0,0 +1,14 @@
+```mermaid
+erDiagram
+ Companies["**Companies**"] {
+ int Id pk
+ nvarchar(max) Name
+ }
+ Employees["**Employees**"] {
+ int Id pk
+ int Age
+ int CompanyId
+ nvarchar(max) Name
+ }
+ Companies ||--o{ Employees : "FK_Employees_Companies_CompanyId"
+```
\ No newline at end of file
diff --git a/src/Verify.EntityFramework.Tests/ModelDiagramTests.Formats.verified.png b/src/Verify.EntityFramework.Tests/ModelDiagramTests.Formats.verified.png
new file mode 100644
index 00000000..e2f6b004
Binary files /dev/null and b/src/Verify.EntityFramework.Tests/ModelDiagramTests.Formats.verified.png differ
diff --git a/src/Verify.EntityFramework.Tests/ModelDiagramTests.Formats.verified.svg b/src/Verify.EntityFramework.Tests/ModelDiagramTests.Formats.verified.svg
new file mode 100644
index 00000000..880097f2
--- /dev/null
+++ b/src/Verify.EntityFramework.Tests/ModelDiagramTests.Formats.verified.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Verify.EntityFramework.Tests/ModelDiagramTests.Markdown.verified.md b/src/Verify.EntityFramework.Tests/ModelDiagramTests.Markdown.verified.md
new file mode 100644
index 00000000..421a1171
--- /dev/null
+++ b/src/Verify.EntityFramework.Tests/ModelDiagramTests.Markdown.verified.md
@@ -0,0 +1,14 @@
+```mermaid
+erDiagram
+ Companies["**Companies**"] {
+ int Id pk
+ nvarchar(max) Name
+ }
+ Employees["**Employees**"] {
+ int Id pk
+ int Age
+ int CompanyId
+ nvarchar(max) Name
+ }
+ Companies ||--o{ Employees : "FK_Employees_Companies_CompanyId"
+```
\ No newline at end of file
diff --git a/src/Verify.EntityFramework.Tests/ModelDiagramTests.SvgOnly.verified.svg b/src/Verify.EntityFramework.Tests/ModelDiagramTests.SvgOnly.verified.svg
new file mode 100644
index 00000000..880097f2
--- /dev/null
+++ b/src/Verify.EntityFramework.Tests/ModelDiagramTests.SvgOnly.verified.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Verify.EntityFramework.Tests/ModelDiagramTests.cs b/src/Verify.EntityFramework.Tests/ModelDiagramTests.cs
new file mode 100644
index 00000000..e8124cd0
--- /dev/null
+++ b/src/Verify.EntityFramework.Tests/ModelDiagramTests.cs
@@ -0,0 +1,55 @@
+using VerifyTests.EntityFramework;
+
+public class ModelDiagramTests
+{
+ [Test]
+ public async Task Markdown()
+ {
+ await using var data = BuildData();
+
+ #region ModelDiagram
+
+ await Verify(data.Model);
+
+ #endregion
+ }
+
+ [Test]
+ public async Task Formats()
+ {
+ await using var data = BuildData();
+
+ #region ModelDiagramFormats
+
+ await Verify(data.Model)
+ .ModelAsDiagram(
+ ModelDiagramFormat.Markdown |
+ ModelDiagramFormat.Svg |
+ ModelDiagramFormat.Png);
+
+ #endregion
+ }
+
+ [Test]
+ public async Task SvgOnly()
+ {
+ await using var data = BuildData();
+ await Verify(data.Model)
+ .ModelAsDiagram(ModelDiagramFormat.Svg);
+ }
+
+ [Test]
+ public async Task NoFormat()
+ {
+ var settings = new VerifySettings();
+ await Assert.That(() => settings.ModelAsDiagram(0))
+ .Throws();
+ }
+
+ static SampleDbContext BuildData()
+ {
+ var options = new DbContextOptionsBuilder();
+ options.UseSqlServer("fake");
+ return new(options.Options);
+ }
+}
diff --git a/src/Verify.EntityFramework.Tests/ModuleInitializer.cs b/src/Verify.EntityFramework.Tests/ModuleInitializer.cs
index f2c5a964..794ca7aa 100644
--- a/src/Verify.EntityFramework.Tests/ModuleInitializer.cs
+++ b/src/Verify.EntityFramework.Tests/ModuleInitializer.cs
@@ -24,6 +24,12 @@ public static void Init()
[ModuleInitializer]
public static void InitOther()
{
+ #region UseSsimForPng
+
+ VerifierSettings.UseSsimForPng();
+
+ #endregion
+
VerifierSettings.InitializePlugins();
Recording.IgnoreNames("sql");
}
diff --git a/src/Verify.EntityFramework/ModelDiagram.cs b/src/Verify.EntityFramework/ModelDiagram.cs
new file mode 100644
index 00000000..e00a64de
--- /dev/null
+++ b/src/Verify.EntityFramework/ModelDiagram.cs
@@ -0,0 +1,65 @@
+using DbToMermaid;
+
+namespace VerifyTests;
+
+public static class VerifyEntityFrameworkModelDiagram
+{
+ const string contextKey = "EntityFramework.ModelDiagram";
+
+ ///
+ /// Select the files produced when an is verified as a Mermaid ER diagram.
+ ///
+ /// The settings to apply to.
+ /// The files to produce. Can be combined, with one file verified per format.
+ public static SettingsTask ModelAsDiagram(
+ this SettingsTask settings,
+ ModelDiagramFormat formats = ModelDiagramFormat.Markdown)
+ {
+ settings.CurrentSettings.ModelAsDiagram(formats);
+ return settings;
+ }
+
+ ///
+ public static void ModelAsDiagram(
+ this VerifySettings settings,
+ ModelDiagramFormat formats = ModelDiagramFormat.Markdown)
+ {
+ if ((formats & ModelDiagramFormat.All) == 0)
+ {
+ throw new ArgumentException("At least one format is required.", nameof(formats));
+ }
+
+ settings.Context[contextKey] = formats;
+ }
+
+ internal static async Task Convert(IModel model, IReadOnlyDictionary context)
+ {
+ var formats = ModelDiagramFormat.Markdown;
+ if (context.TryGetValue(contextKey, out var value))
+ {
+ formats = (ModelDiagramFormat) value;
+ }
+
+ var targets = new List();
+
+ if (formats.HasFlag(ModelDiagramFormat.Markdown))
+ {
+ var markdown = await EfToMermaid.RenderMarkdown(model);
+ targets.Add(new("md", markdown));
+ }
+
+ if (formats.HasFlag(ModelDiagramFormat.Svg))
+ {
+ var svg = await EfToMermaid.RenderSvg(model);
+ targets.Add(new("svg", svg));
+ }
+
+ if (formats.HasFlag(ModelDiagramFormat.Png))
+ {
+ var png = await EfToMermaid.RenderPng(model);
+ targets.Add(new("png", new MemoryStream(png)));
+ }
+
+ return new(null, targets);
+ }
+}
diff --git a/src/Verify.EntityFramework/ModelDiagramFormat.cs b/src/Verify.EntityFramework/ModelDiagramFormat.cs
new file mode 100644
index 00000000..154f1b0e
--- /dev/null
+++ b/src/Verify.EntityFramework/ModelDiagramFormat.cs
@@ -0,0 +1,25 @@
+namespace VerifyTests.EntityFramework;
+
+///
+/// The files produced when a model is verified as a diagram. Can be combined.
+///
+[Flags]
+public enum ModelDiagramFormat
+{
+ ///
+ /// A Mermaid ER diagram in a markdown code block. Uses the `md` extension.
+ ///
+ Markdown = 1,
+
+ ///
+ /// The diagram rendered to an image. Uses the `svg` extension.
+ ///
+ Svg = 2,
+
+ ///
+ /// The diagram rendered to an image. Uses the `png` extension.
+ ///
+ Png = 4,
+
+ All = Markdown | Svg | Png
+}
diff --git a/src/Verify.EntityFramework/Verify.EntityFramework.csproj b/src/Verify.EntityFramework/Verify.EntityFramework.csproj
index d6e12053..ed1640c2 100644
--- a/src/Verify.EntityFramework/Verify.EntityFramework.csproj
+++ b/src/Verify.EntityFramework/Verify.EntityFramework.csproj
@@ -3,6 +3,7 @@
net10.0
+
diff --git a/src/Verify.EntityFramework/VerifyEntityFramework.cs b/src/Verify.EntityFramework/VerifyEntityFramework.cs
index 45909e76..0fd4fa65 100644
--- a/src/Verify.EntityFramework/VerifyEntityFramework.cs
+++ b/src/Verify.EntityFramework/VerifyEntityFramework.cs
@@ -199,6 +199,7 @@ public static void Initialize(IModel? model, bool recordCommands)
VerifierSettings.RegisterFileConverter(
QueryableToSql,
(target, _) => QueryableConverter.IsQueryable(target));
+ VerifierSettings.RegisterFileConverter(VerifyEntityFrameworkModelDiagram.Convert);
VerifierSettings.IgnoreMembersWithType(typeof(IDbContextFactory<>));
VerifierSettings.IgnoreMembersWithType();
diff --git a/src/mdsnippets.json b/src/mdsnippets.json
index 331566e9..6f67fb44 100644
--- a/src/mdsnippets.json
+++ b/src/mdsnippets.json
@@ -1,7 +1,8 @@
-{
+{
"$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json",
"TocExcludes": [ "NuGet package", "Release Notes", "Icon" ],
"MaxWidth": 90,
"ValidateContent": true,
- "Convention": "InPlaceOverwrite"
+ "Convention": "InPlaceOverwrite",
+ "ExcludeMarkdownFiles": [ "*.verified.md", "*.received.md" ]
}
\ No newline at end of file