Skip to content

Commit

Permalink
Fix and mix with PedroGGaspar
Browse files Browse the repository at this point in the history
  • Loading branch information
ProDInfo committed Feb 16, 2024
1 parent 26b7895 commit 6372864
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>true</PublishReadyToRun>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>true</PublishReadyToRun>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<ApplicationIcon>server_lightning.ico</ApplicationIcon>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
</ItemGroup>

</Project>
177 changes: 116 additions & 61 deletions DbContextOnModelCreatingSplitter/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,21 @@ public class Options

[Option('s', "suffix", Required = false, HelpText = "Suffix for the generated configuration files (Default: Configuration)")]
public string? Suffix { get; set; }

[Option('B', "no-backup", Required = false, HelpText = "Don't keep a copy of the original DbContext file")]
public bool NoBackup { get; set; }

[Option('r', "no-replace", Default = false, Required = false, HelpText = "Don't replace OnModelCreating event code")]
public bool NoReplacement { get; set; }

[Option('e', "embed-config", Default = false, Required = false, HelpText = "Embed EntityTypeConfiguration class into entity model file")]
public bool EmbedConfigClass { get; set; }
}

class Program
{
static readonly short _indentSizeSource = 4;
static readonly short _indentSize = 4;

static void Main(string[] args)
{
Parser.Default.ParseArguments<Options>(args)
Expand All @@ -34,7 +42,7 @@ private static void Run(Options options)
{
var dbContextFilePath = Path.GetFullPath(options.DbContextPath);
string dbContexFolder = Path.GetDirectoryName(dbContextFilePath) ?? throw new NullReferenceException();
var configurationsDirectoryPath = options.OutputDirectoryPath != null ? Path.GetFullPath(options.OutputDirectoryPath) : dbContexFolder;
var configurationsDirectoryPath = !string.IsNullOrEmpty(options.OutputDirectoryPath) ? Path.GetFullPath(options.OutputDirectoryPath) : dbContexFolder;

var source = File.ReadAllText(dbContextFilePath);

Expand All @@ -44,90 +52,137 @@ private static void Run(Options options)
.Select(m => m.Value)
.ToList();

string contextNamespace = Regex.Match(source, @"(?<=(?:^|\s|;)namespace\s+).*?(?=(?:\s|\{))", RegexOptions.Multiline | RegexOptions.Singleline).Value;
string contextNamespace = Regex.Match(source,
@"(?<=(?:^|\s|;)namespace\s+).*?(?=(?:;|\s|\{))",
RegexOptions.Multiline | RegexOptions.Singleline).Value;

var configurationNamespace = options.Namespace ?? contextNamespace;

const string statementsInnerBlockPattern = @"(?<=modelBuilder\.Entity<(?<EntityName>.*?)>\((?<EntityParameterName>.*?)\s*=>\s*\{).*?(?:;)(?=\r?\n\s*\}\);)";
//const string statementsInnerBlockPattern = @"(?<=modelBuilder\.Entity<(?<EntityName>.*?)>\((?<EntityParameterName>.*?)\s*=>\s*\{).*?(?:;)(?=\s*\}\);)";
Console.WriteLine("Create configClass files:");

var statementsBlockMatches = Regex.Matches(source, statementsInnerBlockPattern, RegexOptions.Multiline | RegexOptions.Singleline)
.ToList();
string modelsBackupDirPath = "";
if (!options.NoBackup & options.EmbedConfigClass)
{
var modelsBackupDir = $"_backup_{DateTime.Now:yyyyMMddHHmmss}";
modelsBackupDirPath = Path.Combine(configurationsDirectoryPath, modelsBackupDir);
Directory.CreateDirectory(modelsBackupDirPath);
}

const string statementsInnerBlockPattern = @"(?<=modelBuilder\.Entity<(?<EntityName>.*?)>\((?<EntityParameterName>.*?)\s*=>\s*\{\r?\n).*?(?:;)(?=\r?\n\s*\}\);(?!\r?\n\s*\}\);))";
//const string statementsInnerBlockPattern = @"(?<=modelBuilder\.Entity<(?<EntityName>.*?)>\((?<EntityParameterName>.*?)\s*=>\s*\{\r?\n).*?(?=\r?\n\s*\}\);)";
//const string statementsOuterBlockPattern = @"\s*modelBuilder\.Entity<.*?>\(.*?\s*=>\s*\{.*?;?.*\}\);";
const string statementsOuterBlockPattern = @"\s*modelBuilder\.Entity<.*?>\(.*?\s*=>\s*\{.*?\r?\n\s*\}\);(?!\r?\n\s*\}\);)\r?\n";

Console.WriteLine("Create configuration files:");
var statementsBlockMatches = Regex.Matches(source, statementsOuterBlockPattern, RegexOptions.Multiline | RegexOptions.Singleline).ToList();

foreach (var blockMatch in statementsBlockMatches)
{
var entityName = blockMatch.Groups["EntityName"].Value;
var entityParameterName = blockMatch.Groups["EntityParameterName"].Value;
var statements = Regex.Replace(blockMatch.Value, @"^\t+", new string(' ', 4), RegexOptions.Multiline)
.TrimStart('\r', '\n', '\t', ' ')
.Replace(new string(' ', 16), new string(' ', 12));
var innerBlock = Regex.Match(blockMatch.Value, statementsInnerBlockPattern, RegexOptions.Multiline | RegexOptions.Singleline);

var entityMaybeFullName = innerBlock.Groups["EntityName"].Value;
var entityName = entityMaybeFullName.Substring(entityMaybeFullName.LastIndexOf('.') + 1);
var entityParameterName = innerBlock.Groups["EntityParameterName"].Value;

var statements = innerBlock.Value;
var indentAtStatementsFirstLine = Regex.Match(statements, @"^\s+").Value;
string suffix = options.Suffix ?? "Configuration";

var configuration = new StringBuilder();

configuration.AppendLine(string.Join(Environment.NewLine, contextUsingStatements));
configuration.AppendLine("using Microsoft.EntityFrameworkCore.Metadata.Builders;");
configuration.AppendLine($"using {contextNamespace};");
configuration.AppendLine();
configuration.AppendLine($"namespace {configurationNamespace}");
configuration.AppendLine("{");
configuration.AppendLine(new string(' ', 4) + $"public class {entityName}{suffix} : IEntityTypeConfiguration<{entityName}>");
configuration.AppendLine(new string(' ', 4) + "{");
configuration.AppendLine(new string(' ', 8) + $"public void Configure(EntityTypeBuilder<{entityName}> {entityParameterName})");
configuration.AppendLine(new string(' ', 8) + "{");
configuration.AppendLine(new string(' ', 12) + statements);
configuration.AppendLine(new string(' ', 8) + "}");
configuration.AppendLine(new string(' ', 4) + "}");
configuration.AppendLine("}");

var configurationContents = configuration.ToString();
var configurationFilePath = Path.Combine(configurationsDirectoryPath, $"{entityName}{suffix}.cs");

Console.WriteLine(new string(' ', 4) + configurationFilePath);

File.WriteAllText(configurationFilePath, configurationContents);
short tabs = 0;
if (!options.EmbedConfigClass)
tabs = 1;

var configClass = new StringBuilder();
configClass.AppendLine(Tab(tabs) + $"public class {entityName}{suffix} : IEntityTypeConfiguration<{entityMaybeFullName}>");
configClass.AppendLine(Tab(tabs) + "{");
tabs++;
configClass.AppendLine(Tab(tabs) + $"public void Configure(EntityTypeBuilder<{entityMaybeFullName}> {entityParameterName})");
configClass.AppendLine(Tab(tabs) + "{");
tabs++;
configClass.AppendLine(statements.Replace(indentAtStatementsFirstLine, Tab(tabs)));
tabs--;
configClass.AppendLine(Tab(tabs) + "}");
tabs--;
configClass.AppendLine(Tab(tabs) + "}");

string configurationFilePath;
if (options.EmbedConfigClass)
{
configurationFilePath = Path.Combine(configurationsDirectoryPath, $"{entityName}.cs");
var modelContents = File.ReadAllText(configurationFilePath);

var usingBlockMatches = Regex.Matches(modelContents, "^using .*?;\r?\n", RegexOptions.Multiline | RegexOptions.Singleline);
var lastUsingLine = usingBlockMatches.Last().Value;
var modifiedUsingBlock = lastUsingLine +
"using Microsoft.EntityFrameworkCore;" + Environment.NewLine +
"using Microsoft.EntityFrameworkCore.Metadata.Builders;" + Environment.NewLine;
modelContents = modelContents.Replace(lastUsingLine, modifiedUsingBlock);
modelContents += Environment.NewLine + configClass.ToString();

if (!options.NoBackup)
{
var backupFilePath = Path.Combine(modelsBackupDirPath, Path.GetFileName(configurationFilePath));
File.Copy(configurationFilePath, backupFilePath, true);
}

File.WriteAllText(configurationFilePath, modelContents);
}
else
{
var configFile = new StringBuilder();
configFile.AppendLine(string.Join(Environment.NewLine, contextUsingStatements));
configFile.AppendLine("using Microsoft.EntityFrameworkCore.Metadata.Builders;");
configFile.AppendLine($"using {contextNamespace};");
configFile.AppendLine();
configFile.AppendLine($"namespace {configurationNamespace}");
configFile.AppendLine("{");
configFile.Append(configClass);
configFile.AppendLine("}");

configurationFilePath = Path.Combine(configurationsDirectoryPath, $"{entityName}{suffix}.cs");
File.WriteAllText(configurationFilePath, configFile.ToString());
}

Console.WriteLine(TabSrc(1) + configurationFilePath);

string srcEntityConfigLine = "";
if (!options.NoReplacement)
srcEntityConfigLine = Environment.NewLine + TabSrc(3) +
$"new {entityName}{suffix}().Configure(modelBuilder.Entity<{entityMaybeFullName}>());";
//$"modelBuilder.ApplyConfiguration(new {entityName}{suffix}());";
source = source.Replace(blockMatch.Value, srcEntityConfigLine);
}

if (!statementsBlockMatches.Any())
{
Console.WriteLine(new string(' ', 4) + "No entity definitions found.");
Console.WriteLine(TabSrc(1) + "No entity definitions found.");
return;
}

const string statementsOuterBlockPattern = @"\s*modelBuilder\.Entity<.*?>\(.*?\s*=>\s*\{.*?;?.*\}\);";

var netcontent = new StringBuilder();
netcontent.AppendLine();
netcontent.AppendLine();
foreach (var blockMatch in statementsBlockMatches)
{
var entityName = blockMatch.Groups["EntityName"].Value;
string suffix = options.Suffix ?? "Configuration";

netcontent.AppendLine(new string(' ', 12) + $"modelBuilder.ApplyConfiguration(new {entityName}{suffix}());");
}
var newConfigurationContents = netcontent.ToString(); //string.Empty

source = Regex.Replace(source, statementsOuterBlockPattern, newConfigurationContents, RegexOptions.Multiline | RegexOptions.Singleline);



if (!options.NoBackup)
{
Console.WriteLine("Backup DbContext file:");
if (options.EmbedConfigClass)
Console.WriteLine($"Backup models files saved at: {modelsBackupDirPath}");
Console.WriteLine();

var backupFilePath = Path.Combine(dbContexFolder, $"{Path.GetFileName(dbContextFilePath)}.{DateTime.Now:yyyyMMddHHmmss}.bak");

Console.WriteLine(new string(' ', 4) + "Original file path:" + dbContextFilePath);
Console.WriteLine(new string(' ', 4) + "Backup file path:" + backupFilePath);

Console.WriteLine("Backup DbContext file:");
Console.WriteLine(TabSrc(1) + $"Original file path: {dbContextFilePath}");
Console.WriteLine(TabSrc(1) + $"Backup file path: {backupFilePath}");
File.Copy(dbContextFilePath, backupFilePath, true);
}

File.WriteAllText(dbContextFilePath, source);
}

private static string TabSrc(short tabsNumber)
{
return new string(' ', _indentSizeSource * tabsNumber); ;
}

private static string Tab(short tabsNumber)
{
return new string(' ', _indentSize * tabsNumber); ;
}

}
}
}
Binary file not shown.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# what's different about this fork:
### Fixes:
- The using statement for contextNamespace had two semicolons at the end of line.
- The modelBuilder not capture all lambda.

### News:
- Suffix for the generated configuration files, by using the 'suffix' option at command-line ('-s').
- Code that invokes configuration class is inserted at OnModelCreating event, but can be ignored by 'no-replace' option at command-line ('-r').
- The configuration class can now be embedded into entity model file, by using the 'embed-config' option at command-line ('-e').
---

# DbContext OnModelCreating Splitter
Command line tool that splits FluentAPI entity definitions from `DbContext.OnModelCreating()` into separate configuration files.

Expand Down

0 comments on commit 6372864

Please sign in to comment.