Conventions basées sur des modèles

Remarque

EF6 et versions ultérieures uniquement : Les fonctionnalités, les API, etc. décrites dans cette page ont été introduites dans Entity Framework 6. Si vous utilisez une version antérieure, certaines ou toutes les informations ne s’appliquent pas.

Les conventions basées sur des modèles sont une méthode avancée de configuration de modèle basée sur des conventions. Pour la plupart des scénarios, l’API Custom Code First Convention sur DbModelBuilder doit être utilisée. Une compréhension de l’API DbModelBuilder pour les conventions est recommandée avant d’utiliser des conventions basées sur des modèles.

Les conventions basées sur des modèles permettent de créer des conventions qui affectent les propriétés et les tables qui ne sont pas configurables par le biais de conventions standard. Par exemple, il s’agit de colonnes de discrimination dans la table par modèle de hiérarchie et les colonnes Association indépendante.

Création d’une convention

La première étape de la création d’une convention basée sur un modèle consiste à choisir quand, dans le pipeline, la convention doit être appliquée au modèle. Il existe deux types de conventions de modèle, Conceptual (C-Space) et Store (S-Space). Une convention C-Space est appliquée au modèle généré par l’application, tandis qu’une convention S-Space est appliquée à la version du modèle qui représente la base de données et contrôle des éléments tels que la façon dont les colonnes générées automatiquement sont nommées.

Une convention de modèle est une classe qui s’étend de IConceptualModelConvention ou IStoreModelConvention. Ces interfaces acceptent tous deux un type générique qui peut être de type MetadataItem qui est utilisé pour filtrer le type de données auquel la convention s’applique.

Ajout d’une convention

Les conventions de modèle sont ajoutées de la même façon que les classes de conventions régulières. Dans la méthode OnModelCreating, ajoutez la convention à la liste des conventions d’un modèle.

using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;

public class BlogContext : DbContext  
{  
    public DbSet<Post> Posts { get; set; }  
    public DbSet<Comment> Comments { get; set; }  

    protected override void OnModelCreating(DbModelBuilder modelBuilder)  
    {  
        modelBuilder.Conventions.Add<MyModelBasedConvention>();  
    }  
}

Une convention peut également être ajoutée par rapport à une autre convention à l’aide des méthodes Conventions.AddBefore<> ou Conventions.AddAfter<>. Pour plus d’informations sur les conventions que Entity Framework applique, consultez la section notes.

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.AddAfter<IdKeyDiscoveryConvention>(new MyModelBasedConvention());
}

Exemple : Convention de modèle de discrimination

Le changement de nom des colonnes générées par EF est un exemple de quelque chose que vous ne pouvez pas faire avec les autres API de conventions. Il s’agit d’une situation où l’utilisation des conventions de modèle est votre seule option.

Un exemple d’utilisation d’une convention basée sur un modèle pour configurer des colonnes générées consiste à personnaliser la façon dont les colonnes de discrimination sont nommées. Voici un exemple de convention simple basée sur un modèle qui renomme chaque colonne du modèle nommé « Discriminator » en « EntityType ». Cela inclut les colonnes que le développeur appelle « simplement Discriminator ». Étant donné que la colonne « Discriminator » est une colonne générée, elle doit s’exécuter dans S-Space.

using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;

class DiscriminatorRenamingConvention : IStoreModelConvention<EdmProperty>  
{  
    public void Apply(EdmProperty property, DbModel model)  
    {            
        if (property.Name == "Discriminator")  
        {  
            property.Name = "EntityType";  
        }  
    }  
}

Exemple : Convention générale de changement de nom d’IA

Un autre exemple plus compliqué de conventions basées sur des modèles en action consiste à configurer la façon dont les associations indépendantes sont nommées. Il s’agit d’une situation où les conventions de modèle s’appliquent, car les IA sont générées par EF et ne sont pas présentes dans le modèle auquel l’API DbModelBuilder peut accéder.

Quand EF génère une IA, elle crée une colonne nommée EntityType_KeyName. Par exemple, pour une association nommée Customer avec une colonne clé nommée CustomerId, elle génère une colonne nommée Customer_CustomerId. La convention suivante supprime le caractère « _ » du nom de colonne généré pour l’IA.

using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;

// Provides a convention for fixing the independent association (IA) foreign key column names.  
public class ForeignKeyNamingConvention : IStoreModelConvention<AssociationType>
{

    public void Apply(AssociationType association, DbModel model)
    {
        // Identify ForeignKey properties (including IAs)  
        if (association.IsForeignKey)
        {
            // rename FK columns  
            var constraint = association.Constraint;
            if (DoPropertiesHaveDefaultNames(constraint.FromProperties, constraint.ToRole.Name, constraint.ToProperties))
            {
                NormalizeForeignKeyProperties(constraint.FromProperties);
            }
            if (DoPropertiesHaveDefaultNames(constraint.ToProperties, constraint.FromRole.Name, constraint.FromProperties))
            {
                NormalizeForeignKeyProperties(constraint.ToProperties);
            }
        }
    }

    private bool DoPropertiesHaveDefaultNames(ReadOnlyMetadataCollection<EdmProperty> properties, string roleName, ReadOnlyMetadataCollection<EdmProperty> otherEndProperties)
    {
        if (properties.Count != otherEndProperties.Count)
        {
            return false;
        }

        for (int i = 0; i < properties.Count; ++i)
        {
            if (!properties[i].Name.EndsWith("_" + otherEndProperties[i].Name))
            {
                return false;
            }
        }
        return true;
    }

    private void NormalizeForeignKeyProperties(ReadOnlyMetadataCollection<EdmProperty> properties)
    {
        for (int i = 0; i < properties.Count; ++i)
        {
            int underscoreIndex = properties[i].Name.IndexOf('_');
            if (underscoreIndex > 0)
            {
                properties[i].Name = properties[i].Name.Remove(underscoreIndex, 1);
            }                 
        }
    }
}

Étendre des conventions existantes

Si vous devez écrire une convention similaire à l’une des conventions que Entity Framework s’applique déjà à votre modèle, vous pouvez toujours étendre cette convention pour éviter de devoir la réécrire à partir de zéro. Par exemple, remplacez la convention de correspondance d’ID existante par une convention personnalisée. Un avantage supplémentaire pour remplacer la convention de clé est que la méthode substituée est appelée uniquement si aucune clé n’est déjà détectée ou configurée explicitement. Une liste de conventions utilisées par Entity Framework est disponible ici : http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx.

using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;  

// Convention to detect primary key properties.
// Recognized naming patterns in order of precedence are:
// 1. 'Key'
// 2. [type name]Key
// Primary key detection is case insensitive.
public class CustomKeyDiscoveryConvention : KeyDiscoveryConvention
{
    private const string Id = "Key";

    protected override IEnumerable<EdmProperty> MatchKeyProperty(
        EntityType entityType, IEnumerable<EdmProperty> primitiveProperties)
    {
        Debug.Assert(entityType != null);
        Debug.Assert(primitiveProperties != null);

        var matches = primitiveProperties
            .Where(p => Id.Equals(p.Name, StringComparison.OrdinalIgnoreCase));

        if (!matches.Any())
       {
            matches = primitiveProperties
                .Where(p => (entityType.Name + Id).Equals(p.Name, StringComparison.OrdinalIgnoreCase));
        }

        // If the number of matches is more than one, then multiple properties matched differing only by
        // case--for example, "Key" and "key".  
        if (matches.Count() > 1)
        {
            throw new InvalidOperationException("Multiple properties match the key convention");
        }

        return matches;
    }
}

Nous devons ensuite ajouter notre nouvelle convention avant la convention clé existante. Après avoir ajouté CustomKeyDiscoveryConvention, nous pouvons supprimer IdKeyDiscoveryConvention. Si nous n’avons pas supprimé le idKeyDiscoveryConvention existant, cette convention est toujours prioritaire sur la convention de découverte d’ID, car elle est exécutée en premier, mais dans le cas où aucune propriété « clé » n’est trouvée, la convention « ID » s’exécutera. Nous voyons ce comportement, car chaque convention voit le modèle comme mis à jour par la convention précédente (plutôt que d’opérer sur elle indépendamment et tous combinés ensemble) afin que si, par exemple, une convention précédente a mis à jour un nom de colonne pour qu’il corresponde à quelque chose d’intéressant à votre convention personnalisée (quand avant que le nom n’ait pas d’intérêt), il s’applique à cette colonne.

public class BlogContext : DbContext
{
    public DbSet<Post> Posts { get; set; }
    public DbSet<Comment> Comments { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.AddBefore<IdKeyDiscoveryConvention>(new CustomKeyDiscoveryConvention());
        modelBuilder.Conventions.Remove<IdKeyDiscoveryConvention>();
    }
}

Notes

Une liste de conventions actuellement appliquées par Entity Framework est disponible dans la documentation MSDN ici : http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx. Cette liste est extraite directement de notre code source. Le code source pour Entity Framework 6 est disponible sur GitHub et la plupart des conventions utilisées par Entity Framework sont de bons points de départ pour les conventions personnalisées basées sur des modèles.