Implementar a camada de persistência de infraestrutura com o Entity Framework CoreImplement the infrastructure persistence layer with Entity Framework Core

Ao usar bancos de dados relacionais, como o SQL Server, o Oracle ou o PostgreSQL, uma abordagem recomendada é implementar a camada de persistência com base no EF (Entity Framework).When you use relational databases such as SQL Server, Oracle, or PostgreSQL, a recommended approach is to implement the persistence layer based on Entity Framework (EF). O EF é compatível com LINQ e fornece objetos fortemente tipados para o modelo, bem como uma persistência simplificada no banco de dados.EF supports LINQ and provides strongly typed objects for your model, as well as simplified persistence into your database.

O Entity Framework tem uma longa história de participação no .NET Framework.Entity Framework has a long history as part of the .NET Framework. Ao usar o .NET Core, você também deve usar o Entity Framework Core, que é executado no Windows ou no Linux da mesma maneira que o .NET Core.When you use .NET Core, you should also use Entity Framework Core, which runs on Windows or Linux in the same way as .NET Core. O EF Core é uma reformulação completa do Entity Framework, implementado com muito menos espaço e importantes melhorias no desempenho.EF Core is a complete rewrite of Entity Framework, implemented with a much smaller footprint and important improvements in performance.

Introdução ao Entity Framework CoreIntroduction to Entity Framework Core

O Entity Framework (EF) Core é uma versão de multiplaforma leve, extensível e de plataforma cruzada da popular tecnologia de acesso a dados do Entity Framework.Entity Framework (EF) Core is a lightweight, extensible, and cross-platform version of the popular Entity Framework data access technology. Ele foi introduzido com o .NET Core em meados de 2016.It was introduced with .NET Core in mid-2016.

Como uma introdução ao EF Core já está disponível na documentação da Microsoft, aqui nós vamos fornecer apenas os links para as informações.Since an introduction to EF Core is already available in Microsoft documentation, here we simply provide links to that information.

Recursos adicionaisAdditional resources

Infraestrutura no Entity Framework Core da perspectiva do DDDInfrastructure in Entity Framework Core from a DDD perspective

Do ponto de vista do DDD, um recurso importante do EF é a capacidade de usar as entidades de domínio POCO (objeto CRL básico), também conhecidas na terminologia do EF como entidades code-first POCO.From a DDD point of view, an important capability of EF is the ability to use POCO domain entities, also known in EF terminology as POCO code-first entities. Se você usar as entidades de domínio POCO, as classes de modelo de domínio ignorarão a persistência, seguindo os princípios de Ignorância de Persistência e de Ignorância de Infraestrutura.If you use POCO domain entities, your domain model classes are persistence-ignorant, following the Persistence Ignorance and the Infrastructure Ignorance principles.

De acordo com os padrões do DDD você deve encapsular o comportamento e as regras do domínio dentro da própria classe de entidade, assim ela poderá controlar as invariáveis, as validações e as regras ao acessar qualquer coleção.Per DDD patterns, you should encapsulate domain behavior and rules within the entity class itself, so it can control invariants, validations, and rules when accessing any collection. Portanto, não é uma prática recomendada no DDD permitir o acesso público a coleções de entidades filhas ou a objetos de valor.Therefore, it is not a good practice in DDD to allow public access to collections of child entities or value objects. Em vez disso, é possível expor métodos que controlam como e quando as coleções de propriedade e os campos podem ser atualizados e qual comportamento e medidas deverão ser tomadas quando isso acontecer.Instead, you want to expose methods that control how and when your fields and property collections can be updated, and what behavior and actions should occur when that happens.

Desde o EF Core 1.1, para atender a esses requisitos de DDD, é possível ter campos simples nas entidades em vez de propriedades públicas.Since EF Core 1.1, to satisfy those DDD requirements, you can have plain fields in your entities instead of public properties. Se você não quiser que um campo de entidade fique acessível externamente, bastará criar o atributo ou o campo em vez de uma propriedade.If you do not want an entity field to be externally accessible, you can just create the attribute or field instead of a property. Também é possível usar setters de propriedade privada.You can also use private property setters.

