Ulepszanie nazw właściwości nawigacyjnych podczas inżynierii odwrotnej bazy danych

Używam Entity Framework 5 z Visual Studio z Entity Framework Power Tools Beta 2 do inżynierii wstecznej średniej wielkości baz danych (~100 tabel).

Niestety, właściwości nawigacji nie mają znaczących nazw . Na przykład, jeśli istnieją dwie tabele:

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

To wygeneruje takie klasy:

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

Widzę kilka wariantów, które byłyby lepsze od tego:

  • użyj nazwy klucza obcego : Na przykład wszystko po ostatnim podkreśleniu (FK_Projects_TechnicalContact --> TechnicalContact). Chociaż prawdopodobnie byłoby to rozwiązanie z największą kontrolą, może to być trudniejsze do zintegrowania z istniejącymi szablonami.
  • użyj właściwości nazwa właściwości odpowiadającej kolumnie klucza obcego: Strip off the sufiks ID (TechnicalContactID --> TechnicalContact)
  • użyj konkatenacji nazwy własności i istniejącego rozwiązania : przykład TechnicalContactIDProjects (zbiór) i TechnicalContactIDContact

Na szczęście, istnieje możliwość modyfikacji szablonów poprzez włączenie ich do projektu .

Należy dokonać modyfikacji Entity.tt i Mapping.tt. Uważam, że jest to trudne ze względu na brak możliwości intellisense i debugowania, aby wprowadzić te zmiany.


konkatenacja nazw właściwości (trzecia na powyższej liście) jest prawdopodobnie najprostszym rozwiązaniem do implementacji.

Jak zmienić tworzenie właściwości nawigacyjnych w Entity.tt i Mapping.tt aby osiągnąć następujący wynik:

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

Jest kilka rzeczy, które musisz zmienić w pliku. tt. Wybieram użycie trzeciego rozwiązania, które zasugerowałeś, ale wymaga to sformatowania jak FK_CollectionName_RelationName. Dzielę je przez ' _ ' i używam ostatniego ciągu w tablicy. Używam RelationName z właściwością toendmember, aby utworzyć nazwę właściwości. FK_Projects_TechnicalContact spowoduje

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

A twoje projekty będą takie.

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

Teraz Kod, o który możesz zapytać. Ive dodał 2 funkcje do Klasa CodeStringGenerator w pliku T4. Taki, który buduje nazwę właściwości, pobierając NavigationProperty. a drugi generuje kod dla właściwości otrzymującej NavigationProperty i nazwę dla właściwości.

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

Jeśli umieścisz powyższy kod w klasie, nadal musisz zmienić 2 części. Musisz znaleźć miejsce, w którym budowana jest część konstruktora i część właściwości nawigacyjnej jednostki. W części konstruktora (wokół linii 60) należy wymienić istniejący kod przez wywołanie metody GetPropertyNameForNavigationProperty i przekazanie jej do metody escape.

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

I w części NavigationProperties (wokół linii 100) należy również zastąpić kod poniższym.

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

Mam nadzieję, że to pomoże i zawsze możesz debugować funkcję GetPropertyNameForNavigationProperty i pobawić się trochę z nazewnictwem właściwości.

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-25 08:16:20

Bazując na odpowiedzi BikeMrown, możemy dodać Intellisense do właściwości używając RelationshipName, który jest ustawiony w MSSQL:

Relacje MSSQL

Edytuj model.tt w Twoim projekcie VS i zmień to:

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

Do tego:

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

Teraz, gdy zaczniesz wpisywać nazwę właściwości, otrzymasz podpowiedź w następujący sposób: IntelliSense tooltip

Prawdopodobnie warto zauważyć, że jeśli zmienisz model DB, właściwości mogą się wskazywać na różne pola DB, ponieważ EF generuje nazwy właściwości nawigacyjnych na podstawie alfabetycznego pierwszeństwa ich nazw pól DB!

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-22 16:08:15

To pytanie/odpowiedź okazała się bardzo pomocna. Jednak nie chciałem robić tyle, co odpowiedź Rikko. Po prostu musiałem znaleźć nazwę kolumny zaangażowanej w NavigationProperty i nie widziałem, jak uzyskać to w żadnej z próbek(przynajmniej nie bez edmx do wyciągnięcia).

<#
  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/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-21 15:58:49

Wybrana odpowiedź jest niesamowita i sprawiła, że na pewno zmierzam w dobrym kierunku. Ale mój duży problem z tym polega na tym, że wziął wszystkie moje już działające właściwości nawigacji i dodał do nich nazwę typu podstawowego, więc skończysz z takimi rzeczami jak poniżej.

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

Więc zajrzałem do proponowanych dodatków do pliku. tt i zmodyfikowałem je nieco, aby usunąć nazwy duplikatów typów i trochę posprzątać. Myślę, że musi być ktoś inny, kto chciałby to samo, więc postanowiłem zamieścić tutaj swoje postanowienie.

Oto kod do aktualizacji w ramach 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;
}

A oto kod do aktualizacji w ramach generacji modelu,

Update both occurences of this:

var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty)

Do tego

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/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-25 15:45:32