entity framework - tools - Mejore los nombres de las propiedades de navegación cuando realice ingeniería inversa de una base de datos
visual studio 2017 community entity framework (4)
Estoy usando Entity Framework 5 con Visual Studio con Entity Framework Power Tools Beta 2 para realizar ingeniería inversa en 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:
- Use el nombre de la clave externa : por ejemplo, todo después del último guión bajo (
FK_Projects_TechnicalContact
->TechnicalContact
). Aunque probablemente esta sea la solución con más control, puede ser más difícil de integrar con las plantillas existentes. - Use el nombre de la propiedad correspondiente a la columna de la clave externa: elimine la
ID
del sufijo (TechnicalContactID
->TechnicalContact
) - Utilice la concatenación de nombre de propiedad y la solución existente : Ejemplo
TechnicalContactIDProjects
(colección) yTechnicalContactIDContact
Afortunadamente, es posible modificar las plantillas incluyéndolas en el proyecto .
Las modificaciones tendrían que hacerse a Entity.tt
y Mapping.tt
. Me resulta difícil debido a la falta de posibilidades intellisense y de depuración para hacer esos cambios.
Concatenar los nombres de las propiedades (tercera 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; }
}
Basándonos en la respuesta de BikeMrown, podemos agregar Intellisense a las propiedades usando RelationshipName
que se establece en MSSQL:
Edite model.tt en su VS Project, y cambie 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 empiezas a escribir un nombre de propiedad, obtienes una información sobre herramientas como esta:
Probablemente valga la pena señalar que si cambia su modelo de base de datos, las propiedades pueden encontrarse apuntando a diferentes campos de base de datos porque el EF genera nombres de propiedad de navegación en función de la precedencia alfabética de su nombre de campo de DB respectivo.
Encontré esta pregunta / respuesta muy útil. Sin embargo, no quería hacer tanto como la respuesta de Rikko. Solo necesitaba encontrar el nombre de columna involucrado en 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 #>
Hay algunas cosas que necesita cambiar dentro del archivo .tt. Elijo usar la tercera solución que sugirió, pero esto requiere formatearse como FK_CollectionName_RelationName. Los separé con ''_'' y usaré la última cuerda de la matriz. Uso el RelationName con la propiedad ToEndMember para crear un nombre de propiedad. FK_Projects_TechnicalContact dará como resultado
//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 puede preguntar. He agregado 2 funciones a la clase CodeStringGenerator
en el archivo T4. Uno que crea el propertyName que recibe una propiedad de navegación. y el otro genera el código para la propiedad que recibe una Propiedad de Navegación y el nombre de 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, aún necesita cambiar 2 partes. Necesita encontrar el lugar donde la parte del constructor y la parte de la propiedad de navegación están siendo creadas por la entidad. En la parte del constructor (alrededor de la línea 60) debe reemplazar el código existente llamando al método GetPropertyNameForNavigationProperty
y pasarlo al método de escape.
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty);
#>
this.<#=code.Escape(propName)#> = new HashSet<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>();
<#
Y en la parte de 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 se puede depurar la función GetPropertyNameForNavigationProperty
y jugar un poco con el nombre de la propiedad.
La respuesta seleccionada es asombrosa y me ayudó a ir en la dirección correcta. Pero mi gran problema es que tomó todas mis propiedades de navegación que ya estaban funcionando y les agregó el nombre del tipo de 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 profundicé en las adiciones propuestas al archivo .tt y las modifiqué un poco para eliminar nombres duplicados y limpiar un poco las cosas. Me imagino que debe haber alguien más que quiera lo mismo, así que pensé que publicaría mi resolución aquí.
Aquí está el código para actualizar dentro de la 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,
actualizar ambas ocurrencias de esto:
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty)
a esto
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty, entity.Name);