Da mesma forma, agora é possível ter acesso somente leitura a coleções usando uma propriedade pública digitada como IReadOnlyCollection<T>, com o apoio de um membro de campo privado para a coleção (como uma List<T>) na entidade, que se baseia no EF para persistência.In a similar way, you can now have read-only access to collections by using a public property typed as IReadOnlyCollection<T>, which is backed by a private field member for the collection (like a List<T>) in your entity that relies on EF for persistence. As versões anteriores do Entity Framework exigiam que as propriedades da coleção fossem compatíveis com ICollection<T>, o que significava que qualquer desenvolvedor que usasse uma classe da entidade pai poderia adicionar ou remover itens por meio de suas coleções de propriedade.Previous versions of Entity Framework required collection properties to support ICollection<T>, which meant that any developer using the parent entity class could add or remove items through its property collections. Essa possibilidade seria em relação aos padrões recomendados no DDD.That possibility would be against the recommended patterns in DDD.

É possível usar uma coleção privada ao expor um objeto IReadOnlyCollection<T> somente leitura, como é mostrado no exemplo de código a seguir:You can use a private collection while exposing a read-only IReadOnlyCollection<T> object, as shown in the following code example:

public class Order : Entity
{
    // Using private fields, allowed since EF Core 1.1
    private DateTime _orderDate;
    // Other fields ...

    private readonly List<OrderItem> _orderItems;
    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;

    protected Order() { }

    public Order(int buyerId, int paymentMethodId, Address address)
    {
        // Initializations ...
    }

    public void AddOrderItem(int productId, string productName,
                             decimal unitPrice, decimal discount,
                             string pictureUrl, int units = 1)
    {
        // Validation logic...

        var orderItem = new OrderItem(productId, productName,
                                      unitPrice, discount,
                                      pictureUrl, units);
        _orderItems.Add(orderItem);
    }
}

Observe que a propriedade OrderItems somente pode ser acessada como somente leitura usando IReadOnlyCollection<OrderItem>.Note that the OrderItems property can only be accessed as read-only using IReadOnlyCollection<OrderItem>. Esse tipo é somente leitura, portanto, ele está protegido contra as atualizações externas regulares.This type is read-only so it is protected against regular external updates.

O EF Core fornece uma maneira de mapear o modelo de domínio para o banco de dados físico sem "contaminar" o modelo de domínio.EF Core provides a way to map the domain model to the physical database without "contaminating" the domain model. Trata-se de puro código POCO do .NET, pois a ação de mapeamento é implementada na camada de persistência.It is pure .NET POCO code, because the mapping action is implemented in the persistence layer. Nessa ação de mapeamento, você precisa configurar o mapeamento dos campos para o banco de dados.In that mapping action, you need to configure the fields-to-database mapping. No exemplo a seguir do método OnModelCreating de OrderingContext e da classe OrderEntityTypeConfiguration, a chamada para SetPropertyAccessMode informa ao EF Core para acessar a propriedade OrderItems por meio de seu campo.In the following example of the OnModelCreating method from OrderingContext and the OrderEntityTypeConfiguration class, the call to SetPropertyAccessMode tells EF Core to access the OrderItems property through its field.

// At OrderingContext.cs from eShopOnContainers
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   // ...
   modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
   // Other entities’ configuration ...
}

// At OrderEntityTypeConfiguration.cs from eShopOnContainers
class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
    public void Configure(EntityTypeBuilder<Order> orderConfiguration)
    {
        orderConfiguration.ToTable("orders", OrderingContext.DEFAULT_SCHEMA);
        // Other configuration

        var navigation =
              orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems));

        //EF access the OrderItem collection property through its backing field
        navigation.SetPropertyAccessMode(PropertyAccessMode.Field);

        // Other configuration
    }
}

Ao usar campos em vez de propriedades, a entidade OrderItem será persistida como se tivesse uma propriedade List<OrderItem>.When you use fields instead of properties, the OrderItem entity is persisted just as if it had a List<OrderItem> property. No entanto, ela expõe um único acessador, o método AddOrderItem, para adicionar novos itens ao pedido.However, it exposes a single accessor, the AddOrderItem method, for adding new items to the order. Como resultado, o comportamento e os dados ficarão vinculados e serão consistentes em todos os códigos de aplicativo que usarem o modelo de domínio.As a result, behavior and data are tied together and will be consistent throughout any application code that uses the domain model.

Implementar repositórios personalizados com o Entity Framework CoreImplement custom repositories with Entity Framework Core

No nível da implementação, um repositório é simplesmente uma classe com o código de persistência de dados, coordenada por uma unidade de trabalho (DBContext no EF Core) ao executar atualizações, como mostra a seguinte classe:At the implementation level, a repository is simply a class with data persistence code coordinated by a unit of work (DBContext in EF Core) when performing updates, as shown in the following class:

