Proprietà shadow e indicizzatore

Le proprietà shadow sono proprietà non definite nella classe di entità .NET, ma definite per tale tipo di entità nel modello EF Core. Il valore e lo stato di queste proprietà vengono mantenuti esclusivamente in Rilevamento modifiche. Le proprietà shadow sono utili quando nel database sono presenti dati che non devono essere esposti nei tipi di entità mappati.

Le proprietà dell'indicizzatore sono proprietà del tipo di entità, supportate da un indicizzatore nella classe di entità .NET. È possibile accedervi usando l'indicizzatore nelle istanze della classe .NET. Consente inoltre di aggiungere proprietà aggiuntive al tipo di entità senza modificare la classe CLR.

Proprietà dell'ombreggiatura della chiave esterna

Le proprietà shadow vengono spesso usate per le proprietà di chiave esterna, in cui vengono aggiunte al modello per convenzione quando non viene trovata alcuna proprietà di chiave esterna per convenzione o configurata in modo esplicito. La relazione è rappresentata dalle proprietà di navigazione, ma nel database viene applicata da un vincolo di chiave esterna e il valore per la colonna chiave esterna viene archiviato nella proprietà shadow corrispondente.

La proprietà verrà denominata <navigation property name><principal key property name> (la navigazione sull'entità dipendente, che punta all'entità principale, viene usata per la denominazione). Se il nome della proprietà della chiave principale inizia con il nome della proprietà di navigazione, il nome sarà <principal key property name>solo . Se non esiste alcuna proprietà di navigazione nell'entità dipendente, il nome del tipo di entità concatenato con il nome della proprietà primaria o alternativa viene usato al suo posto <principal type name><principal key property name>.

Ad esempio, il listato di codice seguente comporterà l'introduzione di una BlogId proprietà shadow all'entità Post :

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    // Since there is no CLR property which holds the foreign
    // key for this relationship, a shadow property is created.
    public Blog Blog { get; set; }
}

Configurazione delle proprietà shadow

È possibile usare l'API Fluent per configurare le proprietà shadow. Dopo aver chiamato l'overload della stringa di Property<TProperty>(String), è possibile concatenare qualsiasi chiamata di configurazione per altre proprietà. Nell'esempio seguente, poiché Blog non ha alcuna proprietà CLR denominata LastUpdated, viene creata una proprietà shadow:

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property<DateTime>("LastUpdated");
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

Se il nome fornito al metodo corrisponde al Property nome di una proprietà esistente (una proprietà shadow o una definita nella classe di entità), il codice configurerà tale proprietà esistente anziché introdurre una nuova proprietà shadow.

Accesso alle proprietà shadow

I valori delle proprietà Shadow possono essere ottenuti e modificati tramite l'API ChangeTracker :

context.Entry(myBlog).Property("LastUpdated").CurrentValue = DateTime.Now;

È possibile fare riferimento alle proprietà shadow nelle query LINQ tramite il EF.Property metodo statico:

var blogs = context.Blogs
    .OrderBy(b => EF.Property<DateTime>(b, "LastUpdated"));

Non è possibile accedere alle proprietà shadow dopo una query senza rilevamento perché le entità restituite non vengono rilevate dal rilevamento delle modifiche.

Configurazione delle proprietà dell'indicizzatore

È possibile usare l'API Fluent per configurare le proprietà dell'indicizzatore. Dopo aver chiamato il metodo IndexerProperty, è possibile concatenare qualsiasi chiamata di configurazione per altre proprietà. Nell'esempio seguente è Blog definito un indicizzatore che verrà usato per creare una proprietà dell'indicizzatore.

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().IndexerProperty<DateTime>("LastUpdated");
    }
}

public class Blog
{
    private readonly Dictionary<string, object> _data = new Dictionary<string, object>();
    public int BlogId { get; set; }

    public object this[string key]
    {
        get => _data[key];
        set => _data[key] = value;
    }
}

Se il nome fornito al IndexerProperty metodo corrisponde al nome di una proprietà dell'indicizzatore esistente, il codice configurerà tale proprietà esistente. Se il tipo di entità ha una proprietà, supportata da una proprietà nella classe di entità, viene generata un'eccezione perché le proprietà dell'indicizzatore devono essere accessibili solo tramite l'indicizzatore.

È possibile fare riferimento alle proprietà dell'indicizzatore nelle query LINQ tramite il EF.Property metodo statico come illustrato in precedenza o usando la proprietà dell'indicizzatore CLR.

Tipi di entità contenitore delle proprietà

I tipi di entità che contengono solo le proprietà dell'indicizzatore sono noti come tipi di entità contenitore delle proprietà. Questi tipi di entità non hanno proprietà shadow e EF crea invece le proprietà dell'indicizzatore. Attualmente è supportato solo Dictionary<string, object> come tipo di entità contenitore delle proprietà. Deve essere configurato come tipo di entità di tipo condiviso con un nome univoco e la proprietà corrispondente DbSet deve essere implementata usando una Set chiamata.

internal class MyContext : DbContext
{
    public DbSet<Dictionary<string, object>> Blogs => Set<Dictionary<string, object>>("Blog");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.SharedTypeEntity<Dictionary<string, object>>(
            "Blog", bb =>
            {
                bb.Property<int>("BlogId");
                bb.Property<string>("Url");
                bb.Property<DateTime>("LastUpdated");
            });
    }
}

I tipi di entità contenitore delle proprietà possono essere usati ovunque venga usato un tipo di entità normale, incluso come tipo di entità di proprietà. Tuttavia, presentano alcune limitazioni: