Skip to content

Commit

Permalink
Use a naming service to produce candidate names for identifiers and
Browse files Browse the repository at this point in the history
changing the rules to use any non-letter, non-digit as a word-separator
along with any change from lower to upper case.
  • Loading branch information
lajones committed May 26, 2016
1 parent 14e1690 commit f2f90ca
Show file tree
Hide file tree
Showing 53 changed files with 656 additions and 474 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -392,48 +392,5 @@ private static bool IsLetterChar(UnicodeCategory cat)

return false;
}

private static readonly char[] _wordSeparators = new char[] { '_', '-' };
/// <summary>
/// Attempts to produce a Pascal case identifier by splitting the input
/// identifier into words using '-' and '_' as separator characters. A
/// "word" which contains only upper case characters will then be converted
/// to lower case. Then, always, the first character of each "word" is
/// converted to upper case.
/// </summary>
/// <param name="identifier"> the initial identifier </param>
/// <returns> the Pascal-cased identifier </returns>
public static string Pascalize([NotNull] string identifier)
{
Check.NotEmpty(identifier, nameof(identifier));

string result = string.Empty;
var words = identifier.Split(_wordSeparators, StringSplitOptions.RemoveEmptyEntries);
foreach (var word in words)
{
var firstCharacter = Char.ToUpperInvariant(word[0]);
var restOfWord = word.Substring(1);
result += IsAllUpperCase(word)
? firstCharacter + restOfWord.ToLowerInvariant()
: firstCharacter + restOfWord;
}

return result;
}