// using statements...
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
    public class BuyerRepository : IBuyerRepository
    {
        private readonly OrderingContext _context;
        public IUnitOfWork UnitOfWork
        {
            get
            {
                return _context;
            }
        }

        public BuyerRepository(OrderingContext context)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
        }

        public Buyer Add(Buyer buyer)
        {
            return _context.Buyers.Add(buyer).Entity;
        }

        public async Task<Buyer> FindAsync(string BuyerIdentityGuid)
        {
            var buyer = await _context.Buyers
                .Include(b => b.Payments)
                .Where(b => b.FullName == BuyerIdentityGuid)
                .SingleOrDefaultAsync();

            return buyer;
        }
    }
}

Observe que a interface IBuyerRepository vem da camada de modelo de domínio como um contrato.Note that the IBuyerRepository interface comes from the domain model layer as a contract. No entanto, a implementação do repositório é feita na camada de persistência e de infraestrutura.However, the repository implementation is done at the persistence and infrastructure layer.

O DbContext do EF é fornecido pelo construtor por meio de injeção de dependência.The EF DbContext comes through the constructor through Dependency Injection. Ele é compartilhado entre vários repositórios no mesmo escopo de solicitação HTTP, graças ao seu tempo de vida padrão (ServiceLifetime.Scoped) no contêiner de IoC (inversão de controle) (que também pode ser definido explicitamente com services.AddDbContext<>).It is shared between multiple repositories within the same HTTP request scope, thanks to its default lifetime (ServiceLifetime.Scoped) in the IoC container (which can also be explicitly set with services.AddDbContext<>).

Métodos a serem implementados em um repositório (atualizações ou transações em comparação com consultas)Methods to implement in a repository (updates or transactions versus queries)

Em cada classe de repositório, você deve colocar os métodos de persistência que atualizam o estado das entidades contidas na agregação relacionada.Within each repository class, you should put the persistence methods that update the state of entities contained by its related aggregate. Lembre-se de que há uma relação um-para-um entre uma agregação e seu repositório relacionado.Remember there is one-to-one relationship between an aggregate and its related repository. Leve em consideração que um objeto de entidade de raiz de agregação pode ter entidades filhas inseridas no grafo do EF.Consider that an aggregate root entity object might have embedded child entities within its EF graph. Por exemplo, um comprador pode ter vários métodos de pagamento como entidades filhas relacionadas.For example, a buyer might have multiple payment methods as related child entities.

Como a abordagem para o microsserviço de pedidos no eShopOnContainers também se baseia em CQS/CQRS, a maioria das consultas não são implementadas em repositórios personalizados.Since the approach for the ordering microservice in eShopOnContainers is also based on CQS/CQRS, most of the queries are not implemented in custom repositories. Os desenvolvedores têm a liberdade de criar as consultas e junções que precisam para a camada de apresentação sem as restrições impostas pelas agregações, pelos repositórios personalizados por agregação e pelo DDD em geral.Developers have the freedom to create the queries and joins they need for the presentation layer without the restrictions imposed by aggregates, custom repositories per aggregate, and DDD in general. A maioria dos repositórios personalizados sugeridos por este guia tem vários métodos de atualização ou transacionais, mas apenas os métodos de consulta necessários para fazer com que os dados sejam atualizados.Most of the custom repositories suggested by this guide have several update or transactional methods but just the query methods needed to get data to be updated. Por exemplo, o repositório BuyerRepository implementa um método FindAsync, porque o aplicativo precisa saber se um comprador específico existe antes de criar um novo comprador relacionado ao pedido.For example, the BuyerRepository repository implements a FindAsync method, because the application needs to know whether a particular buyer exists before creating a new buyer related to the order.

No entanto, os métodos de consulta reais para obter os dados a serem enviados à camada de apresentação ou aos aplicativos clientes são implementados, conforme mencionado, nas consultas de CQRS baseadas em consultas flexíveis usando Dapper.However, the real query methods to get data to send to the presentation layer or client apps are implemented, as mentioned, in the CQRS queries based on flexible queries using Dapper.

Usando um repositório personalizado em vez de usar o DbContext EF diretamenteUsing a custom repository versus using EF DbContext directly

