├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── publish-nuget.yml ├── .gitignore ├── Directory.Build.props ├── Directory.Build.targets ├── LICENSE ├── README.md ├── SpatialFocus.EntityFrameworkCore.Extensions.ruleset ├── SpatialFocus.EntityFrameworkCore.Extensions.sln ├── SpatialFocus.EntityFrameworkCore.Extensions.sln.DotSettings ├── docs └── nuget-icon.png ├── samples └── SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo │ ├── Data │ └── DemoContext.cs │ ├── Entities │ ├── Product.cs │ ├── ProductCategory.cs │ ├── Review.cs │ ├── ReviewRating.cs │ └── SpecialOccasion.cs │ ├── Program.cs │ └── SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo.csproj ├── src └── SpatialFocus.EntityFrameworkCore.Extensions │ ├── EnumLookupAttribute.cs │ ├── EnumLookupExtension.cs │ ├── EnumLookupOptions.cs │ ├── EnumWithNumberLookup.cs │ ├── EnumWithNumberLookupAndDescription.cs │ ├── EnumWithStringLookup.cs │ ├── EnumWithStringLookupAndDescription.cs │ ├── NamingExtension.cs │ ├── NamingOptions.cs │ ├── NamingScheme.cs │ ├── NamingSource.cs │ └── SpatialFocus.EntityFrameworkCore.Extensions.csproj ├── stylecop.json └── test └── SpatialFocus.EntityFrameworkCore.Extensions.Test ├── Entities ├── Product.cs ├── ProductCategory.cs ├── ProductTag.cs └── SpecialOccasion.cs ├── NamingOptionsTest.cs ├── ProductContext.cs └── SpatialFocus.EntityFrameworkCore.Extensions.Test.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | # Modified version of https://github.com/dotnet/roslyn/blob/d0ca6f34aff907a9cafde9f629397cc153bb94fe/.editorconfig 2 | # Rules documentation https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | # Don't use tabs for indentation. 8 | [*] 9 | indent_style = tab 10 | trim_trailing_whitespace = true 11 | insert_final_newline = false 12 | # (Please don't specify an indent_size here; that has too many unintended consequences.) 13 | 14 | # Code files 15 | [*.{cs,csx,vb,vbx}] 16 | indent_size = 4 17 | 18 | # Xml project files 19 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 20 | indent_size = 2 21 | 22 | # Xml config files 23 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 24 | indent_size = 2 25 | 26 | # JSON files 27 | [*.json] 28 | indent_size = 2 29 | 30 | # JS and TS files 31 | [*.{js,ts}] 32 | indent_size = 2 33 | 34 | # Dotnet code style settings: 35 | [*.{cs,vb}] 36 | # Sort using and Import directives with System.* appearing first 37 | dotnet_sort_system_directives_first = true 38 | # Avoid "this." and "Me." if not necessary 39 | dotnet_style_qualification_for_field = true:suggestion 40 | dotnet_style_qualification_for_property = false:suggestion 41 | dotnet_style_qualification_for_method = false:suggestion 42 | dotnet_style_qualification_for_event = false:suggestion 43 | 44 | # Use language keywords instead of framework type names for type references 45 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 46 | dotnet_style_predefined_type_for_member_access = true:suggestion 47 | 48 | # Suggest more modern language features when available 49 | dotnet_style_object_initializer = true:suggestion 50 | dotnet_style_collection_initializer = true:suggestion 51 | dotnet_style_coalesce_expression = true:suggestion 52 | dotnet_style_null_propagation = true:suggestion 53 | dotnet_style_explicit_tuple_names = true:suggestion 54 | 55 | # CSharp code style settings: 56 | [*.cs] 57 | # Prefer "var" everywhere 58 | csharp_style_var_for_built_in_types = false:suggestion 59 | csharp_style_var_when_type_is_apparent = false:suggestion 60 | csharp_style_var_elsewhere = false:suggestion 61 | 62 | # Prefer method-like constructs to have a block body 63 | csharp_style_expression_bodied_methods = false:suggestion 64 | csharp_style_expression_bodied_constructors = false:suggestion 65 | csharp_style_expression_bodied_operators = false:suggestion 66 | 67 | # Prefer property-like constructs to have an expression-body 68 | csharp_style_expression_bodied_properties = true:suggestion 69 | csharp_style_expression_bodied_indexers = true:suggestion 70 | csharp_style_expression_bodied_accessors = true:suggestion 71 | 72 | # Suggest more modern language features when available 73 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 74 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 75 | csharp_style_inlined_variable_declaration = true:suggestion 76 | csharp_style_throw_expression = true:suggestion 77 | csharp_style_conditional_delegate_call = true:suggestion 78 | 79 | # Newline settings 80 | csharp_new_line_before_open_brace = all 81 | csharp_new_line_before_else = true 82 | csharp_new_line_before_catch = true 83 | csharp_new_line_before_finally = true 84 | csharp_new_line_before_members_in_object_initializers = true 85 | csharp_new_line_before_members_in_anonymous_types = true 86 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/publish-nuget.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish NuGet 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | publish: 11 | name: build, pack & publish 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-dotnet@v1 16 | with: 17 | dotnet-version: | 18 | 3.1.x 19 | 5.0.x 20 | 6.0.x 21 | 8.0.x 22 | - name: Build 23 | run: dotnet build --configuration Release 24 | - name: Pack 25 | run: dotnet pack src/SpatialFocus.EntityFrameworkCore.Extensions/SpatialFocus.EntityFrameworkCore.Extensions.csproj --output . --configuration Release 26 | - name: Push 27 | run: dotnet nuget push *.nupkg --skip-duplicate --api-key ${{secrets.nuget_api_key}} --source https://api.nuget.org/v3/index.json 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Visual Studio (>=2015) project-specific, machine local files 5 | .vs/ 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.sln.docstates 11 | *.userprefs 12 | 13 | # ignore Xamarin.Android Resource.Designer.cs files 14 | **/*.Droid/**/[Rr]esource.[Dd]esigner.cs 15 | **/*.Android/**/[Rr]esource.[Dd]esigner.cs 16 | **/Android/**/[Rr]esource.[Dd]esigner.cs 17 | **/Droid/**/[Rr]esource.[Dd]esigner.cs 18 | 19 | # Xamarin Components 20 | Components/ 21 | 22 | # Build results 23 | [Dd]ebug/ 24 | [Dd]ebugPublic/ 25 | [Rr]elease/ 26 | x64/ 27 | build/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | #NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding addin-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | *.ncrunch* 108 | _NCrunch_* 109 | .*crunch*.local.xml 110 | 111 | # MightyMoose 112 | *.mm.* 113 | AutoTest.Net/ 114 | 115 | # Web workbench (sass) 116 | .sass-cache/ 117 | 118 | # Installshield output folder 119 | [Ee]xpress/ 120 | 121 | # DocProject is a documentation generator add-in 122 | DocProject/buildhelp/ 123 | DocProject/Help/*.HxT 124 | DocProject/Help/*.HxC 125 | DocProject/Help/*.hhc 126 | DocProject/Help/*.hhk 127 | DocProject/Help/*.hhp 128 | DocProject/Help/Html2 129 | DocProject/Help/html 130 | 131 | # Click-Once directory 132 | publish/ 133 | 134 | # Publish Web Output 135 | *.[Pp]ublish.xml 136 | *.azurePubxml 137 | 138 | # NuGet Packages Directory 139 | .nuget/ 140 | packages/ 141 | *.nuget.targets 142 | *.lock.json 143 | *.nuget.props 144 | 145 | ## TODO: If the tool you use requires repositories.config uncomment the next line 146 | #!packages/repositories.config 147 | 148 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 149 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) 150 | !packages/build/ 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Others 160 | sql/ 161 | *.Cache 162 | ClientBin/ 163 | [Ss]tyle[Cc]op.* 164 | ~$* 165 | *~ 166 | *.dbmdl 167 | *.dbproj.schemaview 168 | *.pfx 169 | *.publishsettings 170 | node_modules/ 171 | .DS_Store 172 | *.bak 173 | 174 | # RIA/Silverlight projects 175 | Generated_Code/ 176 | 177 | # Backup & report files from converting an old project file to a newer 178 | # Visual Studio version. Backup files are not needed, because we have git ;-) 179 | _UpgradeReport_Files/ 180 | Backup*/ 181 | UpgradeLog*.XML 182 | UpgradeLog*.htm 183 | 184 | # SQL Server files 185 | *.mdf 186 | *.ldf 187 | 188 | # Business Intelligence projects 189 | *.rdl.data 190 | *.bim.layout 191 | *.bim_*.settings 192 | 193 | # Microsoft Fakes 194 | FakesAssemblies/ -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(MSBuildThisFileDirectory)SpatialFocus.EntityFrameworkCore.Extensions.ruleset 4 | 5 | 6 | 7 | 1.2.0-beta.113 8 | all 9 | 10 | 11 | stylecop.json 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Spatial Focus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpatialFocus.EntityFrameworkCore.Extensions 2 | 3 | A set of useful extensions for EntityFrameworkCore (Enum Lookup Tables, Naming of tables / properties / keys, Pluralize). 4 | 5 | [![Build / Publish NuGet](https://github.com/SpatialFocus/EntityFrameworkCore.Extensions/actions/workflows/publish-nuget.yml/badge.svg)](https://github.com/SpatialFocus/EntityFrameworkCore.Extensions/actions/workflows/publish-nuget.yml) 6 | [![NuGet](https://img.shields.io/nuget/v/SpatialFocus.EntityFrameworkCore.Extensions.svg)](https://www.nuget.org/packages/SpatialFocus.EntityFrameworkCore.Extensions/) 7 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FSpatialFocus%2FEntityFrameworkCore.Extensions.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FSpatialFocus%2FEntityFrameworkCore.Extensions?ref=badge_shield) 8 | 9 | ## Installation 10 | 11 | ```console 12 | Install-Package SpatialFocus.EntityFrameworkCore.Extensions 13 | ``` 14 | 15 | ## Usage 16 | 17 | This extension provides two extension methods to modify the `modelBuilder` in the `OnModelCreating` method in the `DbContext` class. 18 | 19 | ```csharp 20 | using SpatialFocus.EntityFrameworkCore.Extensions; 21 | 22 | public partial class MyContext : DbContext 23 | { 24 | // ... 25 | 26 | protected override void OnModelCreating(ModelBuilder modelBuilder) 27 | { 28 | // Any custom model configuration should come before calling ConfigureEnumLookup and ConfigureNames 29 | // ... 30 | 31 | modelBuilder.ConfigureEnumLookup(EnumLookupOptions.Default.UseStringAsIdentifier()); 32 | 33 | modelBuilder.ConfigureNames(NamingOptions.Default.SkipTableNamingForGenericEntityTypes()); 34 | 35 | // Any configuration after calling the two methods will not be processed by this Extension 36 | // ... 37 | } 38 | } 39 | ``` 40 | 41 | ## Configuration Options 42 | 43 | There are two extension methods in the `OnModelCreating` method in the `DbContext` class: 44 | 45 | - **ConfigureEnumLookup(...)** allows you to define in which form lookup tables for *Enums* will be constructed and named: 46 | 47 | - **Default** defines the naming scheme for the table to use *snake_case* and to use the number lookup format. 48 | - **UseNumberAsIdentifier()** or **UseStringAsIdentifier()** defines whether the lookup table will be based on the string enum values or the numeric enum value as primary key in the resulting table and as foreign key in the relation. 49 | - **Singularize()** or **Pluralize()** defines whether the table names will be the singular or plural versions of the enum type. 50 | - **SetNamingScheme(...)** allows you to override the naming using one of the predefined schemes (see below) or a custom function. 51 | - **UseEnumsWithAttributeOnly()** to generate the enum lookup tables only for enums marked with the `[EnumLookup]` attribute 52 | - **SetDeleteBehavior(...)** to configure the delete behavior for the generated FKs, using the `Microsoft.EntityFrameworkCore.DeleteBehavior` enum (defaults to _Cascade_) 53 | - **Please note:** For owned entities, you should call the **ConfigureOwnedEnumLookup(...)** method on the `OwnedNavigationBuilder`. Please see [#16](/../../issues/16) for more details. E.g. `modelBuilder.Entity().OwnsOne(x => x.Address, ownedBuilder => ownedBuilder.ConfigureOwnedEnumLookup(...));` 54 | 55 | - **ConfigureNames(...)** allows you to define in which form tables, properties and constraints will be named: 56 | 57 | - **Default** defines the naming scheme for the elemens to use *snake_case* for naming and the *DbSet name* to name the tables. 58 | - **SetTableNamingSource(...)** defines which naming source to use (see below). It means whether to use the ClrType name or the DbSet name to name the tables. 59 | - **Singularize()** or **Pluralize()** defines whether the table names will be the singular or plural versions. 60 | - **SetNamingScheme(...)** allows you to override the naming using one of the predefined schemes (see below) or a custom function. 61 | - **OverrideTableNaming(...)**, **OverrideColumnNaming(...)**, **OverrideConstraintNaming(...)** to deviate from the general naming scheme. 62 | - **SkipEntireEntities(...)** and **SkipTableNamingForEntities(...)** to skip the naming for either the whole entity or just the table name for certain entities by using a `Func` skip function, e.g. `SkipEntireEntities(entity => entity.Name == "MyEntity")`. 63 | - **SkipTableNamingForGenericEntityTypes()** should be used to avoid naming the Enum lookup tables which can lead to unwanted results. 64 | 65 | ### Naming Schemes 66 | 67 | - NamingScheme.SnakeCase 68 | - NamingScheme.ScreamingSnakeCase 69 | - NamingScheme.KebabCase 70 | - Any custom `Func`, e.g. `SetNamingScheme(name => name.ToLower())` 71 | 72 | #### Naming Source 73 | 74 | - NamingSource.ClrType 75 | - NamingSource.DbSet 76 | 77 | ## Examples 78 | 79 | For an exemplary usage, see the `DemoContext` in the [sample project](https://github.com/SpatialFocus/SpatialFocus.EntityFrameworkCore.Extensions/tree/master/samples/SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo). 80 | 81 | ## License 82 | 83 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FSpatialFocus%2FEntityFrameworkCore.Extensions.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FSpatialFocus%2FEntityFrameworkCore.Extensions?ref=badge_large) 84 | 85 | ---- 86 | 87 | Made with :heart: by [Spatial Focus](https://spatial-focus.net/) 88 | -------------------------------------------------------------------------------- /SpatialFocus.EntityFrameworkCore.Extensions.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SpatialFocus.EntityFrameworkCore.Extensions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29709.97 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpatialFocus.EntityFrameworkCore.Extensions", "src\SpatialFocus.EntityFrameworkCore.Extensions\SpatialFocus.EntityFrameworkCore.Extensions.csproj", "{768CB889-C6A3-475B-B8EE-72CE676DD5B8}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo", "samples\SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo\SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo.csproj", "{28AC20A6-FE86-427F-80D9-19D5D02D31FB}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{70FF5B08-F25B-44BE-B8FE-1F1884B5B3F0}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{8092090A-24E1-45E6-84B3-AA060CC34619}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2C1E7909-3D0A-462C-AEE1-21D583FBCFD2}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{AA695DE3-C613-4023-97F5-BF3841C5E233}" 17 | ProjectSection(SolutionItems) = preProject 18 | .editorconfig = .editorconfig 19 | .gitattributes = .gitattributes 20 | .gitignore = .gitignore 21 | Directory.Build.props = Directory.Build.props 22 | Directory.Build.targets = Directory.Build.targets 23 | LICENSE = LICENSE 24 | README.md = README.md 25 | SpatialFocus.EntityFrameworkCore.Extensions.ruleset = SpatialFocus.EntityFrameworkCore.Extensions.ruleset 26 | SpatialFocus.EntityFrameworkCore.Extensions.sln.DotSettings = SpatialFocus.EntityFrameworkCore.Extensions.sln.DotSettings 27 | stylecop.json = stylecop.json 28 | EndProjectSection 29 | EndProject 30 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpatialFocus.EntityFrameworkCore.Extensions.Test", "test\SpatialFocus.EntityFrameworkCore.Extensions.Test\SpatialFocus.EntityFrameworkCore.Extensions.Test.csproj", "{F40647CD-65D8-4FD0-8391-88F7CD8D976D}" 31 | EndProject 32 | Global 33 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 34 | Debug|Any CPU = Debug|Any CPU 35 | Release|Any CPU = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 38 | {768CB889-C6A3-475B-B8EE-72CE676DD5B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {768CB889-C6A3-475B-B8EE-72CE676DD5B8}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {768CB889-C6A3-475B-B8EE-72CE676DD5B8}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {768CB889-C6A3-475B-B8EE-72CE676DD5B8}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {28AC20A6-FE86-427F-80D9-19D5D02D31FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {28AC20A6-FE86-427F-80D9-19D5D02D31FB}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {28AC20A6-FE86-427F-80D9-19D5D02D31FB}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {28AC20A6-FE86-427F-80D9-19D5D02D31FB}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {F40647CD-65D8-4FD0-8391-88F7CD8D976D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {F40647CD-65D8-4FD0-8391-88F7CD8D976D}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {F40647CD-65D8-4FD0-8391-88F7CD8D976D}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {F40647CD-65D8-4FD0-8391-88F7CD8D976D}.Release|Any CPU.Build.0 = Release|Any CPU 50 | EndGlobalSection 51 | GlobalSection(SolutionProperties) = preSolution 52 | HideSolutionNode = FALSE 53 | EndGlobalSection 54 | GlobalSection(NestedProjects) = preSolution 55 | {768CB889-C6A3-475B-B8EE-72CE676DD5B8} = {70FF5B08-F25B-44BE-B8FE-1F1884B5B3F0} 56 | {28AC20A6-FE86-427F-80D9-19D5D02D31FB} = {8092090A-24E1-45E6-84B3-AA060CC34619} 57 | {F40647CD-65D8-4FD0-8391-88F7CD8D976D} = {2C1E7909-3D0A-462C-AEE1-21D583FBCFD2} 58 | EndGlobalSection 59 | GlobalSection(ExtensibilityGlobals) = postSolution 60 | SolutionGuid = {8D07B300-5F54-4D11-BA2E-668BF1671D72} 61 | EndGlobalSection 62 | EndGlobal 63 | -------------------------------------------------------------------------------- /SpatialFocus.EntityFrameworkCore.Extensions.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | DO_NOT_SHOW 3 | ECMAScript 2016 4 | <?xml version="1.0" encoding="utf-16"?><Profile name="Default"><CSReorderTypeMembers>True</CSReorderTypeMembers><CSUpdateFileHeader>True</CSUpdateFileHeader><CSUseAutoProperty>True</CSUseAutoProperty><CSArrangeQualifiers>True</CSArrangeQualifiers><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="False" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="False" ArrangeVarStyle="True" /><CSEnforceVarKeywordUsageSettings>True</CSEnforceVarKeywordUsageSettings><XMLReformatCode>True</XMLReformatCode></Profile> 5 | Default 6 | USE_TABS_ONLY 7 | USE_TABS_ONLY 8 | 1 9 | 1 10 | 1 11 | 1 12 | 1 13 | 1 14 | 1 15 | False 16 | False 17 | False 18 | NEVER 19 | NEVER 20 | False 21 | NEVER 22 | NEVER 23 | LINE_BREAK 24 | True 25 | False 26 | False 27 | False 28 | True 29 | False 30 | True 31 | False 32 | False 33 | 1 34 | 1 35 | Required 36 | Required 37 | Required 38 | Required 39 | CHOP_IF_LONG 40 | 140 41 | USE_TABS_ONLY 42 | USE_TABS_ONLY 43 | True 44 | True 45 | True 46 | OneStep 47 | html,thead,tbody,tfoot 48 | True 49 | USE_TABS_ONLY 50 | USE_TABS_ONLY 51 | USE_TABS_ONLY 52 | USE_TABS_ONLY 53 | USE_TABS_ONLY 54 | USE_TABS_ONLY 55 | True 56 | OnSingleLine 57 | OneStep 58 | OnSingleLine 59 | OneStep 60 | 1 61 | <copyright file="${File.FileName}" company="Spatial Focus"> 62 | Copyright (c) Spatial Focus. All rights reserved. 63 | Licensed under the MIT license. See LICENSE file in the project root for full license information. 64 | </copyright> 65 | UI 66 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 67 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 68 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 69 | <?xml version="1.0" encoding="utf-16"?> 70 | <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> 71 | <TypePattern DisplayName="COM interfaces or structs"> 72 | <TypePattern.Match> 73 | <Or> 74 | <And> 75 | <Kind Is="Interface" /> 76 | <Or> 77 | <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> 78 | <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> 79 | </Or> 80 | </And> 81 | <Kind Is="Struct" /> 82 | </Or> 83 | </TypePattern.Match> 84 | </TypePattern> 85 | <TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All"> 86 | <TypePattern.Match> 87 | <And> 88 | <Kind Is="Class" /> 89 | <HasMember> 90 | <And> 91 | <Kind Is="Method" /> 92 | <HasAttribute Name="Xunit.FactAttribute" Inherited="True" /> 93 | </And> 94 | </HasMember> 95 | </And> 96 | </TypePattern.Match> 97 | <Entry DisplayName="Setup/Teardown Methods"> 98 | <Entry.Match> 99 | <Or> 100 | <Kind Is="Constructor" /> 101 | <And> 102 | <Kind Is="Method" /> 103 | <ImplementsInterface Name="System.IDisposable" /> 104 | </And> 105 | </Or> 106 | </Entry.Match> 107 | <Entry.SortBy> 108 | <Kind Order="Constructor" /> 109 | </Entry.SortBy> 110 | </Entry> 111 | <Entry DisplayName="All other members" /> 112 | <Entry Priority="100" DisplayName="Test Methods"> 113 | <Entry.Match> 114 | <And> 115 | <Kind Is="Method" /> 116 | <HasAttribute Name="Xunit.FactAttribute" /> 117 | </And> 118 | </Entry.Match> 119 | <Entry.SortBy> 120 | <Name /> 121 | </Entry.SortBy> 122 | </Entry> 123 | </TypePattern> 124 | <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> 125 | <TypePattern.Match> 126 | <And> 127 | <Kind Is="Class" /> 128 | <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> 129 | </And> 130 | </TypePattern.Match> 131 | <Entry DisplayName="Setup/Teardown Methods"> 132 | <Entry.Match> 133 | <And> 134 | <Kind Is="Method" /> 135 | <Or> 136 | <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> 137 | <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> 138 | <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> 139 | <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> 140 | </Or> 141 | </And> 142 | </Entry.Match> 143 | </Entry> 144 | <Entry DisplayName="All other members" /> 145 | <Entry Priority="100" DisplayName="Test Methods"> 146 | <Entry.Match> 147 | <And> 148 | <Kind Is="Method" /> 149 | <HasAttribute Name="NUnit.Framework.TestAttribute" /> 150 | </And> 151 | </Entry.Match> 152 | <Entry.SortBy> 153 | <Name /> 154 | </Entry.SortBy> 155 | </Entry> 156 | </TypePattern> 157 | <TypePattern DisplayName="Default Pattern"> 158 | <Entry DisplayName="All Members"> 159 | <Entry.SortBy> 160 | <Kind Order="Constant Field Constructor Destructor Delegate Event Enum Interface Property Indexer Operator Method Struct Class" /> 161 | <Access Order="Public Protected Internal Private" /> 162 | <Static /> 163 | <Readonly /> 164 | <Name /> 165 | </Entry.SortBy> 166 | </Entry> 167 | </TypePattern> 168 | </Patterns> 169 | UseExplicitType 170 | UseExplicitType 171 | UseExplicitType 172 | True 173 | True 174 | False 175 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 176 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 177 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 178 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 179 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 180 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 181 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 182 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 183 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 184 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 185 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 186 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 187 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 188 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 189 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 190 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 191 | <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> 192 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 193 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 194 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 195 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 196 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 197 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 198 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 199 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 200 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 201 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 202 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 203 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 204 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 205 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 206 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 207 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 208 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 209 | <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> 210 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 211 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 212 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 213 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 214 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 215 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 216 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 217 | C:\Users\chris\AppData\Local\JetBrains\Transient\ReSharperPlatformVs15\v11_47ebdade\SolutionCaches 218 | LIVE_MONITOR 219 | LIVE_MONITOR 220 | DO_NOTHING 221 | LIVE_MONITOR 222 | LIVE_MONITOR 223 | LIVE_MONITOR 224 | LIVE_MONITOR 225 | LIVE_MONITOR 226 | LIVE_MONITOR 227 | LIVE_MONITOR 228 | LIVE_MONITOR 229 | DO_NOTHING 230 | LIVE_MONITOR 231 | True 232 | True 233 | True 234 | True 235 | True 236 | True 237 | True 238 | True 239 | True 240 | True 241 | True 242 | True 243 | True 244 | True 245 | True 246 | True -------------------------------------------------------------------------------- /docs/nuget-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpatialFocus/EntityFrameworkCore.Extensions/2bb23618cb4c098aef2d15c5a081bb43f6c521e2/docs/nuget-icon.png -------------------------------------------------------------------------------- /samples/SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo/Data/DemoContext.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo.Data 7 | { 8 | using System; 9 | using Microsoft.EntityFrameworkCore; 10 | using SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo.Entities; 11 | 12 | public partial class DemoContext : DbContext 13 | { 14 | public DemoContext() 15 | { 16 | Database.EnsureDeleted(); 17 | Database.EnsureCreated(); 18 | } 19 | 20 | public DemoContext(DbContextOptions options) 21 | : base(options) 22 | { 23 | Database.EnsureDeleted(); 24 | Database.EnsureCreated(); 25 | } 26 | 27 | public DbSet Products { get; set; } 28 | 29 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 30 | { 31 | base.OnConfiguring(optionsBuilder); 32 | 33 | if (!optionsBuilder.IsConfigured) 34 | { 35 | optionsBuilder.UseSqlite("Data Source=demo.db"); 36 | } 37 | } 38 | 39 | protected override void OnModelCreating(ModelBuilder modelBuilder) 40 | { 41 | // Any custom model configuration should come before calling ConfigureEnumLookup and ConfigureNames 42 | modelBuilder.Entity() 43 | .OwnsMany(nameof(Product.Reviews), 44 | builder => 45 | { 46 | builder.ConfigureOwnedEnumLookup(EnumLookupOptions.Default.Pluralize().UseStringAsIdentifier(), modelBuilder); 47 | }); 48 | 49 | modelBuilder.ConfigureEnumLookup(EnumLookupOptions.Default.Pluralize().UseStringAsIdentifier()); 50 | 51 | modelBuilder.ConfigureNames(NamingOptions.Default.Pluralize() 52 | ////.SetTableNamingSource(NamingSource.ClrType) 53 | ////.SetNamingScheme(NamingScheme.SnakeCase) 54 | ////.OverrideTableNaming(NamingScheme.SnakeCase) 55 | .SkipTableNamingForGenericEntityTypes()); 56 | 57 | modelBuilder.Entity() 58 | .HasData( 59 | new Product 60 | { 61 | ProductId = 1, 62 | ProductCategory = ProductCategory.Book, 63 | Name = "Robinson Crusoe", 64 | ReleaseDate = new DateTime(1719, 4, 25), 65 | Price = 14.99, 66 | }, 67 | new Product 68 | { 69 | ProductId = 2, 70 | ProductCategory = ProductCategory.Bluray, 71 | Name = "Rogue One: A Star Wars Story", 72 | ReleaseDate = new DateTime(2017, 5, 4), 73 | Price = 11.99, 74 | }, 75 | new Product 76 | { 77 | ProductId = 3, 78 | ProductCategory = ProductCategory.CD, 79 | Name = "Wham! - Last Christmas", 80 | ReleaseDate = new DateTime(1984, 12, 3), 81 | Price = 6.97, 82 | IdealForSpecialOccasion = SpecialOccasion.Christmas, 83 | }); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /samples/SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo/Entities/Product.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo.Entities 7 | { 8 | using System; 9 | using System.Collections.Generic; 10 | 11 | public class Product 12 | { 13 | public Product() 14 | { 15 | Created = DateTime.Now; 16 | } 17 | 18 | public DateTime Created { get; set; } 19 | 20 | public SpecialOccasion? IdealForSpecialOccasion { get; set; } 21 | 22 | public string Name { get; set; } 23 | 24 | public double Price { get; set; } 25 | 26 | public ProductCategory ProductCategory { get; set; } 27 | 28 | public int ProductId { get; set; } 29 | 30 | public DateTime ReleaseDate { get; set; } 31 | 32 | public List Reviews { get; set; } 33 | } 34 | } -------------------------------------------------------------------------------- /samples/SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo/Entities/ProductCategory.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo.Entities 7 | { 8 | public enum ProductCategory 9 | { 10 | Book = 1, 11 | 12 | Bluray, 13 | 14 | CD, 15 | 16 | DVD, 17 | 18 | Other, 19 | } 20 | } -------------------------------------------------------------------------------- /samples/SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo/Entities/Review.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo.Entities 7 | { 8 | public class Review 9 | { 10 | public ReviewRating Rating { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /samples/SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo/Entities/ReviewRating.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo.Entities 7 | { 8 | public enum ReviewRating 9 | { 10 | Excellent = 1, 11 | 12 | Average, 13 | 14 | Bad, 15 | } 16 | } -------------------------------------------------------------------------------- /samples/SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo/Entities/SpecialOccasion.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo.Entities 7 | { 8 | using System.ComponentModel; 9 | 10 | public enum SpecialOccasion 11 | { 12 | [Description("Your birth anniversary")] 13 | Birthday = 1, 14 | 15 | [Description("Jesus' birth anniversary")] 16 | Christmas, 17 | 18 | [Description("Jesus' resurrection anniversary")] 19 | Easter, 20 | 21 | [Description("Florist holiday")] 22 | Valentines, 23 | 24 | [Description("Marriage anniversary")] 25 | WeddingDay, 26 | } 27 | } -------------------------------------------------------------------------------- /samples/SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo/Program.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo 7 | { 8 | using System; 9 | using System.Linq; 10 | using SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo.Data; 11 | 12 | public class Program 13 | { 14 | private static void Main(string[] args) 15 | { 16 | using (DemoContext context = new DemoContext()) 17 | { 18 | Console.WriteLine($"Found {context.Products.Count()} products."); 19 | } 20 | 21 | Console.WriteLine("--- press a key ---"); 22 | Console.ReadKey(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /samples/SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo/SpatialFocus.EntityFrameworkCore.Extensions.SQLiteDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/SpatialFocus.EntityFrameworkCore.Extensions/EnumLookupAttribute.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions 7 | { 8 | using System; 9 | 10 | public class EnumLookupAttribute : Attribute 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /src/SpatialFocus.EntityFrameworkCore.Extensions/EnumLookupExtension.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions 7 | { 8 | using System; 9 | using System.Collections.Generic; 10 | using System.ComponentModel; 11 | using System.Linq; 12 | using System.Reflection; 13 | using Microsoft.EntityFrameworkCore; 14 | using Microsoft.EntityFrameworkCore.Metadata; 15 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 16 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 17 | 18 | public static class EnumLookupExtension 19 | { 20 | public static void ConfigureEnumLookup(this ModelBuilder modelBuilder, EnumLookupOptions enumOptions) 21 | { 22 | foreach (IMutableProperty property in modelBuilder.Model.GetEntityTypes().SelectMany(x => x.GetProperties()).ToList()) 23 | { 24 | IMutableEntityType entityType = property.DeclaringEntityType; 25 | 26 | if (entityType.IsOwned()) 27 | { 28 | continue; 29 | } 30 | 31 | ConfigureEnumLookupForProperty(modelBuilder, enumOptions, property, 32 | (enumLookupEntityType) => 33 | { 34 | modelBuilder.Entity(entityType.Name) 35 | .HasOne(enumLookupEntityType) 36 | .WithMany() 37 | .HasPrincipalKey("Id") 38 | .HasForeignKey(property.Name) 39 | .OnDelete(enumOptions.DeleteBehavior); 40 | }, (valueConverter) => { modelBuilder.Entity(entityType.Name).Property(property.Name).HasConversion(valueConverter); }); 41 | } 42 | } 43 | 44 | public static void ConfigureOwnedEnumLookup( 45 | this OwnedNavigationBuilder ownedNavigationBuilder, EnumLookupOptions enumOptions, 46 | ModelBuilder modelBuilder) where TEntity : class where TDependentEntity : class 47 | { 48 | foreach (IMutableProperty property in ownedNavigationBuilder.OwnedEntityType.GetProperties().ToList()) 49 | { 50 | ConfigureEnumLookupForProperty(modelBuilder, enumOptions, property, 51 | (enumLookupEntityType) => 52 | { 53 | ownedNavigationBuilder.HasOne(enumLookupEntityType) 54 | .WithMany() 55 | .HasPrincipalKey("Id") 56 | .HasForeignKey(property.Name) 57 | .OnDelete(enumOptions.DeleteBehavior); 58 | }, (valueConverter) => { ownedNavigationBuilder.Property(property.Name).HasConversion(valueConverter); }); 59 | } 60 | } 61 | 62 | public static string GetEnumDescription(Enum value) 63 | { 64 | FieldInfo fieldInfo = value.GetType().GetField(value.ToString()); 65 | 66 | DescriptionAttribute attribute = (DescriptionAttribute)fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute), true); 67 | 68 | return attribute?.Description; 69 | } 70 | 71 | // See https://github.com/aspnet/EntityFrameworkCore/issues/12248#issuecomment-395450990 72 | private static void ConfigureEnumLookupForProperty(ModelBuilder modelBuilder, EnumLookupOptions enumOptions, 73 | IMutableProperty property, Action configureEntityType, Action configureEntityTypeConversion) 74 | { 75 | Type propertyType = property.ClrType; 76 | 77 | if (ShouldSkip(propertyType, enumOptions)) 78 | { 79 | return; 80 | } 81 | 82 | Type enumType = propertyType.GetEnumOrNullableEnumType(); 83 | 84 | Dictionary enumValueDescriptions = GetEnumValueDescriptions(enumType); 85 | 86 | bool usesDescription = enumValueDescriptions.Values.Any(x => x != null); 87 | 88 | Type enumLookupEntityType = GetEnumLookupEntityType(enumOptions, usesDescription, enumType); 89 | bool shouldSkipEnumLookupTableConfiguration = modelBuilder.Model.FindEntityType(enumLookupEntityType) != null; 90 | 91 | configureEntityType(enumLookupEntityType); 92 | 93 | ValueConverter valueConverter = GetValueConverter(enumType); 94 | 95 | if (!enumOptions.UseNumberLookup) 96 | { 97 | configureEntityTypeConversion(valueConverter); 98 | } 99 | 100 | if (shouldSkipEnumLookupTableConfiguration) 101 | { 102 | return; 103 | } 104 | 105 | EntityTypeBuilder enumLookupBuilder = modelBuilder.Entity(enumLookupEntityType); 106 | ConfigureEnumLookupTable(enumLookupBuilder, enumOptions, enumType); 107 | 108 | if (enumOptions.UseNumberLookup) 109 | { 110 | modelBuilder.Entity(enumLookupEntityType).HasIndex("Name").IsUnique(); 111 | } 112 | else 113 | { 114 | modelBuilder.Entity(enumLookupEntityType).Property("Id").HasConversion(valueConverter); 115 | } 116 | 117 | // TODO: Check status of https://github.com/aspnet/EntityFrameworkCore/issues/12194 before using migrations 118 | enumLookupBuilder.HasData(GetEnumData(enumType, enumLookupEntityType, enumOptions.UseNumberLookup, usesDescription, 119 | enumValueDescriptions)); 120 | } 121 | 122 | private static void ConfigureEnumLookupTable(EntityTypeBuilder enumLookupBuilder, EnumLookupOptions enumOptions, Type enumType) 123 | { 124 | string typeName = enumType.Name; 125 | string tableName = enumOptions.NamingFunction(typeName); 126 | enumLookupBuilder.ToTable(tableName); 127 | } 128 | 129 | private static object[] GetEnumData(Type enumType, Type concreteType, bool useNumberLookup, bool usesDescription, 130 | Dictionary enumValueDescriptions) 131 | { 132 | return Enum.GetValues(enumType) 133 | .OfType() 134 | .Select(x => 135 | { 136 | object instance = Activator.CreateInstance(concreteType); 137 | 138 | concreteType.GetProperty("Id").SetValue(instance, x); 139 | 140 | if (useNumberLookup) 141 | { 142 | concreteType.GetProperty("Name").SetValue(instance, x.ToString()); 143 | } 144 | 145 | if (usesDescription) 146 | { 147 | concreteType.GetProperty("Description").SetValue(instance, enumValueDescriptions[(int)x]); 148 | } 149 | 150 | return instance; 151 | }) 152 | .ToArray(); 153 | } 154 | 155 | private static Type GetEnumLookupEntityType(EnumLookupOptions enumOptions, bool usesDescription, Type enumType) 156 | { 157 | Type concreteType; 158 | if (usesDescription) 159 | { 160 | concreteType = enumOptions.UseNumberLookup 161 | ? typeof(EnumWithNumberLookupAndDescription<>).MakeGenericType(enumType) 162 | : typeof(EnumWithStringLookupAndDescription<>).MakeGenericType(enumType); 163 | } 164 | else 165 | { 166 | concreteType = enumOptions.UseNumberLookup 167 | ? typeof(EnumWithNumberLookup<>).MakeGenericType(enumType) 168 | : typeof(EnumWithStringLookup<>).MakeGenericType(enumType); 169 | } 170 | 171 | return concreteType; 172 | } 173 | 174 | private static Type GetEnumOrNullableEnumType(this Type propertyType) 175 | { 176 | if (!propertyType.IsEnumOrNullableEnumType()) 177 | { 178 | return null; 179 | } 180 | 181 | return propertyType.IsEnum ? propertyType : propertyType.GetGenericArguments()[0]; 182 | } 183 | 184 | private static Dictionary GetEnumValueDescriptions(Type enumType) 185 | { 186 | return Enum.GetValues(enumType).Cast().ToDictionary(Convert.ToInt32, GetEnumDescription); 187 | } 188 | 189 | private static ValueConverter GetValueConverter(Type enumType) 190 | { 191 | Type converterType = typeof(EnumToStringConverter<>).MakeGenericType(enumType); 192 | ValueConverter valueConverter = (ValueConverter)Activator.CreateInstance(converterType, new object[] { null }); 193 | return valueConverter; 194 | } 195 | 196 | private static bool HasEnumLookupAttribute(this Type propertyType) 197 | { 198 | if (propertyType.GetCustomAttributes(typeof(EnumLookupAttribute), true).Any()) 199 | { 200 | return true; 201 | } 202 | 203 | if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) 204 | { 205 | if (propertyType.GetGenericArguments()[0].GetCustomAttributes(typeof(EnumLookupAttribute), true).Any()) 206 | { 207 | return true; 208 | } 209 | } 210 | 211 | return false; 212 | } 213 | 214 | private static bool IsEnumOrNullableEnumType(this Type propertyType) 215 | { 216 | if (propertyType.IsEnum) 217 | { 218 | return true; 219 | } 220 | 221 | if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) 222 | { 223 | if (propertyType.GetGenericArguments()[0].IsEnum) 224 | { 225 | return true; 226 | } 227 | } 228 | 229 | return false; 230 | } 231 | 232 | private static bool ShouldSkip(Type propertyType, EnumLookupOptions enumOptions) 233 | { 234 | if (!propertyType.IsEnumOrNullableEnumType()) 235 | { 236 | return true; 237 | } 238 | 239 | if (enumOptions.UseEnumsWithAttributesOnly && !propertyType.HasEnumLookupAttribute()) 240 | { 241 | return true; 242 | } 243 | 244 | return false; 245 | } 246 | } 247 | } -------------------------------------------------------------------------------- /src/SpatialFocus.EntityFrameworkCore.Extensions/EnumLookupOptions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions 7 | { 8 | using System; 9 | using Humanizer; 10 | using Microsoft.EntityFrameworkCore; 11 | 12 | public class EnumLookupOptions 13 | { 14 | private Func namingFunction = name => name; 15 | 16 | private Func postProcessingTableNamingFunction = name => name; 17 | 18 | public static EnumLookupOptions Default 19 | { 20 | get 21 | { 22 | EnumLookupOptions enumOptions = new EnumLookupOptions(); 23 | enumOptions.SetNamingScheme(NamingScheme.SnakeCase); 24 | enumOptions.UseNumberLookup = true; 25 | enumOptions.UseEnumsWithAttributesOnly = false; 26 | enumOptions.DeleteBehavior = DeleteBehavior.Cascade; 27 | 28 | return enumOptions; 29 | } 30 | } 31 | 32 | internal Func NamingFunction => name => this.postProcessingTableNamingFunction(this.namingFunction(name)); 33 | 34 | internal DeleteBehavior DeleteBehavior { get; private set; } 35 | 36 | internal bool UseEnumsWithAttributesOnly { get; private set; } 37 | 38 | internal bool UseNumberLookup { get; private set; } 39 | 40 | public EnumLookupOptions Pluralize() 41 | { 42 | this.postProcessingTableNamingFunction = name => name.Pluralize(false); 43 | return this; 44 | } 45 | 46 | public EnumLookupOptions SetDeleteBehavior(DeleteBehavior deleteBehavior) 47 | { 48 | DeleteBehavior = deleteBehavior; 49 | return this; 50 | } 51 | 52 | public EnumLookupOptions SetNamingScheme(Func namingFunc) 53 | { 54 | this.namingFunction = namingFunc; 55 | return this; 56 | } 57 | 58 | public EnumLookupOptions Singularize() 59 | { 60 | this.postProcessingTableNamingFunction = name => name.Singularize(false); 61 | return this; 62 | } 63 | 64 | public EnumLookupOptions UseEnumsWithAttributeOnly() 65 | { 66 | UseEnumsWithAttributesOnly = true; 67 | return this; 68 | } 69 | 70 | public EnumLookupOptions UseNumberAsIdentifier() 71 | { 72 | UseNumberLookup = true; 73 | return this; 74 | } 75 | 76 | public EnumLookupOptions UseStringAsIdentifier() 77 | { 78 | UseNumberLookup = false; 79 | return this; 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/SpatialFocus.EntityFrameworkCore.Extensions/EnumWithNumberLookup.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions 7 | { 8 | using System.ComponentModel.DataAnnotations.Schema; 9 | 10 | public class EnumWithNumberLookup 11 | { 12 | public EnumWithNumberLookup() 13 | { 14 | } 15 | 16 | public EnumWithNumberLookup(T value) 17 | { 18 | Id = value; 19 | Name = value.ToString(); 20 | } 21 | 22 | [DatabaseGenerated(DatabaseGeneratedOption.None)] 23 | public T Id { get; set; } 24 | 25 | public string Name { get; set; } 26 | } 27 | } -------------------------------------------------------------------------------- /src/SpatialFocus.EntityFrameworkCore.Extensions/EnumWithNumberLookupAndDescription.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions 7 | { 8 | public class EnumWithNumberLookupAndDescription : EnumWithNumberLookup 9 | { 10 | public EnumWithNumberLookupAndDescription() 11 | : base() 12 | { 13 | } 14 | 15 | public EnumWithNumberLookupAndDescription(T value) 16 | : base(value) 17 | { 18 | } 19 | 20 | public string Description { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /src/SpatialFocus.EntityFrameworkCore.Extensions/EnumWithStringLookup.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions 7 | { 8 | using System.ComponentModel.DataAnnotations.Schema; 9 | 10 | public class EnumWithStringLookup 11 | { 12 | public EnumWithStringLookup() 13 | { 14 | } 15 | 16 | public EnumWithStringLookup(T value) 17 | { 18 | Id = value; 19 | } 20 | 21 | [DatabaseGenerated(DatabaseGeneratedOption.None)] 22 | public T Id { get; set; } 23 | } 24 | } -------------------------------------------------------------------------------- /src/SpatialFocus.EntityFrameworkCore.Extensions/EnumWithStringLookupAndDescription.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions 7 | { 8 | public class EnumWithStringLookupAndDescription : EnumWithStringLookup 9 | { 10 | public EnumWithStringLookupAndDescription() 11 | : base() 12 | { 13 | } 14 | 15 | public EnumWithStringLookupAndDescription(T value) 16 | : base(value) 17 | { 18 | } 19 | 20 | public string Description { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /src/SpatialFocus.EntityFrameworkCore.Extensions/NamingExtension.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions 7 | { 8 | using System.Linq; 9 | using Microsoft.EntityFrameworkCore; 10 | using Microsoft.EntityFrameworkCore.Metadata; 11 | 12 | public static class NamingExtension 13 | { 14 | public static void ConfigureNames(this ModelBuilder modelBuilder, NamingOptions namingOptions) 15 | { 16 | foreach (IMutableEntityType entity in modelBuilder.Model.GetEntityTypes()) 17 | { 18 | if (namingOptions.EntitiesToSkipEntirely(entity)) 19 | { 20 | continue; 21 | } 22 | 23 | // Entity / Table 24 | if (!namingOptions.EntitiesToSkipTableNaming(entity)) 25 | { 26 | string tableName = namingOptions.TableNameSource(entity); 27 | 28 | entity.SetTableName(namingOptions.TableNamingFunction(tableName)); 29 | } 30 | 31 | // Properties 32 | entity.GetProperties() 33 | .ToList() 34 | #if NET5_0_OR_GREATER 35 | .ForEach(x => x.SetColumnName(namingOptions.ColumnNamingFunction(x.GetColumnBaseName()))); 36 | #else 37 | .ForEach(x => x.SetColumnName(namingOptions.ColumnNamingFunction(x.GetColumnName()))); 38 | #endif 39 | 40 | // Primary and Alternative keys 41 | entity.GetKeys().ToList().ForEach(x => x.SetName(namingOptions.ConstraintNamingFunction(x.GetName()))); 42 | 43 | // Foreign keys 44 | entity.GetForeignKeys() 45 | .ToList() 46 | .ForEach(x => x.SetConstraintName(namingOptions.ConstraintNamingFunction(x.GetConstraintName()))); 47 | 48 | // Indices 49 | entity.GetIndexes() 50 | .ToList() 51 | #if NET5_0_OR_GREATER 52 | .ForEach(x => x.SetDatabaseName(namingOptions.ConstraintNamingFunction(x.GetDatabaseName()))); 53 | #else 54 | .ForEach(x => x.SetName(namingOptions.ConstraintNamingFunction(x.GetName()))); 55 | #endif 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/SpatialFocus.EntityFrameworkCore.Extensions/NamingOptions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions 7 | { 8 | using System; 9 | using Humanizer; 10 | using Microsoft.EntityFrameworkCore.Metadata; 11 | 12 | public class NamingOptions 13 | { 14 | private Func columnNamingFunction; 15 | 16 | private Func constraintNamingFunction; 17 | 18 | private Func entitiesToSkipEntirely; 19 | 20 | private Func entitiesToSkipTableNaming; 21 | 22 | private Func postProcessingTableNamingFunction = name => name; 23 | 24 | private Func tableNamingFunction; 25 | 26 | public static NamingOptions Default => 27 | new NamingOptions().SetTableNamingSource(NamingSource.DbSet).SetNamingScheme(NamingScheme.SnakeCase); 28 | 29 | internal Func ColumnNamingFunction 30 | { 31 | get => this.columnNamingFunction ?? NamingFunction; 32 | set => this.columnNamingFunction = value; 33 | } 34 | 35 | internal Func ConstraintNamingFunction 36 | { 37 | get => this.constraintNamingFunction ?? NamingFunction; 38 | set => this.constraintNamingFunction = value; 39 | } 40 | 41 | internal Func EntitiesToSkipEntirely 42 | { 43 | get => this.entitiesToSkipEntirely ?? (type => false); 44 | set => this.entitiesToSkipEntirely = value; 45 | } 46 | 47 | internal Func EntitiesToSkipTableNaming 48 | { 49 | get => this.entitiesToSkipTableNaming ?? (type => false); 50 | set => this.entitiesToSkipTableNaming = value; 51 | } 52 | 53 | internal Func NamingFunction { get; set; } = name => name; 54 | 55 | internal Func TableNameSource { get; set; } = NamingSource.DbSet; 56 | 57 | internal Func TableNamingFunction 58 | { 59 | get => 60 | name => this.postProcessingTableNamingFunction(this.tableNamingFunction != null 61 | ? this.tableNamingFunction(name) 62 | : NamingFunction(name)); 63 | set => this.tableNamingFunction = value; 64 | } 65 | 66 | public NamingOptions OverrideColumnNaming(Func namingFunc) 67 | { 68 | ColumnNamingFunction = namingFunc; 69 | return this; 70 | } 71 | 72 | public NamingOptions OverrideConstraintNaming(Func namingFunc) 73 | { 74 | ConstraintNamingFunction = namingFunc; 75 | return this; 76 | } 77 | 78 | public NamingOptions OverrideTableNaming(Func namingFunc) 79 | { 80 | TableNamingFunction = namingFunc; 81 | return this; 82 | } 83 | 84 | public NamingOptions Pluralize() 85 | { 86 | this.postProcessingTableNamingFunction = name => name.Pluralize(false); 87 | return this; 88 | } 89 | 90 | public NamingOptions SetNamingScheme(Func namingFunc) 91 | { 92 | NamingFunction = namingFunc; 93 | return this; 94 | } 95 | 96 | public NamingOptions SetTableNamingSource(Func namingFunc) 97 | { 98 | TableNameSource = namingFunc; 99 | return this; 100 | } 101 | 102 | public NamingOptions Singularize() 103 | { 104 | this.postProcessingTableNamingFunction = name => name.Singularize(false); 105 | return this; 106 | } 107 | 108 | public NamingOptions SkipEntireEntities(Func skipFunction) 109 | { 110 | EntitiesToSkipEntirely = skipFunction; 111 | return this; 112 | } 113 | 114 | public NamingOptions SkipTableNamingForEntities(Func skipFunction) 115 | { 116 | EntitiesToSkipTableNaming = skipFunction; 117 | return this; 118 | } 119 | 120 | public NamingOptions SkipTableNamingForGenericEntityTypes() 121 | { 122 | return SkipTableNamingForEntities(entity => entity.ClrType.IsGenericType && 123 | (entity.ClrType.GetGenericTypeDefinition() == typeof(EnumWithNumberLookup<>) || 124 | entity.ClrType.GetGenericTypeDefinition() == typeof(EnumWithNumberLookupAndDescription<>) || 125 | entity.ClrType.GetGenericTypeDefinition() == typeof(EnumWithStringLookup<>) || 126 | entity.ClrType.GetGenericTypeDefinition() == typeof(EnumWithNumberLookupAndDescription<>))); 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /src/SpatialFocus.EntityFrameworkCore.Extensions/NamingScheme.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions 7 | { 8 | using System; 9 | using System.Text.RegularExpressions; 10 | 11 | public static class NamingScheme 12 | { 13 | public static Func KebabCase => 14 | (name) => 15 | { 16 | Regex underscoreRegex = new Regex(@"(?<=[a-z0-9])([A-Z])(?![A-Z])"); 17 | return underscoreRegex.Replace(name, @"-$0").ToLower(); 18 | }; 19 | 20 | public static Func ScreamingSnakeCase => 21 | (name) => 22 | { 23 | Regex underscoreRegex = new Regex(@"(?<=[a-z0-9])([A-Z])(?![A-Z])"); 24 | return underscoreRegex.Replace(name, @"_$0").ToUpper(); 25 | }; 26 | 27 | public static Func SnakeCase => 28 | (name) => 29 | { 30 | Regex underscoreRegex = new Regex(@"(?<=[a-z0-9])([A-Z])(?![A-Z])"); 31 | return underscoreRegex.Replace(name, @"_$0").ToLower(); 32 | }; 33 | } 34 | } -------------------------------------------------------------------------------- /src/SpatialFocus.EntityFrameworkCore.Extensions/NamingSource.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions 7 | { 8 | using System; 9 | using Microsoft.EntityFrameworkCore; 10 | using Microsoft.EntityFrameworkCore.Metadata; 11 | 12 | public static class NamingSource 13 | { 14 | public static Func ClrType => entity => entity.ClrType.Name; 15 | 16 | public static Func DbSet => entity => entity.GetTableName(); 17 | } 18 | } -------------------------------------------------------------------------------- /src/SpatialFocus.EntityFrameworkCore.Extensions/SpatialFocus.EntityFrameworkCore.Extensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;net6.0;net8.0;netstandard2.1 5 | 6 | 7 | 8 | 2.3.0 9 | SpatialFocus.EntityFrameworkCore.Extensions 10 | Spatial Focus EntityFrameworkCore Extensions 11 | A set of useful extensions for EntityFrameworkCore (Enum Lookup Tables, Naming of tables / properties / keys, Pluralize). 12 | EntityFrameworkCore,EFCore,enum lookup,naming helpers 13 | https://github.com/SpatialFocus/SpatialFocus.EntityFrameworkCore.Extensions 14 | MIT 15 | nuget-icon.png 16 | https://raw.githubusercontent.com/SpatialFocus/SpatialFocus.EntityFrameworkCore.Extensions/master/docs/nuget-icon.png 17 | git 18 | https://github.com/SpatialFocus/SpatialFocus.EntityFrameworkCore.Extensions.git 19 | Dresel,pergerch 20 | SpatialFocus 21 | ..\..\.nuget 22 | True 23 | 24 | true 25 | snupkg 26 | true 27 | 28 | 29 | 30 | true 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "indentation": { 5 | "tabSize": 4, 6 | "useTabs": true 7 | }, 8 | "layoutRules": { 9 | "newlineAtEndOfFile": "omit" 10 | }, 11 | "documentationRules": { 12 | "companyName": "Spatial Focus", 13 | "copyrightText": "Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} license. See {licenseFile} file in the project root for full license information.", 14 | "variables": { 15 | "licenseName": "MIT", 16 | "licenseFile": "LICENSE" 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /test/SpatialFocus.EntityFrameworkCore.Extensions.Test/Entities/Product.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions.Test.Entities 7 | { 8 | using System; 9 | 10 | public class Product 11 | { 12 | public Product() 13 | { 14 | Created = DateTime.Now; 15 | } 16 | 17 | public DateTime Created { get; set; } 18 | 19 | public SpecialOccasion? IdealForSpecialOccasion { get; set; } 20 | 21 | public string Name { get; set; } 22 | 23 | public double Price { get; set; } 24 | 25 | public ProductCategory ProductCategory { get; set; } 26 | 27 | public int ProductId { get; set; } 28 | 29 | public DateTime ReleaseDate { get; set; } 30 | } 31 | } -------------------------------------------------------------------------------- /test/SpatialFocus.EntityFrameworkCore.Extensions.Test/Entities/ProductCategory.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions.Test.Entities 7 | { 8 | public enum ProductCategory 9 | { 10 | Book = 1, 11 | 12 | Bluray, 13 | 14 | CD, 15 | 16 | DVD, 17 | 18 | Other, 19 | } 20 | } -------------------------------------------------------------------------------- /test/SpatialFocus.EntityFrameworkCore.Extensions.Test/Entities/ProductTag.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions.Test.Entities 7 | { 8 | public class ProductTag 9 | { 10 | public int ProductTagId { get; set; } 11 | 12 | public virtual Product Product { get; set; } 13 | 14 | public string Name { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /test/SpatialFocus.EntityFrameworkCore.Extensions.Test/Entities/SpecialOccasion.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions.Test.Entities 7 | { 8 | using System.ComponentModel; 9 | 10 | public enum SpecialOccasion 11 | { 12 | [Description("Your birth anniversary")] 13 | Birthday = 1, 14 | 15 | [Description("Jesus' birth anniversary")] 16 | Christmas, 17 | 18 | [Description("Jesus' resurrection anniversary")] 19 | Easter, 20 | 21 | [Description("Florist holiday")] 22 | Valentines, 23 | 24 | [Description("Marriage anniversary")] 25 | WeddingDay, 26 | } 27 | } -------------------------------------------------------------------------------- /test/SpatialFocus.EntityFrameworkCore.Extensions.Test/NamingOptionsTest.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions.Test 7 | { 8 | using System; 9 | using System.Linq; 10 | using Microsoft.EntityFrameworkCore; 11 | using Microsoft.EntityFrameworkCore.ChangeTracking; 12 | using Microsoft.EntityFrameworkCore.Metadata; 13 | using Microsoft.EntityFrameworkCore.Storage; 14 | using SpatialFocus.EntityFrameworkCore.Extensions.Test.Entities; 15 | using Xunit; 16 | 17 | public class NamingOptionsTest 18 | { 19 | protected ProductContext GetContext(EnumLookupOptions? enumLookupOptions = null, NamingOptions? namingOptions = null) 20 | { 21 | DbContextOptions options = new DbContextOptionsBuilder() 22 | .UseInMemoryDatabase(Guid.NewGuid().ToString(), new InMemoryDatabaseRoot()) 23 | .Options; 24 | 25 | ProductContext context = new ProductContext(options, null, namingOptions); 26 | context.Database.EnsureCreated(); 27 | 28 | return context; 29 | } 30 | 31 | [Fact] 32 | public void OverrideColumnNaming() 33 | { 34 | ProductContext context = GetContext(namingOptions: new NamingOptions().OverrideColumnNaming(NamingScheme.SnakeCase)); 35 | 36 | IEntityType findEntityType = context.Model.FindEntityType(typeof(ProductTag)); 37 | #if NET5_0_OR_GREATER 38 | Assert.Equal("product_tag_id", findEntityType.FindProperty(nameof(ProductTag.ProductTagId)).GetColumnBaseName()); 39 | #else 40 | Assert.Equal("product_tag_id", findEntityType.FindProperty(nameof(ProductTag.ProductTagId)).GetColumnName()); 41 | #endif 42 | } 43 | 44 | [Fact] 45 | public void OverrideConstraintNaming() 46 | { 47 | ProductContext context = GetContext(namingOptions: new NamingOptions().OverrideConstraintNaming(NamingScheme.SnakeCase)); 48 | 49 | IEntityType findEntityType = context.Model.FindEntityType(typeof(ProductTag)); 50 | Assert.Equal("ProductTag", findEntityType.GetTableName()); 51 | 52 | #if NET5_0_OR_GREATER 53 | Assert.Equal("ProductTagId", findEntityType.FindProperty(nameof(ProductTag.ProductTagId)).GetColumnBaseName()); 54 | #else 55 | Assert.Equal("ProductTagId", findEntityType.FindProperty(nameof(ProductTag.ProductTagId)).GetColumnName()); 56 | #endif 57 | 58 | Assert.True(findEntityType.GetKeys().All(x => x.GetName() == NamingScheme.SnakeCase(x.GetDefaultName()))); 59 | Assert.True(findEntityType.GetForeignKeys().All(x => x.GetConstraintName() == NamingScheme.SnakeCase(x.GetDefaultName()))); 60 | 61 | #if NET5_0_OR_GREATER 62 | Assert.True(findEntityType.GetIndexes().All(x => x.GetDatabaseName() == NamingScheme.SnakeCase(x.GetDefaultDatabaseName()))); 63 | #else 64 | Assert.True(findEntityType.GetIndexes().All(x => x.GetName() == NamingScheme.SnakeCase(x.GetDefaultName()))); 65 | #endif 66 | } 67 | 68 | [Fact] 69 | public void OverrideTableNaming() 70 | { 71 | ProductContext context = GetContext(namingOptions: new NamingOptions().OverrideTableNaming(NamingScheme.SnakeCase)); 72 | 73 | IEntityType findEntityType = context.Model.FindEntityType(typeof(ProductTag)); 74 | Assert.Equal("product_tag", findEntityType.GetTableName()); 75 | } 76 | 77 | [Fact] 78 | public void Pluralize() 79 | { 80 | ProductContext context = GetContext(namingOptions: new NamingOptions().Pluralize()); 81 | 82 | IEntityType findEntityType = context.Model.FindEntityType(typeof(ProductTag)); 83 | Assert.Equal("ProductTags", findEntityType.GetTableName()); 84 | } 85 | 86 | [Fact] 87 | public void PluralizeAndOverrideTableNaming() 88 | { 89 | ProductContext context = GetContext(namingOptions: new NamingOptions().Pluralize().OverrideTableNaming(NamingScheme.SnakeCase)); 90 | 91 | IEntityType findEntityType = context.Model.FindEntityType(typeof(ProductTag)); 92 | Assert.Equal("product_tags", findEntityType.GetTableName()); 93 | } 94 | 95 | #if NET5_0_OR_GREATER 96 | [Fact] 97 | public void SetNamingScheme() 98 | { 99 | ProductContext context = GetContext(namingOptions: new NamingOptions().SetNamingScheme(NamingScheme.SnakeCase)); 100 | 101 | IEntityType findEntityType = context.Model.FindEntityType(typeof(ProductTag)); 102 | Assert.Equal("product_tag", findEntityType.GetTableName()); 103 | Assert.Equal("product_tag_id", findEntityType.FindProperty(nameof(ProductTag.ProductTagId)).GetColumnBaseName()); 104 | Assert.True(findEntityType.GetKeys().All(x => x.GetName() == NamingScheme.SnakeCase(x.GetDefaultName()))); 105 | Assert.True(findEntityType.GetForeignKeys().All(x => x.GetConstraintName() == NamingScheme.SnakeCase(x.GetDefaultName()))); 106 | Assert.True(findEntityType.GetIndexes().All(x => x.GetDatabaseName() == NamingScheme.SnakeCase(x.GetDefaultDatabaseName()))); 107 | } 108 | #else 109 | [Fact] 110 | public void SetNamingScheme() 111 | { 112 | ProductContext context = GetContext(namingOptions: new NamingOptions().SetNamingScheme(NamingScheme.SnakeCase)); 113 | 114 | IEntityType findEntityType = context.Model.FindEntityType(typeof(ProductTag)); 115 | Assert.Equal("product_tag", findEntityType.GetTableName()); 116 | Assert.Equal("product_tag_id", findEntityType.FindProperty(nameof(ProductTag.ProductTagId)).GetColumnName()); 117 | Assert.True(findEntityType.GetKeys().All(x => x.GetName() == NamingScheme.SnakeCase(x.GetDefaultName()))); 118 | Assert.True(findEntityType.GetForeignKeys().All(x => x.GetConstraintName() == NamingScheme.SnakeCase(x.GetDefaultName()))); 119 | Assert.True(findEntityType.GetIndexes().All(x => x.GetName() == NamingScheme.SnakeCase(x.GetDefaultName()))); 120 | } 121 | #endif 122 | 123 | [Fact] 124 | public void MultipleProviders() 125 | { 126 | DbContextOptions inMemoryOptions = new DbContextOptionsBuilder() 127 | .UseInMemoryDatabase(Guid.NewGuid().ToString(), new InMemoryDatabaseRoot()) 128 | .Options; 129 | 130 | ChangeTrackerInConstructorContext _ = new ChangeTrackerInConstructorContext(inMemoryOptions, EnumLookupOptions.Default, null); 131 | 132 | DbContextOptions sqliteOptions = new DbContextOptionsBuilder() 133 | .UseSqlite("Filename=:memory:") 134 | .Options; 135 | 136 | ChangeTrackerInConstructorContext context = new ChangeTrackerInConstructorContext(sqliteOptions, EnumLookupOptions.Default, null); 137 | context.Database.EnsureCreated(); 138 | context.Dispose(); 139 | } 140 | 141 | private class ChangeTrackerInConstructorContext : ProductContext 142 | { 143 | public ChangeTrackerInConstructorContext(DbContextOptions options, EnumLookupOptions enumLookupOptions, NamingOptions namingOptions) 144 | : base(options, enumLookupOptions, namingOptions) 145 | { 146 | ChangeTracker _ = ChangeTracker; 147 | } 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /test/SpatialFocus.EntityFrameworkCore.Extensions.Test/ProductContext.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Spatial Focus. All rights reserved. 3 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace SpatialFocus.EntityFrameworkCore.Extensions.Test 7 | { 8 | using System; 9 | using Microsoft.EntityFrameworkCore; 10 | using SpatialFocus.EntityFrameworkCore.Extensions.Test.Entities; 11 | 12 | public class ProductContext : DbContext 13 | { 14 | public ProductContext(DbContextOptions options, EnumLookupOptions enumLookupOptions, NamingOptions namingOptions) 15 | : base(options) 16 | { 17 | EnumLookupOptions = enumLookupOptions; 18 | NamingOptions = namingOptions; 19 | } 20 | 21 | public DbSet Products { get; set; } 22 | 23 | public DbSet ProductTags { get; set; } 24 | 25 | protected EnumLookupOptions EnumLookupOptions { get; } 26 | 27 | protected NamingOptions NamingOptions { get; } 28 | 29 | protected override void OnModelCreating(ModelBuilder modelBuilder) 30 | { 31 | base.OnModelCreating(modelBuilder); 32 | 33 | modelBuilder.Entity() 34 | .HasData( 35 | new Product 36 | { 37 | ProductId = 1, 38 | ProductCategory = ProductCategory.Book, 39 | Name = "Robinson Crusoe", 40 | ReleaseDate = new DateTime(1719, 4, 25), 41 | Price = 14.99, 42 | }, 43 | new Product 44 | { 45 | ProductId = 2, 46 | ProductCategory = ProductCategory.Bluray, 47 | Name = "Rogue One: A Star Wars Story", 48 | ReleaseDate = new DateTime(2017, 5, 4), 49 | Price = 11.99, 50 | }, 51 | new Product 52 | { 53 | ProductId = 3, 54 | ProductCategory = ProductCategory.CD, 55 | Name = "Wham! - Last Christmas", 56 | ReleaseDate = new DateTime(1984, 12, 3), 57 | Price = 6.97, 58 | IdealForSpecialOccasion = SpecialOccasion.Christmas, 59 | }); 60 | 61 | modelBuilder.Entity().HasIndex("ProductId", "Name").IsUnique(); 62 | 63 | if (EnumLookupOptions != null) 64 | { 65 | modelBuilder.ConfigureEnumLookup(EnumLookupOptions); 66 | } 67 | 68 | if (NamingOptions != null) 69 | { 70 | modelBuilder.ConfigureNames(NamingOptions); 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /test/SpatialFocus.EntityFrameworkCore.Extensions.Test/SpatialFocus.EntityFrameworkCore.Extensions.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;net6.0;net8.0;netcoreapp3.1 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | --------------------------------------------------------------------------------