Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Dica
Esse conteúdo é um trecho do eBook, arquitetura de microsserviços do .NET para aplicativos .NET em contêineres, disponível em do .NET Docs ou como um PDF para download gratuito que pode ser lido offline.
Quando você usa bancos de dados relacionais, como SQL Server, Oracle ou PostgreSQL, uma abordagem recomendada é implementar a camada de persistência com base no EF (Entity Framework). O EF é compatível com LINQ e fornece objetos fortemente tipados para o modelo, bem como uma persistência simplificada no banco de dados.
O Entity Framework tem um longo histórico como parte do .NET Framework. Ao usar o .NET, você também deve usar o Entity Framework Core, que é executado no Windows ou linux da mesma maneira que o .NET. O EF Core é uma reescrita completa do Entity Framework que é implementada com um volume muito menor e melhorias importantes no desempenho.
Introdução ao Entity Framework Core
O Entity Framework (EF) Core é uma versão leve, extensível e multiplataforma da popular tecnologia de acesso a dados do Entity Framework. Ele foi introduzido com o .NET Core em meados de 2016.
Como uma introdução ao EF Core já está disponível na documentação da Microsoft, aqui simplesmente fornecemos links para essas informações.
Recursos adicionais
Entity Framework Core
https://learn.microsoft.com/ef/core/Introdução ao ASP.NET Core e ao Entity Framework Core usando o Visual Studio
https://learn.microsoft.com/aspnet/core/data/ef-mvc/Classe DbContext
https://learn.microsoft.com/dotnet/api/microsoft.entityframeworkcore.dbcontextComparar EF Core &eF6.x
https://learn.microsoft.com/ef/efcore-and-ef6/index
Infraestrutura no Entity Framework Core a partir de uma perspectiva de DDD
Do ponto de vista do DDD, uma funcionalidade importante do EF é a capacidade de usar entidades de domínio POCO, também conhecidas na terminologia de EF como entidades de código-primeiro POCO. 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.
Por padrões DDD, você deve encapsular o comportamento do domínio e as regras dentro da própria classe de entidade, para que ela possa controlar invariáveis, validações e regras ao acessar qualquer coleção. 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. Em vez disso, você deseja expor métodos que controlam como e quando seus campos e coleções de propriedades podem ser atualizados e quais comportamentos e ações devem ocorrer quando isso acontecer.
Desde o EF Core 1.1, para atender a esses requisitos de DDD, você pode ter campos simples em suas entidades em vez de propriedades públicas. Se você não quiser que um campo de entidade seja acessível externamente, basta criar o atributo ou o campo em vez de uma propriedade. Também é possível usar setters de propriedade privada.
Da mesma forma, agora é possível que haja acesso somente leitura a coleções usando uma propriedade pública tipada 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. As versões anteriores do Entity Framework precisavam de propriedades de coleção para dar suporte ICollection<T>
, o que significava que qualquer desenvolvedor que usa a classe de entidade pai poderia adicionar ou remover itens por meio de suas coleções de propriedades. Essa possibilidade seria contra os padrões recomendados no DDD.
Você pode usar uma coleção privada ao deixar visível um objeto de somente leitura IReadOnlyCollection<T>
, conforme mostrado no exemplo de código a seguir.
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);
}
}
A OrderItems
propriedade só pode ser acessada como somente leitura usando IReadOnlyCollection<OrderItem>
. Esse tipo é somente leitura, portanto, ele é protegido contra atualizações externas regulares.
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. É código .NET POCO puro, pois a ação de mapeamento é implementada na camada de persistência. Nessa ação de mapeamento, você precisa configurar o mapeamento de campos para banco de dados. No exemplo a seguir do método OnModelCreating
de OrderingContext
e da classe OrderEntityTypeConfiguration
, a chamada para SetPropertyAccessMode
instrui o EF Core a acessar a propriedade OrderItems
por meio de seu campo.
// 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
}
}
Quando você usa campos em vez de propriedades, a OrderItem
entidade é mantida como se tivesse uma List<OrderItem>
propriedade. No entanto, ela expõe um único acessador, o método AddOrderItem
, para adicionar novos itens ao pedido. Como resultado, o comportamento e os dados são vinculados e serão consistentes em todo o código do aplicativo que usa o modelo de domínio.
Implementar repositórios personalizados com o Entity Framework Core
No nível de implementação, um repositório é simplesmente uma classe com código de persistência de dados coordenado por uma unidade de trabalho (DBContext no EF Core) ao executar atualizações, conforme mostrado na seguinte classe:
// using directives...
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;
}
}
}
A IBuyerRepository
interface vem da camada de modelo de domínio como um contrato. No entanto, a implementação do repositório é feita na camada de persistência e infraestrutura.
O DbContext do EF é fornecido pelo construtor por meio de injeção de dependência. Ele é compartilhado entre vários repositórios dentro do mesmo escopo de solicitação HTTP, graças ao seu tempo de vida padrão (ServiceLifetime.Scoped
) no contêiner de IoC (que também pode ser definido explicitamente com services.AddDbContext<>
).
Métodos a serem implementados em um repositório (atualizações ou transações versus consultas)
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. Lembre-se de que há uma relação um-para-um entre uma agregação e seu repositório relacionado. Leve em consideração que um objeto de entidade de raiz de agregação pode ter entidades filhas inseridas no grafo do EF. Por exemplo, um comprador pode ter vários métodos de pagamento como entidades filhas relacionadas.
Como a abordagem para o microsserviço de ordenação no eShopOnContainers também é baseada em CQS/CQRS, a maioria das consultas não são implementadas em repositórios personalizados. Os desenvolvedores têm a liberdade de criar as consultas e junções necessárias para a camada de apresentação sem as restrições impostas por agregações, repositórios personalizados por agregação e DDD em geral. 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 que os dados sejam atualizados. Por exemplo, o repositório BuyerRepository implementa um método FindAsync, pois o aplicativo precisa saber se existe um comprador específico antes de criar um novo comprador relacionado ao pedido.
No entanto, os métodos de consulta reais para obter dados a serem enviados para a camada de apresentação ou aplicativos cliente são implementados, conforme mencionado, nas consultas CQRS com base em consultas flexíveis usando o Dapper.
Usando um repositório personalizado em vez de usar o DbContext EF diretamente
A classe DbContext do Entity Framework baseia-se nos padrões de Unidade de Trabalho e Repositório e pode ser usada diretamente no seu código, como em um controlador MVC ASP.NET Core. Os padrões de Unidade de Trabalho e Repositório resultam no código mais simples, como no microsserviço de catálogo CRUD no eShopOnContainers. Nos casos em que você deseja o código mais simples possível, convém usar diretamente a classe DbContext, como muitos desenvolvedores fazem.
No entanto, a implementação de repositórios personalizados oferece vários benefícios ao implementar microsserviços ou aplicativos mais complexos. Os padrões unidade de trabalho e repositório destinam-se a encapsular a camada de persistência de infraestrutura para que ela seja dissociada das camadas de aplicativo e modelo de domínio. Implementar esses padrões pode facilitar o uso de repositórios fictícios simulando o acesso ao banco de dados.
Na Figura 7-18, você pode ver as diferenças entre não usar repositórios (usando diretamente o EF DbContext) em vez de usar repositórios, o que facilita a simulação desses repositórios.
Figura 7-18. Usando repositórios personalizados versus um DbContext simples
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 zombando do repositório. Existem várias alternativas para simulação. Você pode simular apenas os repositórios ou pode emular toda uma operação lógica. 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.
Posteriormente, quando nos concentrarmos na camada do aplicativo, você verá como a Injeção de Dependência funciona no ASP.NET Core e como ela é implementada ao usar repositórios.
Em suma, os repositórios personalizados permitem testar o código com mais facilidade com testes de unidade que não são afetados pelo estado da camada de dados. Se você executar testes que também acessam o banco de dados real por meio do Entity Framework, eles não são testes de unidade, mas testes de integração, que são muito mais lentos.
Se você estivesse usando o DbContext diretamente, precisaria simular ou executar testes de unidade usando um SQL Server na memória com dados previsíveis para testes de unidade. Mas simular o DbContext ou controlar dados falsos requer mais trabalho do que a simulação no nível do repositório. Claro, você sempre pode testar os controladores MVC.
Tempo de vida da instância de DbContext e de IUnitOfWork do EF no contêiner de IoC
O DbContext
objeto (exposto como um IUnitOfWork
objeto) deve ser compartilhado entre vários repositórios dentro do mesmo escopo de solicitação HTTP. Por exemplo, isso é verdadeiro quando a operação que está sendo executada deve lidar com várias agregações ou simplesmente porque você está usando várias instâncias de repositório. Também é importante mencionar que a interface IUnitOfWork
faz parte da camada de domínio, ela não é um tipo do EF Core.
Para fazer isso, a vida útil de serviço da instância do objeto DbContext
precisa ser definida como ServiceLifetime.Scoped. Esse é o tempo de vida padrão ao registrar um DbContext
com builder.Services.AddDbContext
em seu contêiner de IoC do arquivo Program.cs em seu projeto de API Web do ASP.NET Core. O código a seguir ilustra isso.
// Add framework services.
builder.Services.AddMvc(options =>
{
options.Filters.Add(typeof(HttpGlobalExceptionFilter));
}).AddControllersAsServices();
builder.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 instanciação DbContext não deve ser configurado como ServiceLifetime.Transient ou ServiceLifetime.Singleton.
O tempo de vida da instância de repositório no contêiner de IoC
Da mesma forma, o tempo de vida do repositório deve ser definido como escopo (InstancePerLifetimeScope no 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.
// Registering a Repository in Autofac IoC container
builder.RegisterType<OrderRepository>()
.As<IOrderRepository>()
.InstancePerLifetimeScope();
O uso do tempo de vida singleton para o repositório pode causar sérios problemas de simultaneidade quando o DbContext é definido como tempo de vida com escopo (InstancePerLifetimeScope) (os tempos de vida padrão de um DBContext). Desde que seus tempos de vida de serviço para seus repositórios e seu DbContext estejam no escopo, você evitará esses problemas.
Recursos adicionais
Implementando o repositório e a unidade de padrões de trabalho em um aplicativo MVC ASP.NET
https://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-applicationJonathan Allen. Estratégias de implementação para o padrão de repositório com Entity Framework, Dapper e Chain
https://www.infoq.com/articles/repository-implementation-strategiesCesar de la Torre. Comparando os tempos de vida do serviço de contêiner de IoC do ASP.NET Core com os escopos de instância de contêiner de IoC do Autofac
https://devblogs.microsoft.com/cesardelatorre/comparing-asp-net-core-ioc-service-life-times-and-autofac-ioc-instance-scopes/
Mapeamento de tabelas
O mapeamento de tabela identifica os dados da tabela a serem consultados e salvos no banco de dados. Anteriormente, você viu como as entidades de domínio (por exemplo, um domínio de ordem ou produto) podem ser usadas para gerar um esquema de banco de dados relacionado. O EF foi projetado rigidamente de acordo com o conceito de convenções. As convenções abordam questões como "Qual será o nome de uma tabela?" ou "Qual propriedade é a chave primária?" As convenções normalmente são baseadas em nomes convencionais. Por exemplo, é típico que a chave primária seja uma propriedade que termina com Id
.
Por convenção, cada entidade será configurada para ser mapeada para uma tabela com o mesmo nome da propriedade DbSet<TEntity>
que expõe a entidade no contexto derivado. Se nenhum DbSet<TEntity>
valor for fornecido para a entidade fornecida, o nome da classe será usado.
Anotações de Dados versus API Fluente
Há muitas convenções adicionais do EF Core e a maioria delas pode ser alterada usando anotações de dados ou API Fluent, implementadas no método OnModelCreating.
As anotações de dados devem ser usadas nas próprias classes de modelo de entidade, o que é uma maneira mais intrusiva do ponto de vista do DDD. Isso ocorre porque você está contaminando seu modelo com anotações de dados relacionadas ao banco de dados de infraestrutura. Por outro lado, a API fluente é uma maneira conveniente de alterar a maioria das convenções e mapeamentos em sua camada de infraestrutura de persistência de dados, de modo que o modelo de entidade será limpo e dissociado da infraestrutura de persistência.
API fluente e o método OnModelCreating
Conforme mencionado, para alterar convenções e mapeamentos, você pode usar o método OnModelCreating na classe DbContext.
O microsserviço de ordenação no eShopOnContainers implementa o mapeamento e a configuração explícitos, quando necessário, conforme mostrado no código a seguir.
// 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)
.UseHiLo("orderseq", OrderingContext.DEFAULT_SCHEMA);
//Address value object persisted as owned entity type supported since EF Core 2.0
orderConfiguration
.OwnsOne(o => o.Address, a =>
{
a.WithOwner();
});
orderConfiguration
.Property<int?>("_buyerId")
.UsePropertyAccessMode(PropertyAccessMode.Field)
.HasColumnName("BuyerId")
.IsRequired(false);
orderConfiguration
.Property<DateTime>("_orderDate")
.UsePropertyAccessMode(PropertyAccessMode.Field)
.HasColumnName("OrderDate")
.IsRequired();
orderConfiguration
.Property<int>("_orderStatusId")
.UsePropertyAccessMode(PropertyAccessMode.Field)
.HasColumnName("OrderStatusId")
.IsRequired();
orderConfiguration
.Property<int?>("_paymentMethodId")
.UsePropertyAccessMode(PropertyAccessMode.Field)
.HasColumnName("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");
}
}
Você pode definir todos os mapeamentos de API fluente dentro do mesmo OnModelCreating
método, mas é aconselhável particionar esse código e ter várias classes de configuração, uma por entidade, conforme mostrado no exemplo. Especialmente para modelos grandes, é aconselhável ter classes de configuração separadas para configurar diferentes tipos de entidade.
O código no exemplo mostra algumas declarações explícitas e mapeamento. No entanto, as convenções do EF Core fazem muitos desses mapeamentos automaticamente, portanto, o código real necessário no seu caso pode ser menor.
O algoritmo Hi/Lo no EF Core
Um aspecto interessante do código no exemplo anterior é que ele usa o algoritmo Hi/Lo como a estratégia de geração de chave.
O algoritmo Hi/Lo é útil quando você precisa de chaves exclusivas antes de confirmar alterações. Como resumo, o algoritmo Hi-Lo atribui identificadores exclusivos a linhas de tabela, sem depender do armazenamento imediato da linha no banco de dados. Isso permite que você comece a usar os identificadores imediatamente, como acontece com IDs de banco de dados sequenciais regulares.
O algoritmo Hi/Lo descreve um mecanismo para obter um lote de IDs exclusivas de uma sequência de banco de dados relacionada. Essas IDs são seguras de usar porque o banco de dados garante a exclusividade, portanto, não haverá colisões entre os usuários. Esse algoritmo é interessante por estes motivos:
Ele não interrompe o padrão de unidade de trabalho.
Ele obtém IDs de sequência em lotes, para minimizar viagens de ida e volta para o banco de dados.
Ele gera um identificador legível para humanos, ao contrário de técnicas que usam GUIDs.
O EF Core dá suporte ao HiLo com o UseHiLo
método, conforme mostrado no exemplo anterior.
Mapear campos em vez de propriedades
Com esse recurso, disponível desde o EF Core 1.1, você pode mapear diretamente colunas para campos. É possível não usar propriedades na classe de entidade e apenas mapear colunas de uma tabela para campos. 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.
Você pode fazer isso com campos individuais ou também com coleções, como um List<>
campo. Esse ponto foi mencionado anteriormente quando discutimos a modelagem das classes de modelo de domínio, mas aqui você pode ver como esse mapeamento é executado com a PropertyAccessMode.Field
configuração realçada no código anterior.
Utilize propriedades de sombra no EF Core, ocultas ao nível de infraestrutura
As propriedades de sombra no EF Core são propriedades que não existem no modelo de classe de entidade. Os valores e os estados dessas propriedades são mantidos puramente na classe ChangeTracker no nível da infraestrutura.
Implementar o padrão de Especificação de Consulta
Conforme introduzido anteriormente na seção de design, o padrão especificação de consulta é um padrão de design Domain-Driven projetado como o local onde você pode colocar a definição de uma consulta com lógica opcional de classificação e paginação.
O padrão especificação de consulta define uma consulta em um objeto. Por exemplo, para encapsular uma consulta paginada que pesquisa alguns produtos, você pode criar uma especificação PagedProduct que usa os parâmetros de entrada necessários (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.
Um exemplo de uma interface especificação genérica é o código a seguir, que é semelhante ao código usado no aplicativo de referência 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; }
}
Em seguida, a implementação de uma classe base de especificação genérica é a seguinte.
// 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
// for example, Basket.Items.Product
protected virtual void AddInclude(string includeString)
{
IncludeStrings.Add(includeString);
}
}
A seguinte especificação carrega uma só entidade de cesta de acordo com a ID da cesta ou com a ID do comprador ao qual a cesta pertence. Ela fará o carregamento adiantado da cesta Items
.
// 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);
}
}
Por fim, você pode ver abaixo como um Repositório EF genérico pode usar essa especificação para filtrar e realizar o carregamento antecipado de dados relacionados a um determinado tipo de entidade 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 preenchidas.
Embora não recomendemos retornar IQueryable
de um repositório, é perfeitamente bom usá-los dentro do repositório para criar um conjunto de resultados. Você pode ver essa abordagem usada no método List acima, que usa expressões intermediárias IQueryable
para compilar a lista de inclusões da consulta antes de executar a consulta com os critérios da especificação na última linha.
Saiba como o padrão de especificação é aplicado no exemplo eShopOnWeb.
Recursos adicionais
Mapeamento de tabela
https://learn.microsoft.com/ef/core/modeling/relational/tablesUsar HiLo para gerar chaves com o Entity Framework Core
https://www.talkingdotnet.com/use-hilo-to-generate-keys-with-entity-framework-core/Campos de Backup
https://learn.microsoft.com/ef/core/modeling/backing-fieldSteve Smith. Coleções encapsuladas no Entity Framework Core
https://ardalis.com/encapsulated-collections-in-entity-framework-corePropriedades de sombra
https://learn.microsoft.com/ef/core/modeling/shadow-propertiesO padrão de especificação
https://deviq.com/specification-pattern/Pacote NuGet Ardalis.Specification Usado por eShopOnWeb. \ https://www.nuget.org/packages/Ardalis.Specification