Mejorar los nombres de propiedades de navegación al realizar ingeniería inversa en una base de datos


Estoy usando Entity Framework 5 con Visual Studio con Entity Framework Power Tools Beta 2 para realizar ingeniería inversa de bases de datos de tamaño moderado (~100 tablas).

Desafortunadamente, las propiedades de navegación no tienen nombres significativos. Por ejemplo, si hay dos tablas:

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),
    ...
}

Esto generará clases como esta:

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; }
}

Veo varias variantes que serían todas mejores que esto:{[15]]}

  • Utilice el nombre de la clave externa: Por ejemplo, todo después del último subrayado (FK_Projects_TechnicalContact --> TechnicalContact). Aunque esta probablemente sería la solución con más control, esto puede ser más difícil de integrar con las plantillas existentes.
  • Utilice el nombre de la propiedad correspondiente a la columna de clave foránea: Elimine el sufijo ID (TechnicalContactID --> TechnicalContact)
  • Use la concatenación del nombre de la propiedad y la solución existente : Ejemplo TechnicalContactIDProjects (colección) y TechnicalContactIDContact

Afortunadamente, es posible modificar las plantillas incluidas en el proyecto.

Las modificaciones tendrían que hacerse a Entity.tt y Mapping.tt. Me resulta difícil debido a la falta de intellisense y las posibilidades de depuración para hacer esos cambios.


Concatenar nombres de propiedad (tercero en la lista anterior) es probablemente la solución más fácil de implementar.

Cómo cambiar la creación de propiedades de navegación en Entity.tt y Mapping.tt para lograr el siguiente resultado :

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; }
}
Author: marapet, 2012-10-17

4 answers

Hay algunas cosas que necesita cambiar dentro del archivo .tt. Elijo usar la tercera solución que sugirió, pero esto requiere que se formatee como FK_CollectionName_RelationName. Los divido con ' _ ' y uso la última cadena de la matriz. Utilizo el RelationName con la propiedad ToEndMember para crear un nombre de propiedad. FK_Projects_TechnicalContact resultará en

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

Y tus proyectos serán así.

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

Ahora el código que usted puede pedir. He añadido 2 funciones a la clase CodeStringGenerator en el archivo T4. Uno que construye el propertyName recibiendo una NavigationProperty. y el otro generando el código para la propiedad recibiendo una NavigationProperty y el nombre para la propiedad.

//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 coloca el código anterior en la clase, todavía necesita cambiar 2 partes. Necesita encontrar el lugar donde la parte del constructor y la parte de la propiedad de navegación se están construyendo de la entidad. En la parte del constructor (alrededor de la línea 60) necesita reemplazar código existente llamando al método GetPropertyNameForNavigationProperty y pasando esto al método escape.

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

Y en la parte NavigationProperties (alrededor de la línea 100) también necesita reemplazar el código con lo siguiente.

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

Espero que esto ayude y siempre puede depurar la función GetPropertyNameForNavigationProperty y jugar un poco con el nombre de la propiedad.

 47
Author: Rik van den Berg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-25 08:16:20

Basándonos en la respuesta de BikeMrown, podemos agregar Intellisense a las propiedades usando el RelationshipName que se establece en MSSQL:

Relaciones MSSQL

Editar model.tt en tu Proyecto VS, y cambia esto:

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

A esto:

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

Ahora, cuando comienzas a escribir un nombre de propiedad, obtienes una descripción como esta: Descripción de Intellisense

Probablemente vale la pena señalar que si cambia su modelo de BD, las propiedades pueden encontrarse apuntando a diferentes campos de BD porque el EF genera nombres de propiedad de navegación basados en la prioridad alfabética de sus respectivos nombres de campo de base de datos!

 5
Author: 3-14159265358979323846264,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-22 16:08:15

Encontró esta pregunta/respuesta muy útil. Sin embargo, no quería hacer tanto como la respuesta de Rikko. Solo necesitaba encontrar el nombre de la columna involucrada en la NavigationProperty y no estaba viendo cómo obtenerlo en ninguna de las muestras (al menos no sin un edmx para extraer).

<#
  var association = (AssociationType)navProperty.RelationshipType;
#>  //  <#= association.ReferentialConstraints.Single().ToProperties.Single().Name #>
 3
Author: BikeMrown,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-21 15:58:49

La respuesta seleccionada es impresionante y me hizo ir en la dirección correcta con seguridad. Pero mi gran problema con él es que tomó todas mis propiedades de navegación ya en funcionamiento y les agregó el nombre del tipo base, por lo que terminaría con cosas como las siguientes.

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

Así que investigué las adiciones propuestas al archivo .tt y las modificé un poco para eliminar los nombres de tipos duplicados y limpiar un poco las cosas. Me imagino que tiene que haber alguien más por ahí que querría el lo mismo, así que pensé en publicar mi resolución aquí.

Aquí está el código para actualizar dentro del 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;
}

Y aquí está el código para actualizar dentro de la generación del modelo,

Actualice ambas ocurrencias de esto:

var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty)

A esto

var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty, entity.Name);
 0
Author: Dylan Hayes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-25 15:45:32