A classe DbContext do Entity Framework baseia-se nos padrões de unidade de trabalho e de repositório e pode ser usada diretamente no código, como em um controlador MVC do ASP.NET Core.The Entity Framework DbContext class is based on the Unit of Work and Repository patterns, and can be used directly from your code, such as from an ASP.NET Core MVC controller. Essa é a maneira de criar o código mais simples possível, como o microsserviço de catálogo de CRUD (criar, ler, atualizar e excluir) no eShopOnContainers.That is the way you can create the simplest code, as in the CRUD catalog microservice in eShopOnContainers. Nos casos em que você deseja o código mais simples possível, é possível usar diretamente a classe DbContext, como muitos desenvolvedores fazem.In cases where you want the simplest code possible, you might want to directly use the DbContext class, as many developers do.

No entanto, a implementação de repositórios personalizados oferece vários benefícios ao implementar microsserviços ou aplicativos mais complexos.However, implementing custom repositories provides several benefits when implementing more complex microservices or applications. Os padrões de unidade de trabalho e de repositório são indicados para encapsular a camada de persistência da infraestrutura para que ela fique desacoplada das camadas de aplicativo e de modelo de domínio.The Unit of Work and Repository patterns are intended to encapsulate the infrastructure persistence layer so it is decoupled from the application and domain model layers. A implementação desses padrões pode facilitar o uso de repositórios fictícios para simulação de acesso ao banco de dados.Implementing these patterns can facilitate the use of mock repositories simulating access to the database.

Na Figura 7-18, veja as diferenças entre não usar repositórios (usando diretamente o DbContext do EF) e usar repositórios, o que facilita a simulação desses repositórios.In Figure 7-18 you can see the differences between not using repositories (directly using the EF DbContext) versus using repositories which make it easier to mock those repositories.

Diagrama mostrando os componentes e Dataflow nos dois repositórios.

Figura 7-18.Figure 7-18. Usando repositórios personalizados em vez de um DbContext simplesUsing custom repositories versus a plain DbContext

A Figura 7-18 mostra que o uso de um repositório personalizado adiciona uma camada de abstração que pode ser usada para facilitar o teste, simulando o repositório.Figure 7-18 shows that using a custom repository adds an abstraction layer that can be used to ease testing by mocking the repository. Existem várias alternativas para simulação.There are multiple alternatives when mocking. Você pode simular apenas repositórios ou simular toda a unidade de trabalho.You could mock just repositories or you could mock a whole unit of work. Geralmente, simular apenas os repositórios já é suficiente e a complexidade de abstrair e simular toda a unidade de trabalho, normalmente, não é necessária.Usually mocking just the repositories is enough, and the complexity to abstract and mock a whole unit of work is usually not needed.

Mais adiante, quando nos concentramos na camada de aplicativo, você verá como funciona a injeção de dependência no ASP.NET Core e como ela é implementada ao usar repositórios.Later, when we focus on the application layer, you will see how Dependency Injection works in ASP.NET Core and how it is implemented when using repositories.

Em resumo, os repositórios personalizados permitem que você teste o código mais facilmente com testes de unidade que não são afetados pelo estado da camada de dados.In short, custom repositories allow you to test code more easily with unit tests that are not impacted by the data tier state. Se você executar testes que também acessem o banco de dados real por meio do Entity Framework, eles não serão testes de unidade, mas sim testes de integração, que são muito mais lentos.If you run tests that also access the actual database through the Entity Framework, they are not unit tests but integration tests, which are a lot slower.

Se estivesse usando o DbContext diretamente, você precisaria simulá-lo ou executar testes de unidade usando um SQL Server na memória, com os dados previsíveis para testes de unidade.If you were using DbContext directly, you would have to mock it or to run unit tests by using an in-memory SQL Server with predictable data for unit tests. Mas simular o DbContext ou controlar dados falsos requer mais trabalho do que a simulação no nível do repositório.But mocking the DbContext or controlling fake data requires more work than mocking at the repository level. Obviamente, sempre é possível testar os controladores MVC.Of course, you could always test the MVC controllers.

Tempo de vida da instância de DbContext e de IUnitOfWork do EF no contêiner de IoCEF DbContext and IUnitOfWork instance lifetime in your IoC container

O objeto DbContext (exposto como um objeto IUnitOfWork) deve ser compartilhado entre vários repositórios dentro do mesmo escopo de solicitação HTTP.The DbContext object (exposed as an IUnitOfWork object) should be shared among multiple repositories within the same HTTP request scope. Por exemplo, isso é verdadeiro quando a operação que está sendo executada precisa lidar com várias agregações ou simplesmente porque você está usando várias instâncias do repositório.For example, this is true when the operation being executed must deal with multiple aggregates, or simply because you are using multiple repository instances. Também é importante mencionar que a interface IUnitOfWork faz parte da camada de domínio, ela não é um tipo do EF Core.It is also important to mention that the IUnitOfWork interface is part of your domain layer, not an EF Core type.