private static bool IsAllUpperCase([NotNull] string word)
{
Check.NotEmpty(word, nameof(word));

foreach (char c in word)
{
if (Char.IsLower(c))
{
return false;
}
}

return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal
{
public class CandidateNamingService
{
/// <summary>
/// Generates a candidate identifier from an original identifier using the following rules.
/// Split the original identifier into words using as word separator either a change from
/// lower to upper case or a non-letter, non-digit character. Then ignore any non-letter, non-digit
/// characters. Then upper-case the first character of each word and lower-case the remaining
/// characters. Then concatenate the words back together into a single candidate identifier.
/// </summary>
/// <param name="originalIdentifier"> the original identifier </param>
/// <returns> the candidate identifier </returns>
public virtual string GenerateCandidateIdentifier([NotNull] string originalIdentifier)
{
Check.NotEmpty(originalIdentifier, nameof(originalIdentifier));

var candidateStringBuilder = new StringBuilder();
var previousLetterCharInWordIsLowerCase = false;
var isFirstCharacterInWord = true;
foreach (char c in originalIdentifier)
{
var isNotLetterOrDigit = !char.IsLetterOrDigit(c);
if (isNotLetterOrDigit
|| (previousLetterCharInWordIsLowerCase && char.IsUpper(c)))
{
isFirstCharacterInWord = true;
previousLetterCharInWordIsLowerCase = false;
if (isNotLetterOrDigit)
{
continue;
}
}

candidateStringBuilder.Append(
isFirstCharacterInWord ? char.ToUpperInvariant(c) : char.ToLowerInvariant(c));
isFirstCharacterInWord = false;
if (char.IsLower(c))
{
previousLetterCharInWordIsLowerCase = true;
}
}

return candidateStringBuilder.ToString();
}

public virtual string GetDependentEndCandidateNavigationPropertyName([NotNull] IForeignKey foreignKey)
{
Check.NotNull(foreignKey, nameof(foreignKey));

var candidateName = FindCandidateNavigationName(foreignKey.Properties);

if (!string.IsNullOrEmpty(candidateName))
{
return candidateName;
}

return foreignKey.PrincipalEntityType.DisplayName();
}

public virtual string GetPrincipalEndCandidateNavigationPropertyName(
[NotNull] IForeignKey foreignKey,
[NotNull] string dependentEndNavigationPropertyName)
{
Check.NotNull(foreignKey, nameof(foreignKey));
Check.NotEmpty(dependentEndNavigationPropertyName, nameof(dependentEndNavigationPropertyName));

var allForeignKeysBetweenDependentAndPrincipal =
foreignKey.PrincipalEntityType?
.GetReferencingForeignKeys()
.Where(fk => foreignKey.DeclaringEntityType == fk.DeclaringEntityType);

if (allForeignKeysBetweenDependentAndPrincipal != null
&& allForeignKeysBetweenDependentAndPrincipal.Count() > 1)
{
return foreignKey.DeclaringEntityType.DisplayName()
+ dependentEndNavigationPropertyName;
}

return foreignKey.DeclaringEntityType.DisplayName();
}

private string FindCandidateNavigationName(IEnumerable<IProperty> properties)
{
if (!properties.Any())
{
return string.Empty;
}

var candidateName = string.Empty;
var firstProperty = properties.First();
if (properties.Count() == 1)
{
candidateName = firstProperty.Name;
}
else
{
candidateName = FindCommonPrefix(firstProperty.Name, properties.Select(p => p.Name));
}

return StripId(candidateName, properties);
}

private string FindCommonPrefix(string firstName, IEnumerable<string> propertyNames)
{
var prefixLength = 0;
foreach (var c in firstName)
{
foreach (var s in propertyNames)
{
if (s.Length <= prefixLength
|| s[prefixLength] != c)
{
return firstName.Substring(0, prefixLength);
}
}

prefixLength++;
}

return firstName.Substring(0, prefixLength);
}

private string StripId(string commonPrefix, IEnumerable<IProperty> properties)
{
if (commonPrefix.Length > 2
&& commonPrefix.EndsWith("id", StringComparison.OrdinalIgnoreCase))
{
return commonPrefix.Substring(0, commonPrefix.Length - 2);
}

return commonPrefix;
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@
<Link>StringBuilderExtensions.cs</Link>
</Compile>
<Compile Include="IDatabaseModelFactory.cs" />
<Compile Include="Internal\CandidateNamingService.cs" />
<Compile Include="Internal\CSharpNamer.cs" />
<Compile Include="Internal\CSharpUniqueNamer.cs" />
<Compile Include="Internal\CSharpUtilities.cs" />
<Compile Include="Internal\DbDataReaderExtension.cs" />
<Compile Include="Internal\ModelUtilities.cs" />
<Compile Include="IScaffoldingModelFactory.cs" />
<Compile Include="Metadata\ColumnModel.cs" />
<Compile Include="Metadata\DatabaseModel.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class RelationalScaffoldingModelFactory : IScaffoldingModelFactory

protected virtual ILogger Logger { get; }
protected virtual IRelationalTypeMapper TypeMapper { get; }
protected virtual CandidateNamingService CandidateNamingService { get; }

private Dictionary<TableModel, CSharpUniqueNamer<ColumnModel>> _columnNamers;
private readonly TableModel _nullTable = new TableModel();
Expand All @@ -37,14 +38,17 @@ public class RelationalScaffoldingModelFactory : IScaffoldingModelFactory
public RelationalScaffoldingModelFactory(
[NotNull] ILoggerFactory loggerFactory,
[NotNull] IRelationalTypeMapper typeMapper,
[NotNull] IDatabaseModelFactory databaseModelFactory)
[NotNull] IDatabaseModelFactory databaseModelFactory,
[NotNull] CandidateNamingService candidateNamingService)
{
Check.NotNull(loggerFactory, nameof(loggerFactory));
Check.NotNull(typeMapper, nameof(typeMapper));
Check.NotNull(databaseModelFactory, nameof(databaseModelFactory));
Check.NotNull(candidateNamingService, nameof(candidateNamingService));

Logger = loggerFactory.CreateCommandsLogger();
TypeMapper = typeMapper;
CandidateNamingService = candidateNamingService;
_databaseModelFactory = databaseModelFactory;
}

Expand Down Expand Up @@ -80,7 +84,7 @@ protected virtual IModel CreateFromDatabaseModel([NotNull] DatabaseModel databas

var modelBuilder = new ModelBuilder(new ConventionSet());

_tableNamer = new CSharpUniqueNamer<TableModel>(t => CSharpUtilities.Pascalize(t.Name));
_tableNamer = new CSharpUniqueNamer<TableModel>(t => CandidateNamingService.GenerateCandidateIdentifier(t.Name));
_columnNamers = new Dictionary<TableModel, CSharpUniqueNamer<ColumnModel>>();

VisitDatabaseModel(modelBuilder, databaseModel);
Expand All @@ -107,7 +111,7 @@ protected virtual string GetPropertyName([NotNull] ColumnModel column)
{
_columnNamers.Add(table,
new CSharpUniqueNamer<ColumnModel>(
c => CSharpUtilities.Pascalize(c.Name), usedNames));
c => CandidateNamingService.GenerateCandidateIdentifier(c.Name), usedNames));
}

return _columnNamers[table].GetName(column);
Expand Down Expand Up @@ -569,10 +573,9 @@ protected virtual void AddNavigationProperties([NotNull] IMutableForeignKey fore
{
Check.NotNull(foreignKey, nameof(foreignKey));

var modelUtilities = new ModelUtilities();
var dependentEndExistingIdentifiers = ExistingIdentifiers(foreignKey.DeclaringEntityType);
var dependentEndNavigationPropertyCandidateName =
modelUtilities.GetDependentEndCandidateNavigationPropertyName(foreignKey);
CandidateNamingService.GetDependentEndCandidateNavigationPropertyName(foreignKey);
var dependentEndNavigationPropertyName =
CSharpUtilities.Instance.GenerateCSharpIdentifier(
dependentEndNavigationPropertyCandidateName,
Expand All @@ -587,7 +590,7 @@ protected virtual void AddNavigationProperties([NotNull] IMutableForeignKey fore
CultureInfo.CurrentCulture,
SelfReferencingPrincipalEndNavigationNamePattern,
dependentEndNavigationPropertyName)
: modelUtilities.GetPrincipalEndCandidateNavigationPropertyName(
: CandidateNamingService.GetPrincipalEndCandidateNavigationPropertyName(
foreignKey, dependentEndNavigationPropertyName);
var principalEndNavigationPropertyName =
CSharpUtilities.Instance.GenerateCSharpIdentifier(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ public class SqlServerScaffoldingModelFactory : RelationalScaffoldingModelFactor
public SqlServerScaffoldingModelFactory(
[NotNull] ILoggerFactory loggerFactory,
[NotNull] IRelationalTypeMapper typeMapper,
[NotNull] IDatabaseModelFactory databaseModelFactory)
: base(loggerFactory, typeMapper, databaseModelFactory)
[NotNull] IDatabaseModelFactory databaseModelFactory,
[NotNull] CandidateNamingService candidateNamingService)
: base(loggerFactory, typeMapper, databaseModelFactory, candidateNamingService)
{
}

Expand Down
Loading

0 comments on commit f2f90ca

Please sign in to comment.