web-dev-qa-db-fra.com

Améliorer les noms de propriété de navigation lors du reverse engineering d'une base de données

J'utilise Entity Framework 5 avec Visual Studio avec la version bêta 2 de Power Tools pour Entity Framework afin de procéder à l'ingénierie inverse de bases de données de taille moyenne (environ 100 tables).

Malheureusement, les propriétés navigation n'ont pas de noms significatifs. Par exemple, s'il y a deux tables:

CREATE TABLE Contacts (
    ContactID INT IDENTITY (1, 1) NOT NULL,
    ...
    CONSTRAINT PK_Contacts PRIMARY KEY CLUSTERED (ContactID ASC)
}

CREATE TABLE Projects (
    ProjectID INT IDENTITY (1, 1) NOT NULL,
    TechnicalContactID INT NOT NULL,
    SalesContactID INT NOT NULL,
    ...
    CONSTRAINT PK_Projects PRIMARY KEY CLUSTERED (ProjectID ASC),
    CONSTRAINT FK_Projects_TechnicalContact FOREIGN KEY (TechnicalContactID)
        REFERENCES Contacts (ContactID),
    CONSTRAINT FK_Projects_SalesContact FOREIGN KEY (SalesContactID)
        REFERENCES Contacts (ContactID),
    ...
}

Cela va générer des classes comme ceci:

public class Contact
{
     public Contact()
     {
          this.Projects = new List<Project>();
          this.Projects1 = new List<Project>();
     }
     public int ContactID { get; set; }
     // ...
     public virtual ICollection<Project> Projects { get; set; }
     public virtual ICollection<Project> Projects1 { get; set; }
}

public class Project
{
     public Project()
     {

     }
     public int ProjectID { get; set; }
     public int TechnicalContactID { get; set; }
     public int SalesContactID { get; set; }
     // ...
     public virtual Contact Contact { get; set; }
     public virtual Contact Contact1 { get; set; }
}

Je vois plusieurs variantes qui seraient toutes meilleures que celle-ci:

  • Utilisez le nom de la clé étrangère: Par exemple, tout ce qui suit le dernier trait de soulignement (FK_Projects_TechnicalContact -> TechnicalContact). Bien que ce soit probablement la solution avec le plus de contrôle, cela peut être plus difficile à intégrer avec les modèles existants.
  • Utilisez le nom de propriété correspondant à la colonne de clé étrangère: Supprimez le suffixe ID (TechnicalContactID -> TechnicalContact)
  • Utilisez la concaténation du nom de la propriété et la solution existante: Exemple TechnicalContactIDProjects (collection) et TechnicalContactIDContact

Heureusement, il est possible de modifier les modèles en les incluant dans le projet

Les modifications devront être apportées à Entity.tt et Mapping.tt. Je trouve cela difficile en raison du manque d'intellisense et des possibilités de débogage pour effectuer ces changements.


Concaténation des noms de propriété} (troisième dans la liste ci-dessus) est probablement la solution la plus simple à mettre en œuvre.

Comment modifier la création des propriétés de navigation dans Entity.tt et Mapping.tt pour obtenir le résultat suivant:

public class Contact
{
     public Contact()
     {
          this.TechnicalContactIDProjects = new List<Project>();
          this.SalesContactIDProjects = new List<Project>();
     }
     public int ContactID { get; set; }
     // ...
     public virtual ICollection<Project> TechnicalContactIDProjects { get; set; }
     public virtual ICollection<Project> SalesContactIDProjects { get; set; }
}

public class Project
{
     public Project()
     {

     }
     public int ProjectID { get; set; }
     public int TechnicalContactID { get; set; }
     public int SalesContactID { get; set; }
     // ...
     public virtual Contact TechnicalContactIDContact { get; set; }
     public virtual Contact SalesContactIDContact { get; set; }
}
51
marapet

Il y a quelques choses que vous devez changer dans le fichier .tt. J'ai choisi d'utiliser la troisième solution que vous avez suggérée, mais cela nécessite d'être formaté comme FK_CollectionName_RelationName. Je les sépare avec '_' et utilise la dernière chaîne du tableau . J'utilise RelationName avec la propriété ToEndMember pour créer un nom de propriété. FK_Projects_TechnicalContact entraînera 

//Plularized because of EF. 
public virtual Contacts TechnicalContactContacts { get; set; }

et vos projets seront comme ça. 

public virtual ICollection<Projects> SalesContactProjects { get;  set; }
public virtual ICollection<Projects> TechnicalContactProjects { get;  set; }

Maintenant, le code que vous pouvez demander. J'ai ajouté 2 fonctions à la classe CodeStringGenerator dans le fichier T4. Celui qui construit le propertyName recevant un NavigationProperty. et l’autre générant le code de la propriété recevant un objet NavigationProperty et le nom de la propriété. 

//CodeStringGenerator class
public string GetPropertyNameForNavigationProperty(NavigationProperty navigationProperty)
{
    var ForeignKeyName = navigationProperty.RelationshipType.Name.Split('_');
    var propertyName = ForeignKeyName[ForeignKeyName.Length-1] + navigationProperty.ToEndMember.Name;
    return propertyName;
}