Para isso, o tempo de vida de serviço da instância do objeto DbContext precisa ser definido como ServiceLifetime.Scoped.In order to do that, the instance of the DbContext object has to have its service lifetime set to ServiceLifetime.Scoped. Este é o tempo de vida padrão ao registrar um DbContext com services.AddDbContext no contêiner de IoC (inversão de controle) do método ConfigureServices do arquivo Startup.cs, no projeto de API Web ASP.NET Core.This is the default lifetime when registering a DbContext with services.AddDbContext in your IoC container from the ConfigureServices method of the Startup.cs file in your ASP.NET Core Web API project. O código a seguir ilustra isso.The following code illustrates this.

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(HttpGlobalExceptionFilter));
    }).AddControllersAsServices();

    services.AddEntityFrameworkSqlServer()
      .AddDbContext<OrderingContext>(options =>
      {
          options.UseSqlServer(Configuration["ConnectionString"],
                               sqlOptions => sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().
                                                                                    Assembly.GetName().Name));
      },
      ServiceLifetime.Scoped // Note that Scoped is the default choice
                             // in AddDbContext. It is shown here only for
                             // pedagogic purposes.
      );
}

O modo de criação de instância do DbContext não deve ser configurado como ServiceLifetime.Transient ou ServiceLifetime.Singleton.The DbContext instantiation mode should not be configured as ServiceLifetime.Transient or ServiceLifetime.Singleton.

O tempo de vida da instância de repositório no contêiner de IoCThe repository instance lifetime in your IoC container

Da mesma forma, o tempo de vida do repositório normalmente deve ser definido como no escopo (InstancePerLifetimeScope no Autofac).In a similar way, repository’s lifetime should usually be set as scoped (InstancePerLifetimeScope in Autofac). Ele também pode ser transitório (InstancePerDependency no Autofac), mas o serviço será mais eficiente em relação à memória ao usar o tempo de vida no escopo.It could also be transient (InstancePerDependency in Autofac), but your service will be more efficient in regards memory when using the scoped lifetime.

// Registering a Repository in Autofac IoC container
builder.RegisterType<OrderRepository>()
    .As<IOrderRepository>()
    .InstancePerLifetimeScope();

Observe que usar o tempo de vida singleton para o repositório poderá causar problemas graves de simultaneidade quando o DbContext estiver definido como tempo de vida no escopo (InstancePerLifetimeScope) (os tempos de vida padrão de um DBContext).Note that using the singleton lifetime for the repository could cause you serious concurrency problems when your DbContext is set to scoped (InstancePerLifetimeScope) lifetime (the default lifetimes for a DBContext).

Recursos adicionaisAdditional resources

Mapeamento de tabelaTable mapping

O mapeamento de tabela identifica os dados de tabela a serem consultados e salvos no banco de dados.Table mapping identifies the table data to be queried from and saved to the database. Você já viu como as entidades de domínio (por exemplo, um domínio de produto ou de pedido) podem ser usadas para gerar um esquema de banco de dados relacionado.Previously you saw how domain entities (for example, a product or order domain) can be used to generate a related database schema. O EF foi projetado rigidamente de acordo com o conceito de convenções.EF is strongly designed around the concept of conventions. As convenções abrangem perguntas como "Qual será o nome de uma tabela?"Conventions address questions like “What will the name of a table be?” ou "Qual propriedade é a chave primária?"or “What property is the primary key?” As convenções normalmente se baseiam nos nomes convencionais. Por exemplo, é comum que a chave primária seja uma propriedade que termine com Id.Conventions are typically based on conventional names—for example, it is typical for the primary key to be a property that ends with Id.

Por convenção, cada entidade será configurada para ser mapeada para uma tabela com o mesmo nome que a propriedade DbSet<TEntity> que expõe a entidade no contexto derivado.By convention, each entity will be set up to map to a table with the same name as the DbSet<TEntity> property that exposes the entity on the derived context. Se nenhum valor de DbSet<TEntity> for fornecido para a entidade especificada, o nome da classe será usado.If no DbSet<TEntity> value is provided for the given entity, the class name is used.

Anotações de dados em comparação com a API fluenteData Annotations versus Fluent API

Existem muitas convenções do EF Core adicionais e a maioria delas pode ser alterada usando anotações de dados ou a API fluente, implementada no método OnModelCreating.There are many additional EF Core conventions, and most of them can be changed by using either data annotations or Fluent API, implemented within the OnModelCreating method.

