Propriedades de sombra e indexador

As propriedades de sombra são propriedades que não são definidas em sua classe de entidade .NET, mas são definidas para esse tipo de entidade no modelo EF Core. O valor e o estado dessas propriedades são mantidos exclusivamente no Change Tracker. As propriedades de sombra são úteis quando há dados no banco de dados que não devem ser expostos nos tipos de entidade mapeados.

As propriedades do indexador são propriedades de tipo de entidade, que são apoiadas por um indexador na classe de entidade .NET. Elas podem ser acessadas usando o indexador nas instâncias de classe .NET. Ele também permite que você acrescente propriedades adicionais ao tipo de entidade sem alterar a classe CLR.

Propriedades de sombra de chave estrangeira

As propriedades de sombra geralmente são usadas para propriedades de chave estrangeira, em que são adicionadas ao modelo por convenção quando nenhuma propriedade de chave estrangeira foi encontrada por convenção ou configurada explicitamente. A relação é representada por propriedades de navegação, mas no banco de dados ela é imposta por uma restrição de chave estrangeira e o valor da coluna de chave estrangeira é armazenado na propriedade de sombra correspondente.

A propriedade será nomeada <navigation property name><principal key property name> (a navegação na entidade dependente, que aponta para a entidade principal, é usada para a nomenclatura). Se o nome da propriedade principal da chave começar com o nome da propriedade de navegação, o nome será apenas <principal key property name>. Se não houver uma propriedade de navegação na entidade dependente, o nome do tipo de entidade de segurança concatenado com o nome da propriedade de chave primária ou alternativa será usado no lugar <principal type name><principal key property name>.

Por exemplo, a listagem de código a seguir resultará em uma propriedade de sombra BlogId sendo introduzida à entidade 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; }
}

Configuração de propriedades de sombra

Você pode usar a API fluente para configurar propriedades de sombra. Depois de chamar a sobrecarga de cadeia de caracteres de Property<TProperty>(String), você poderá encadear qualquer uma das chamadas de configuração que faria para outras propriedades. No exemplo a seguir, como Blog não tem nenhuma propriedade CLR chamada LastUpdated, é criada uma propriedade de sombra:

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 o nome fornecido ao método Property corresponder ao nome de uma propriedade existente (uma propriedade de sombra ou uma definida na classe de entidade), o código configurará essa propriedade existente em vez de introduzir uma nova propriedade de sombra.

Acesso a propriedades de sombra

Os valores da propriedade de sombra podem ser obtidos e alterados por meio da API ChangeTracker:

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

As propriedades de sombra podem ser referenciadas em consultas LINQ por meio do método estático EF.Property:

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

As propriedades de sombra não podem ser acessadas após uma consulta sem rastreamento, pois as entidades retornadas não são rastreadas pelo rastreador de alterações.

Configuração de propriedades do indexador

Você pode usar a API fluente para configurar propriedades do indexador. Depois de chamar o método IndexerProperty, você pode encadear qualquer uma das chamadas de configuração que faria para outras propriedades. No exemplo a seguir, Blog tem um indexador definido e será usado para criar uma propriedade do indexador.

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 o nome fornecido ao método IndexerProperty corresponder ao nome de uma propriedade de indexador existente, o código configurará essa propriedade existente. Se o tipo de entidade tiver uma propriedade, que é apoiada por uma propriedade na classe de entidade, uma exceção será gerada, pois as propriedades do indexador só devem ser acessadas por meio do indexador.

As propriedades do indexador podem ser referenciadas em consultas LINQ por meio do método estático EF.Property, conforme mostrado acima ou usando a propriedade do indexador CLR.

Tipos de entidade do recipiente de propriedades

Tipos de entidade que contêm apenas propriedades do indexador são conhecidos como tipos de entidade do recipiente de propriedades. Esses tipos de entidade não têm propriedades shadow e, em vez disso, o EF cria propriedades de indexador. Atualmente, há suporte apenas para Dictionary<string, object> como um tipo de entidade do recipiente de propriedades. Ele deve ser configurado como um tipo de entidade de tipo compartilhado com um nome exclusivo e a propriedade DbSet correspondente deve ser implementada usando uma chamada Set.

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

Os tipos de entidade do recipiente de propriedades podem ser usados sempre que um tipo de entidade normal for usado, incluindo como um tipo de entidade de propriedade. No entanto, eles têm certas limitações: