Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,29 @@ public enum ModelDiagramFormat
<sup><a href='/src/Verify.EntityFramework/ModelDiagramFormat.cs#L1-L25' title='Snippet source file'>snippet source</a> | <a href='#snippet-ModelDiagramFormat.cs' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

The entity types included in the diagram can be filtered. Foreign keys to an excluded entity type are omitted.

<!-- snippet: ModelDiagramFilter -->
<a id='snippet-ModelDiagramFilter'></a>
```cs
await Verify(data.Model)
// exclude the Employee entity
.ModelDiagramFilter(_ => _.ClrType != typeof(Employee));
```
<sup><a href='/src/Verify.EntityFramework.Tests/ModelDiagramTests.cs#L38-L44' title='Snippet source file'>snippet source</a> | <a href='#snippet-ModelDiagramFilter' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

To render only the table names, without columns:

<!-- snippet: ModelDiagramNamesOnly -->
<a id='snippet-ModelDiagramNamesOnly'></a>
```cs
await Verify(data.Model)
.ModelAsDiagram(namesOnly: true);
```
<sup><a href='/src/Verify.EntityFramework.Tests/ModelDiagramTests.cs#L52-L57' title='Snippet source file'>snippet source</a> | <a href='#snippet-ModelDiagramNamesOnly' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

A rendered png may not be pixel identical across machines, so compare using [ssim](https://github.com/VerifyTests/Verify/blob/main/docs/comparer.md):

<!-- snippet: UseSsimForPng -->
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project>
<PropertyGroup>
<NoWarn>CS1591;CS0649;CS8632;EF1001;NU1608;NU1109</NoWarn>
<Version>16.1.2</Version>
<Version>16.2.0</Version>
<LangVersion>preview</LangVersion>
<AssemblyVersion>1.0.0</AssemblyVersion>
<PackageTags>EntityFramework, Verify</PackageTags>
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<ItemGroup>
<PackageVersion Include="EfClassicLocalDb" Version="27.2.5" />
<PackageVersion Include="EfLocalDb" Version="27.2.5" />
<PackageVersion Include="EfToMermaid" Version="2.2.2" />
<PackageVersion Include="EfToMermaid" Version="2.3.0" />
<PackageVersion Include="EntityFramework" Version="6.5.2" />
<PackageVersion Include="MarkdownSnippets.MsBuild" Version="28.5.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.12" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```mermaid
erDiagram
Companies["**Companies**"] {
int Id pk
nvarchar(max) Name
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
```mermaid
erDiagram
Companies["**Companies**"]
Employees["**Employees**"]
Companies ||--o{ Employees : "FK_Employees_Companies_CompanyId"
```
27 changes: 27 additions & 0 deletions src/Verify.EntityFramework.Tests/ModelDiagramTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,33 @@ await Verify(data.Model)
#endregion
}

[Test]
public async Task Filter()
{
await using var data = BuildData();

#region ModelDiagramFilter

await Verify(data.Model)
// exclude the Employee entity
.ModelDiagramFilter(_ => _.ClrType != typeof(Employee));

#endregion
}

[Test]
public async Task NamesOnly()
{
await using var data = BuildData();

#region ModelDiagramNamesOnly

await Verify(data.Model)
.ModelAsDiagram(namesOnly: true);

#endregion
}

[Test]
public async Task SvgOnly()
{
Expand Down
49 changes: 42 additions & 7 deletions src/Verify.EntityFramework/ModelDiagram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,59 @@ namespace VerifyTests;
public static class VerifyEntityFrameworkModelDiagram
{
const string contextKey = "EntityFramework.ModelDiagram";
const string filterKey = "EntityFramework.ModelDiagramFilter";
const string namesOnlyKey = "EntityFramework.ModelDiagramNamesOnly";

/// <summary>
/// Select the files produced when an <see cref="IModel" /> is verified as a Mermaid ER diagram.
/// </summary>
/// <param name="settings">The settings to apply to.</param>
/// <param name="formats">The files to produce. Can be combined, with one file verified per format.</param>
/// <param name="namesOnly">When true, only the table names are rendered, without columns.</param>
public static SettingsTask ModelAsDiagram(
this SettingsTask settings,
ModelDiagramFormat formats = ModelDiagramFormat.Markdown)
ModelDiagramFormat formats = ModelDiagramFormat.Markdown,
bool namesOnly = false)
{
settings.CurrentSettings.ModelAsDiagram(formats);
settings.CurrentSettings.ModelAsDiagram(formats, namesOnly);
return settings;
}

/// <inheritdoc cref="ModelAsDiagram(SettingsTask, ModelDiagramFormat)" />
/// <inheritdoc cref="ModelAsDiagram(SettingsTask, ModelDiagramFormat, bool)" />
public static void ModelAsDiagram(
this VerifySettings settings,
ModelDiagramFormat formats = ModelDiagramFormat.Markdown)
ModelDiagramFormat formats = ModelDiagramFormat.Markdown,
bool namesOnly = false)
{
if ((formats & ModelDiagramFormat.All) == 0)
{
throw new ArgumentException("At least one format is required.", nameof(formats));
}

settings.Context[contextKey] = formats;
settings.Context[namesOnlyKey] = namesOnly;
}

/// <summary>
/// Select the entity types included when an <see cref="IModel" /> is verified as a Mermaid ER diagram.
/// Foreign keys to an excluded entity type are omitted.
/// </summary>
/// <param name="settings">The settings to apply to.</param>
/// <param name="filter">Returns true for the entity types to include.</param>
public static SettingsTask ModelDiagramFilter(
this SettingsTask settings,
Func<IEntityType, bool> filter)
{
settings.CurrentSettings.ModelDiagramFilter(filter);
return settings;
}

/// <inheritdoc cref="ModelDiagramFilter(SettingsTask, Func{IEntityType, bool})" />
public static void ModelDiagramFilter(
this VerifySettings settings,
Func<IEntityType, bool> filter) =>
settings.Context[filterKey] = filter;

internal static async Task<ConversionResult> Convert(IModel model, IReadOnlyDictionary<string, object> context)
{
var formats = ModelDiagramFormat.Markdown;
Expand All @@ -40,23 +66,32 @@ internal static async Task<ConversionResult> Convert(IModel model, IReadOnlyDict
formats = (ModelDiagramFormat) value;
}

var namesOnly = context.TryGetValue(namesOnlyKey, out var namesOnlyValue) &&
(bool) namesOnlyValue;

Func<IEntityType, bool>? filter = null;
if (context.TryGetValue(filterKey, out var filterValue))
{
filter = (Func<IEntityType, bool>) filterValue;
}

var targets = new List<Target>();

if (formats.HasFlag(ModelDiagramFormat.Markdown))
{
var markdown = await EfToMermaid.RenderMarkdown(model);
var markdown = await EfToMermaid.RenderMarkdown(model, filter, namesOnly);
targets.Add(new("md", markdown));
}

if (formats.HasFlag(ModelDiagramFormat.Svg))
{
var svg = await EfToMermaid.RenderSvg(model);
var svg = await EfToMermaid.RenderSvg(model, filter, namesOnly);
targets.Add(new("svg", svg));
}

if (formats.HasFlag(ModelDiagramFormat.Png))
{
var png = await EfToMermaid.RenderPng(model);
var png = await EfToMermaid.RenderPng(model, filter, namesOnly);
targets.Add(new("png", new MemoryStream(png)));
}

Expand Down
Loading