As anotações de dados devem ser usadas nas próprias classes de modelo de entidade, o que é uma maneira mais invasiva do ponto de vista de DDD.Data annotations must be used on the entity model classes themselves, which is a more intrusive way from a DDD point of view. Isso ocorre porque você está contaminando o modelo com anotações de dados relacionadas ao banco de dados de infraestrutura.This is because you are contaminating your model with data annotations related to the infrastructure database. Por outro lado, a API fluente é uma maneira conveniente de alterar a maioria das convenções e dos mapeamentos dentro da camada de infraestrutura de persistência de dados, para que o modelo de entidade fique limpo e desacoplado da infraestrutura de persistência.On the other hand, Fluent API is a convenient way to change most conventions and mappings within your data persistence infrastructure layer, so the entity model will be clean and decoupled from the persistence infrastructure.

API fluente e o método OnModelCreatingFluent API and the OnModelCreating method

Conforme mencionado, para alterar as convenções e os mapeamentos, você pode usar o método OnModelCreating na classe DbContext.As mentioned, in order to change conventions and mappings, you can use the OnModelCreating method in the DbContext class.

O microsserviço de pedidos no eShopOnContainers implementa o mapeamento e configuração explícitos, quando necessário, conforme é mostrado no código a seguir.The ordering microservice in eShopOnContainers implements explicit mapping and configuration, when needed, as shown in the following code.

// At OrderingContext.cs from eShopOnContainers
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   // ...
   modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
   // Other entities’ configuration ...
}

// At OrderEntityTypeConfiguration.cs from eShopOnContainers
class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
    public void Configure(EntityTypeBuilder<Order> orderConfiguration)
    {
            orderConfiguration.ToTable("orders", OrderingContext.DEFAULT_SCHEMA);

            orderConfiguration.HasKey(o => o.Id);

            orderConfiguration.Ignore(b => b.DomainEvents);

            orderConfiguration.Property(o => o.Id)
                .ForSqlServerUseSequenceHiLo("orderseq", OrderingContext.DEFAULT_SCHEMA);

            //Address Value Object persisted as owned entity type supported since EF Core 2.0
            orderConfiguration.OwnsOne(o => o.Address);

            orderConfiguration.Property<DateTime>("OrderDate").IsRequired();
            orderConfiguration.Property<int?>("BuyerId").IsRequired(false);
            orderConfiguration.Property<int>("OrderStatusId").IsRequired();
            orderConfiguration.Property<int?>("PaymentMethodId").IsRequired(false);
            orderConfiguration.Property<string>("Description").IsRequired(false);

            var navigation = orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems));

            // DDD Patterns comment:
            //Set as field (New since EF 1.1) to access the OrderItem collection property through its field
            navigation.SetPropertyAccessMode(PropertyAccessMode.Field);

            orderConfiguration.HasOne<PaymentMethod>()
                .WithMany()
                .HasForeignKey("PaymentMethodId")
                .IsRequired(false)
                .OnDelete(DeleteBehavior.Restrict);

            orderConfiguration.HasOne<Buyer>()
                .WithMany()
                .IsRequired(false)
                .HasForeignKey("BuyerId");

            orderConfiguration.HasOne(o => o.OrderStatus)
                .WithMany()
                .HasForeignKey("OrderStatusId");
    }
}

É possível definir todos os mapeamentos da API fluente no mesmo método OnModelCreating, mas é recomendável particionar esse código e ter várias classes de configuração, uma por entidade, conforme é mostrado no exemplo.You could set all the Fluent API mappings within the same OnModelCreating method, but it is advisable to partition that code and have multiple configuration classes, one per entity, as shown in the example. Principalmente para modelos muito grandes, é recomendável ter classes de configuração separadas para configurar tipos de entidade diferentes.Especially for particularly large models, it is advisable to have separate configuration classes for configuring different entity types.

O código no exemplo mostra algumas declarações e mapeamentos explícitos.The code in the example shows a few explicit declarations and mapping. No entanto, as convenções do EF Core fazem muitos desses mapeamentos automaticamente, portanto, o código real necessário para o seu caso poderá ser menor.However, EF Core conventions do many of those mappings automatically, so the actual code you would need in your case might be smaller.

O algoritmo Hi/Lo no EF CoreThe Hi/Lo algorithm in EF Core

Um aspecto interessante de código no exemplo anterior é que ele usa o algoritmo Hi/Lo como a estratégia de geração de chave.An interesting aspect of code in the preceding example is that it uses the Hi/Lo algorithm as the key generation strategy.