public string NavigationProperty(NavigationProperty navigationProperty, string name)
{
    var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1} {2} {{ {3}get; {4}set; }}",
        AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)),
        navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
        name,
        _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
        _code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));
}

Si vous placez le code ci-dessus dans la classe, vous devez toujours modifier 2 parties. Vous devez trouver l'emplacement où la partie constructeur et la partie propriété de navigation sont en cours de construction de l'entité. Dans la partie constructeur (autour de la ligne 60), vous devez remplacer le code existant en appelant la méthode GetPropertyNameForNavigationProperty et en le passant à la méthode d'échappement. 

      var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty);
#>
      this.<#=code.Escape(propName)#> = new HashSet<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>();
<#

Et dans la partie NavigationProperties (autour de la ligne 100), vous devez également remplacer le code par le suivant.

    var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty);
#>
    <#=codeStringGenerator.NavigationProperty(navigationProperty, propName)#>
<#

J'espère que cela vous aidera et que vous pourrez toujours déboguer la fonction GetPropertyNameForNavigationProperty et jouer un peu avec l'attribution de nom à la propriété.

47
Rik van den Berg

En nous appuyant sur la réponse de BikeMrown, nous pouvons ajouter Intellisense aux propriétés à l'aide de la variable RelationshipName définie dans MSSQL:

 MSSQL relationships

Editez model.tt dans votre projet VS et changez ceci:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
<#
            }
#>
    <#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
        }
    }

pour ça:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
<#
            }
#>
    /// <summary>
    /// RelationshipName: <#=code.Escape(navigationProperty.RelationshipType.Name)#>
    /// </summary>
    <#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
        }
    }

Maintenant, lorsque vous commencez à taper un nom de propriété, vous obtenez une info-bulle comme celle-ci:  Intellisense tooltip

Il est probablement intéressant de noter que si vous modifiez votre modèle de base de données, les propriétés risquent de se retrouver dirigées vers différents champs de base de données car l'EF génère des noms de propriété de navigation basés sur la priorité alphabétique de leur nom de champ de base de données respectif!

J'ai trouvé cette question/réponse très utile. Cependant, je ne voulais pas faire autant que la réponse de Rikko. J'avais juste besoin de trouver le nom de colonne impliqué dans NavigationProperty et je ne voyais pas comment l'obtenir dans l'un des exemples (du moins sans un edmx à extraire).

<#
  var association = (AssociationType)navProperty.RelationshipType;
#>  //  <#= association.ReferentialConstraints.Single().ToProperties.Single().Name #>
3
BikeMrown

La réponse choisie est géniale et m'a permis d'aller dans la bonne direction à coup sûr. Mais mon gros problème avec cela est qu'il a pris toutes mes propriétés de navigation déjà en fonctionnement et leur a ajouté le nom du type de base. 

public virtual Need UnitNeed { get; set;}
public virtual ShiftEntered UnitShiftEntered {get; set;}`

J'ai donc exploré les ajouts proposés au fichier .tt et les ai modifiés un peu pour supprimer les noms de types en double et nettoyer un peu les choses. Je pense qu'il doit y avoir quelqu'un d'autre qui voudrait la même chose, alors j'ai décidé de poster ma résolution ici.

Voici le code à mettre à jour dans le public class CodeStringGenerator

public string GetPropertyNameForNavigationProperty(NavigationProperty navigationProperty, string entityname = "")
{
    var ForeignKeyName = navigationProperty.RelationshipType.Name.Split('_');
    var propertyName = "";

    if (ForeignKeyName[ForeignKeyName.Length-1] != entityname){
        var prepender = (ForeignKeyName[ForeignKeyName.Length-1].EndsWith(entityname)) ? ReplaceLastOccurrence(ForeignKeyName[ForeignKeyName.Length-1], entityname, "") :  ForeignKeyName[ForeignKeyName.Length-1];
        propertyName = prepender + navigationProperty.ToEndMember.Name;
    }
    else {
        propertyName = navigationProperty.ToEndMember.Name;
    }

    return propertyName;
}

public string NavigationProperty(NavigationProperty navigationProperty, string name)
{
    var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());

    var truname = name;

    if(navigationProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many){
        if(name.Split(endType.ToArray<char>()).Length > 1){
            truname = ReplaceLastOccurrence(name, endType, "");
        }
    }

    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1} {2} {{ {3}get; {4}set; }}",
        AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)),
        navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
        truname,
        _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
        _code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));
}

public static string ReplaceLastOccurrence(string Source, string Find, string Replace)
{
        int place = Source.LastIndexOf(Find);

        if(place == -1)
           return Source;

        string result = Source.Remove(place, Find.Length).Insert(place, Replace);
        return result;
}

et voici le code à mettre à jour dans la génération du modèle,

mettre à jour les deux occurrences de ceci:

var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty)

pour ça

var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty, entity.Name);
0
Dylan Hayes