O algoritmo Hi/Lo é útil quando você precisa de chaves exclusivas antes de confirmar as alterações.The Hi/Lo algorithm is useful when you need unique keys before committing changes. Em resumo, o algoritmo Hi-Lo atribui identificadores exclusivos às linhas da tabela, embora ele não dependa de armazenar a linha no banco de dados imediatamente.As a summary, the Hi-Lo algorithm assigns unique identifiers to table rows while not depending on storing the row in the database immediately. Isso permite começar a usar os identificadores imediatamente, como acontece com as IDs de banco de dados sequenciais regulares.This lets you start using the identifiers right away, as happens with regular sequential database IDs.

O algoritmo Hi/Lo descreve um mecanismo para obter um lote de IDs exclusivas de uma sequência de banco de dados relacionado.The Hi/Lo algorithm describes a mechanism for getting a batch of unique IDs from a related database sequence. Essas IDs são seguras usar porque o banco de dados garante a exclusividade, portanto, não haverá nenhuma colisão entre usuários.These IDs are safe to use because the database guarantees the uniqueness, so there will be no collisions between users. Esse algoritmo é interessante por estes motivos:This algorithm is interesting for these reasons:

  • Ele não interrompe o padrão de unidade de trabalho.It does not break the Unit of Work pattern.

  • Ele obtém as IDs da sequência em lotes, para minimizar as viagens de ida e para o banco de dados.It gets sequence IDs in batches, to minimize round trips to the database.

  • Ele gera um identificador legível por pessoas, ao contrário das técnicas que usam GUIDs.It generates a human readable identifier, unlike techniques that use GUIDs.

O EF Core é compatível com o HiLo com o método ForSqlServerUseSequenceHiLo, conforme foi mostrado no exemplo anterior.EF Core supports HiLo with the ForSqlServerUseSequenceHiLo method, as shown in the preceding example.

Mapear campos em vez de propriedadesMap fields instead of properties

Com esse recurso, disponível desde o EF Core 1.1, você pode mapear colunas para campos diretamente.With this feature, available since EF Core 1.1, you can directly map columns to fields. É possível não usar as propriedades na classe de entidade e apenas mapear as colunas de uma tabela para os campos.It is possible to not use properties in the entity class, and just to map columns from a table to fields. Um caso de uso comum para isso seria os campos privados de algum estado interno que não precisam ser acessados de fora da entidade.A common use for that would be private fields for any internal state that do not need to be accessed from outside the entity.

Você pode fazer isso com campos únicos ou também com coleções, como um campo List<>.You can do this with single fields or also with collections, like a List<> field. Esse ponto já foi mencionado quando discutimos a modelagem das classes de modelo de domínio, mas aqui você pode ver como esse mapeamento é realizado com a configuração PropertyAccessMode.Field realçada no código anterior.This point was mentioned earlier when we discussed modeling the domain model classes, but here you can see how that mapping is performed with the PropertyAccessMode.Field configuration highlighted in the previous code.

Usar propriedades de sombra no EF Core, ocultas no nível da infraestruturaUse shadow properties in EF Core, hidden at the infrastructure level

As propriedades de sombra no EF Core são propriedades que não existem no modelo de classe de entidade.Shadow properties in EF Core are properties that do not exist in your entity class model. Os valores e os estados dessas propriedades são mantidos unicamente na classe ChangeTracker no nível da infraestrutura.The values and states of these properties are maintained purely in the ChangeTracker class at the infrastructure level.

Implementar o padrão de especificação de consultaImplement the Query Specification pattern

Conforme já foi apresentado na seção sobre design, o padrão de especificação de consulta é um padrão de design orientado por domínio projetado para ser o local em que você pode colocar a definição de uma consulta com uma lógica opcional de classificação e paginação.As introduced earlier in the design section, the Query Specification pattern is a Domain-Driven Design pattern designed as the place where you can put the definition of a query with optional sorting and paging logic.

O padrão de especificação de consulta define uma consulta em um objeto.The Query Specification pattern defines a query in an object. Por exemplo, para encapsular uma consulta paginada que procura por alguns produtos, você poderia criar uma especificação PagedProduct que usasse os parâmetros de entrada necessários (pageNumber, pageSize, filtro, etc.).For example, in order to encapsulate a paged query that searches for some products you can create a PagedProduct specification that takes the necessary input parameters (pageNumber, pageSize, filter, etc.). Então, qualquer método de repositório [geralmente uma sobrecarga List()] aceitaria uma IQuerySpecification e executaria a consulta esperada com base nessa especificação.Then, within any Repository method (usually a List() overload) it would accept an IQuerySpecification and run the expected query based on that specification.

Um exemplo de uma interface de especificação genérica é o código a seguir de eShopOnWeb.An example of a generic Specification interface is the following code from eShopOnWeb.

// GENERIC SPECIFICATION INTERFACE
// https://github.com/dotnet-architecture/eShopOnWeb

public interface ISpecification<T>
{
    Expression<Func<T, bool>> Criteria { get; }
    List<Expression<Func<T, object>>> Includes { get; }
    List<string> IncludeStrings { get; }
}

Assim, a implementação de uma classe base de especificação genérica seria a seguinte.Then, the implementation of a generic specification base class is the following.

// GENERIC SPECIFICATION IMPLEMENTATION (BASE CLASS)
// https://github.com/dotnet-architecture/eShopOnWeb

public abstract class BaseSpecification<T> : ISpecification<T>
{
    public BaseSpecification(Expression<Func<T, bool>> criteria)
    {
        Criteria = criteria;
    }
    public Expression<Func<T, bool>> Criteria { get; }

    public List<Expression<Func<T, object>>> Includes { get; } =
                                           new List<Expression<Func<T, object>>>();

    public List<string> IncludeStrings { get; } = new List<string>();

    protected virtual void AddInclude(Expression<Func<T, object>> includeExpression)
    {
        Includes.Add(includeExpression);
    }

    // string-based includes allow for including children of children
    // e.g. Basket.Items.Product
    protected virtual void AddInclude(string includeString)
    {
        IncludeStrings.Add(includeString);
    }
}

A seguinte especificação carrega uma única entidade de cesta de compras de acordo com a ID da cesta de compras ou com a ID do comprador ao qual a cesta de compras pertence.The following specification loads a single basket entity given either the basket’s ID or the ID of the buyer to whom the basket belongs. Isso fará o carregamento adiantado da coleção de itens do carrinho de compras.It will eagerly load the basket’s Items collection.

// SAMPLE QUERY SPECIFICATION IMPLEMENTATION

public class BasketWithItemsSpecification : BaseSpecification<Basket>
{
    public BasketWithItemsSpecification(int basketId)
        : base(b => b.Id == basketId)
    {
        AddInclude(b => b.Items);
    }
    public BasketWithItemsSpecification(string buyerId)
        : base(b => b.BuyerId == buyerId)
    {
        AddInclude(b => b.Items);
    }
}

E, finalmente, veja abaixo como um repositório do EF genérico pode usar uma especificação desse tipo para filtrar e fazer o carregamento adiantado dos dados relacionados a um determinado tipo de entidade T.And finally, you can see below how a generic EF Repository can use such a specification to filter and eager-load data related to a given entity type T.

// GENERIC EF REPOSITORY WITH SPECIFICATION
// https://github.com/dotnet-architecture/eShopOnWeb

public IEnumerable<T> List(ISpecification<T> spec)
{
    // fetch a Queryable that includes all expression-based includes
    var queryableResultWithIncludes = spec.Includes
        .Aggregate(_dbContext.Set<T>().AsQueryable(),
            (current, include) => current.Include(include));

    // modify the IQueryable to include any string-based include statements
    var secondaryResult = spec.IncludeStrings
        .Aggregate(queryableResultWithIncludes,
            (current, include) => current.Include(include));

    // return the result of the query using the specification's criteria expression
    return secondaryResult
                    .Where(spec.Criteria)
                    .AsEnumerable();
}

Além de encapsular a lógica de filtragem, a especificação pode especificar a forma dos dados a serem retornados, incluindo quais propriedades devem ser populadas.In addition to encapsulating filtering logic, the specification can specify the shape of the data to be returned, including which properties to populate.

Embora não seja recomendado retornar IQueryable de um repositório, é perfeitamente normal usá-lo no repositório para criar um conjunto de resultados.Although we don’t recommend to return IQueryable from a repository, it’s perfectly fine to use them within the repository to build up a set of results. Veja essa abordagem usada no método List acima, que usa expressões de IQueryable intermediárias para criar a lista de inclusões da consulta antes de executar a consulta com os critérios da especificação na última linha.You can see this approach used in the List method above, which uses intermediate IQueryable expressions to build up the query’s list of includes before executing the query with the specification’s criteria on the last line.

Recursos adicionaisAdditional resources