From 2221dc6bf70d3fdb3e83608c658ee7a6e0228779 Mon Sep 17 00:00:00 2001 From: Amedeo D'Amelio Date: Thu, 22 Oct 2026 17:14:28 +0200 Subject: [PATCH] refactor: docs translation in english --- docs/adonet.md | 58 +++++++------- docs/dataaccesslayer.md | 168 ++++++++++++++++++++-------------------- docs/efcore.md | 88 ++++++++++----------- docs/mongodb.md | 4 +- docs/net6.md | 8 +- 5 files changed, 163 insertions(+), 163 deletions(-) diff --git a/docs/adonet.md b/docs/adonet.md index a6cde20..92dc961 100644 --- a/docs/adonet.md +++ b/docs/adonet.md @@ -1,19 +1,19 @@ # ADO.NET -Per utilizzare Dapper come ORM, che a sua volta utilizza l'implementazione ADO.NET del repository, impostare appropriatamente il valore del campo `data.orm` dello yml. +To use Dapper as the ORM, which in turn uses the ADO.NET implementation of the repository, set the `data.orm` field of the yml accordingly. ```yml data: orm: Dapper ``` -## Entità +## Entities -Il DAL ADO.NET utilizza la classe base `ModelConfiguration` per la configurazione di entità e relazioni. Per configurare il modello relazionale, è necessario estendere questa classe e dichiarare le entità e le relazioni tra di esse. +The ADO.NET DAL uses the `ModelConfiguration` base class to configure entities and relationships. To configure the relational model, extend this class and declare the entities and the relationships between them. -Per quanto riguarda le entità, esse si dichiarano utilizzando il metodo `Entity` che accetta come parametro di tipo l'entità e come parametro una lambda che configura le proprietà dell'entità. +Entities are declared with the `Entity` method, which takes the entity as its type parameter and, as its argument, a lambda that configures the entity's properties. -Ad esempio, supponiamo di avere un'entità modellata dal seguente yml: +For example, suppose we have an entity modelled by the following yml: ```yml entities: @@ -30,7 +30,7 @@ entities: description: Category description ``` -Questo, oltre a generare l'entità in sé, grazie alla flag `useRepository`, genererà la seguente configurazione: +Besides generating the entity itself, thanks to the `useRepository` flag this generates the following configuration: ```cs public class MyModelConfiguration : ModelConfiguration @@ -39,19 +39,19 @@ public class MyModelConfiguration : ModelConfiguration { Entity(builder => { - // configurazione + // configuration }); } } ``` -## Associazioni +## Associations -Le associazioni vengono definite attraverso i metodi `Aggregate` e `Associate`, che accettano come parametri di tipo le due entità da mettere in relazione e come parametro una lambda che configura la relazione tramite un'API fluent. +Associations are defined with the `Aggregate` and `Associate` methods, which take the two entities to relate as type parameters and, as their argument, a lambda that configures the relationship through a fluent API. -### Configurazione associazione uno-a-uno, intra-aggregato +### Configuring a one-to-one, intra-aggregate association -Supponiamo di avere le entità `Customer` e `Address`, in relazione uno-a-uno e facenti parte dello stesso aggregato, con `Customer` come Aggregate Root. +Suppose we have the entities `Customer` and `Address`, in a one-to-one relationship and part of the same aggregate, with `Customer` as the Aggregate Root. ```cs public class Customer @@ -67,7 +67,7 @@ public class Address } ``` -La relazione è modellabile con il seguente yml: +The association can be modelled with the following yml: ```yml associations: @@ -81,7 +81,7 @@ associations: navigation: Customer ``` -Che genererà la seguente configurazione: +This generates the following configuration: ```cs public class MyModelConfiguration : ModelConfiguration @@ -96,9 +96,9 @@ public class MyModelConfiguration : ModelConfiguration } ``` -### Configurazione associazione uno-a-uno, inter-aggregato +### Configuring a one-to-one, inter-aggregate association -Supponiamo di avere le entità `Customer` e `Address`, in relazione uno-a-uno e non facenti parte dello stesso aggregato. +Suppose we have the entities `Customer` and `Address`, in a one-to-one relationship and not part of the same aggregate. ```cs public class Customer @@ -114,7 +114,7 @@ public class Address } ``` -La relazione è modellabile con il seguente yml: +The association can be modelled with the following yml: ```yml associations: @@ -128,7 +128,7 @@ associations: navigation: Customer ``` -Che genererà la seguente configurazione: +This generates the following configuration: ```cs public class MyModelConfiguration : ModelConfiguration @@ -143,9 +143,9 @@ public class MyModelConfiguration : ModelConfiguration } ``` -### Configurazione associazione uno-a-molti, intra-aggregato +### Configuring a one-to-many, intra-aggregate association -Supponiamo di avere le entità `Cart` e `Product`, in relazione uno-a-molti e facenti parte dello stesso aggregato, con `Cart` come Aggregate Root. +Suppose we have the entities `Cart` and `Product`, in a one-to-many relationship and part of the same aggregate, with `Cart` as the Aggregate Root. ```cs public class Cart @@ -161,7 +161,7 @@ public class Product } ``` -La relazione è modellabile con il seguente yml: +The association can be modelled with the following yml: ```yml associations: @@ -175,7 +175,7 @@ associations: navigation: Cart ``` -Che genererà la seguente configurazione: +This generates the following configuration: ```cs public class MyModelConfiguration : ModelConfiguration @@ -190,9 +190,9 @@ public class MyModelConfiguration : ModelConfiguration } ``` -### Configurazione associazione uno-a-molti, inter-aggregato +### Configuring a one-to-many, inter-aggregate association -Supponiamo di avere le entità `Cart` e `Product`, in relazione uno-a-molti e non facenti parte dello stesso aggregato. +Suppose we have the entities `Cart` and `Product`, in a one-to-many relationship and not part of the same aggregate. ```cs public class Cart @@ -208,7 +208,7 @@ public class Product } ``` -La relazione è modellabile con il seguente yml: +The association can be modelled with the following yml: ```yml associations: @@ -222,7 +222,7 @@ associations: navigation: Cart ``` -Che genererà la seguente configurazione: +This generates the following configuration: ```cs public class MyModelConfiguration : ModelConfiguration @@ -237,9 +237,9 @@ public class MyModelConfiguration : ModelConfiguration } ``` -### Configurazione associazione molti-a-molti +### Configuring a many-to-many association -Supponiamo di avere le entità `Student` e `Teacher`, in relazione molti-a-molti. +Suppose we have the entities `Student` and `Teacher`, in a many-to-many relationship. ```cs public class Student @@ -255,7 +255,7 @@ public class Teacher } ``` -La relazione è modellabile con il seguente yml: +The association can be modelled with the following yml: ```yml associations: @@ -269,7 +269,7 @@ associations: navigation: Students ``` -Che genererà la seguente configurazione: +This generates the following configuration: ```cs public class MyModelConfiguration : ModelConfiguration diff --git a/docs/dataaccesslayer.md b/docs/dataaccesslayer.md index 035c69c..f7e2842 100644 --- a/docs/dataaccesslayer.md +++ b/docs/dataaccesslayer.md @@ -1,12 +1,12 @@ # Data Access Layer -Il Data Access Layer (DAL) la parte di un'applicazione web che si occupa di gestire l'accesso ai dati. Il suo compito principale è quello di fornire un'interfaccia tra il codice applicativo e il database, nascondendo la complessità di quest'ultimo e della comunicazione con lo stesso. Solitamente, il DAL fornisce una serie di metodi per creare, leggere, aggiornare e cancellare i dati. In questo modo, il resto del codice dell'applicazione non deve preoccuparsi dei dettagli tecnici del database. +The Data Access Layer (DAL) is the part of a web application that manages access to data. Its main job is to provide an interface between the application code and the database, hiding the complexity of the database and of the communication with it. The DAL usually provides a set of methods to create, read, update and delete data. This way, the rest of the application code does not have to deal with the technical details of the database. -## Il pattern Repository +## The Repository pattern -I repository sono componenti che incapsulano la logica di accesso ai dati. Centralizzando tali funzionalità, è possibile raggiungere una manutenibilità maggiore del codice, insieme al disaccoppiamento dell'infrastruttura e/o della tecnologia usata per accedere al database (ORM) dal resto dell'applicazione. +Repositories are components that encapsulate the data access logic. Centralizing this functionality makes the code easier to maintain and decouples the infrastructure and/or the technology used to access the database (the ORM) from the rest of the application. -La libreria CodeArchitects.Platform.Data definisce l'interfaccia generica `IRepository`, che modella il pattern appena descritto. +The CodeArchitects.Platform.Data library defines the generic `IRepository` interface, which models the pattern just described. ```c# public interface IRepository @@ -23,18 +23,18 @@ public interface IRepository } ``` -In cui `TEntity` rappresenta il tipo di entità che il repository gestisce e `TKey` il tipo della chiave primaria dell'entità. -Questa interfaccia definisce le operazioni di accesso ai dati più comuni; in particolare: +Here `TEntity` is the type of entity the repository manages and `TKey` is the type of the entity's primary key. +This interface defines the most common data access operations; in particular: -- `FindAsync` esegue una ricerca dell'entità per chiave primaria. -- `InsertAsync` inserisce nel database una nuova entità. -- `UpdateAsync` aggiorna i valori di un'entità esistente. -- `UpsertAsync` se l'entità già esiste nel database, aggiorna i suoi dati, altrimenti la inserisce. -- `RemoveAsync` rimuove un'entità dal database. +- `FindAsync` looks up the entity by primary key. +- `InsertAsync` inserts a new entity into the database. +- `UpdateAsync` updates the values of an existing entity. +- `UpsertAsync` updates the entity's data if it already exists in the database, and inserts it otherwise. +- `RemoveAsync` removes an entity from the database. -Sebbene questi siano le più comuni (CRUD), molto spesso è necessario eseguire delle operazioni specializzate (sia in lettura che scrittura) per un particolare tipo di entità. Per questo motivo, questa interfaccia è pensata per essere estesa da interfacce specializzate per singola entità che, oltre a tipizzare l'interfaccia base, definiscono metodi che rappresentano le suddette operazioni specializzate, in modo tale da centralizzarle e isolarle dal resto della logica applicativa, oltre a consolidarle semanticamente. Questo approccio prende il nome di pattern Repository generico. +Although these are the most common operations (CRUD), you very often need specialized operations (both reads and writes) for a particular entity type. For this reason, this interface is meant to be extended by specialized per-entity interfaces that, besides closing the generic types of the base interface, define methods representing those specialized operations, so that they are centralized, isolated from the rest of the application logic and grouped by meaning. This approach is known as the generic Repository pattern. -Ad esempio, consideriamo un'entità chiamata `Product`, la cui chiave primaria è di tipo `Guid`. +For example, consider an entity called `Product`, whose primary key is of type `Guid`. ```c# public class Product @@ -45,7 +45,7 @@ public class Product } ``` -Definiamo un'interfaccia specializzata per `Product` che estende la generica `IRepository` e che definisce un metodo per recuperare i prodotti più venduti. +We define a specialized interface for `Product` that extends the generic `IRepository` and defines a method to retrieve the best-selling products. ```c# using CodeArchitects.Platform.Data; @@ -56,25 +56,25 @@ public interface IProductRepository : IRepository } ``` -Il metodo `GetTopSellingProducts` recupera gli N (parametro `count`) prodotti più venduti; altri metodi specializzati potranno essere definiti allo stesso modo e le parti dell'applicazione che utilizzano `IProductRepository` avranno accesso a questi metodi specializzati attraverso l'istanza del repository, oltre ai metodi definiti dall'interfaccia base generica. +The `GetTopSellingProducts` method retrieves the top N (the `count` parameter) best-selling products; other specialized methods can be defined in the same way, and the parts of the application that use `IProductRepository` can access these specialized methods through the repository instance, in addition to the methods defined by the generic base interface. -## Implementazione del pattern Repository +## Implementing the Repository pattern -Dall'interfaccia dei repository non si evince né il provider di database, né l'ORM utilizzato per accedervi, in quanto questi, essendo dettagli implementativi, saranno utilizzati dalle classi che forniranno l'implementazione dei metodi. +The repository interface reveals neither the database provider nor the ORM used to access it: these are implementation details, used by the classes that implement the methods. -Al momento, sono disponibili tre implementazioni del repository (e del resto del DAL): +Three implementations of the repository (and of the rest of the DAL) are currently available: -- CodeArchitects.Platform.Data.EntityFrameworkCore: implementazione del DAL che utilizza Entity Framework Core 7. -- CodeArchitects.Platform.Data.AdoNet: implementazione del DAL che utilizza direttamente ADO.NET, da utilizzare tipicamente in congiunzione con Dapper. -- CodeArchitects.Platform.Data.MongoDB: implementazione del DAL per MongoDB, basata sul driver ufficiale MongoDB .NET (vedi [DAL con MongoDB](mongodb.md)). +- CodeArchitects.Platform.Data.EntityFrameworkCore: DAL implementation based on Entity Framework Core 7. +- CodeArchitects.Platform.Data.AdoNet: DAL implementation that uses ADO.NET directly, typically used together with Dapper. +- CodeArchitects.Platform.Data.MongoDB: DAL implementation for MongoDB, based on the official MongoDB .NET driver (see [DAL with MongoDB](mongodb.md)). -> Nota: Entity Framework Core 7 richiede l'utilizzo di .NET 6 o superiore. Per progetti .NET Core 3.1 e .NET 5, esiste il pacchetto CodeArchitects.Platform.Data.EntityFrameworkCore che utilizza Entity Framework Core 5 ed è compatibile con .NET Core 3.1 e versioni superiori. +> Note: Entity Framework Core 7 requires .NET 6 or later. For .NET Core 3.1 and .NET 5 projects, there is a CodeArchitects.Platform.Data.EntityFrameworkCore package that uses Entity Framework Core 5 and is compatible with .NET Core 3.1 and later. -Queste librerie forniscono l'implementazione di `IRepository` che potrà essere utilizzata come classe base per l'implementazione dei repository specializzati. Ad esempio, nel caso di `IProductRepository`, si ha il seguente schema: +These libraries provide the implementation of `IRepository`, which can be used as the base class for specialized repositories. For example, for `IProductRepository` the structure is as follows: ![Generic repository](images/genericrepository.png) -Vediamo come implementare `IProductRepository` utilizzando Entity Framework Core, con `EFCoreRepository` come classe base. +Let's see how to implement `IProductRepository` with Entity Framework Core, using `EFCoreRepository` as the base class. ```c# using CodeArchitects.Platform.Data.EntityFrameworkCore; @@ -96,9 +96,9 @@ public class ProductRepository : EFCoreRepository, IProductReposi } ``` -La classe `EFCoreRepository` espone una proprietà protetta chiamata `Entities`, che corrisponde al `DbSet` (`DbSet` in questo caso). Attraverso il `DbSet` si potranno effettuare tutte le operazioni sull'entità `Product` che Entity Framework Core permette. Inoltre, `EFCoreRepository` espone anche l'istanza del `DbContext` utilizzato dall'applicazione tramite la proprietà protetta `DbContext`. Per ultimo, il costruttore richiede un'istanza dell'interfaccia `IDataContext` che, oltre a contenere il riferimento al `DbContext` dell'applicazione, fornisce l'implementazione dei metodi del repository generico, ma tipizzabili per qualsiasi tipo di entità (ad esempio `FindAsync`), in modo tale da poter aver accesso ai metodi base degli altri repository, senza dover iniettare il repository base; ciò può essere utile quando un repository espone i metodi di accesso ad un'intero aggregato, anziché ad una singola entità. Questo oggetto è disponibile tramite la proprietà protetta `Context` della classe base. +The `EFCoreRepository` class exposes a protected property called `Entities`, which is the `DbSet` (`DbSet` in this case). Through the `DbSet` you can perform every operation on the `Product` entity that Entity Framework Core allows. `EFCoreRepository` also exposes the `DbContext` instance used by the application through the protected `DbContext` property. Finally, the constructor requires an instance of the `IDataContext` interface which, besides holding the reference to the application's `DbContext`, implements the generic repository methods in a form that can be typed for any entity (for example `FindAsync`), so you can use the base methods of other repositories without injecting the base repository; this is useful when a repository exposes access methods for a whole aggregate rather than a single entity. This object is available through the protected `Context` property of the base class. -L'implementazione utilizzando ADO.NET (e Dapper) è strutturalmente molto simile, ma la query andrà scritta a mano o con qualche altro strumento di generazione di codice SQL (ad esempio SqlKata). +The implementation with ADO.NET (and Dapper) is structurally very similar, but the query has to be written by hand or with some other SQL generation tool (for example SqlKata). ```c# using CodeArchitects.Platform.Data.AdoNet; @@ -118,13 +118,13 @@ public class ProductRepository : AdoNetRepository, IProductReposi } ``` -In questo caso, la classe `AdoNetRepository` espone una proprietà protetta chiamata `Connection` (di tipo `IDbConnection`) con cui è possibile effettuare delle query. Anche in questo caso, si ha un'interfaccia `IDataContext` che, similmente all'implementazione con Entity Framework Core, espone i metodi CRUD generici e l'istanza della connessione. +In this case, the `AdoNetRepository` class exposes a protected property called `Connection` (of type `IDbConnection`) used to run queries. Here too there is an `IDataContext` interface that, as in the Entity Framework Core implementation, exposes the generic CRUD methods and the connection instance. -## Il pattern Unit of Work +## The Unit of Work pattern -Il pattern Unit of Work è un pattern utilizzato per raggruppare delle modifiche effettuate al database in una singola operazione (l'unità di lavoro), ed è comunemente utilizzato in congiunzione con il pattern Repository. +The Unit of Work pattern groups changes made to the database into a single operation (the unit of work), and is commonly used together with the Repository pattern. -La libreria CodeArchitects.Platform.Data definisce l'interfaccia `IUnitOfWork`: +The CodeArchitects.Platform.Data library defines the `IUnitOfWork` interface: ```c# public interface IUnitOfWork : IAsyncDisposable @@ -133,9 +133,9 @@ public interface IUnitOfWork : IAsyncDisposable } ``` -L'unico metodo esposto da quest'interfaccia è il metodo `SaveAsync`, che serve a finalizare tutti i cambiamenti avvenuti all'interno dell'unità di lavoro. +The only method exposed by this interface is `SaveAsync`, which commits all the changes made within the unit of work. -Esistono due modi per creare un'unità di lavoro. Il primo è quello di creare puntualmente l'istanza di `IUnitOfWork` utilizzando l'interfaccia `IUnitOfWorkManager`: +There are two ways to create a unit of work. The first is to create the `IUnitOfWork` instance explicitly with the `IUnitOfWorkManager` interface: ```c# public interface IUnitOfWorkManager @@ -145,7 +145,7 @@ public interface IUnitOfWorkManager } ``` -Questa interfaccia definisce il metodo `Begin` che crea una nuova unità di lavoro; considerando che `IUnitOfWork` implementa `IAsyncDisposable`, questo metodo va utilizzato insieme al costrutto `await using`. +This interface defines the `Begin` method, which creates a new unit of work; since `IUnitOfWork` implements `IAsyncDisposable`, this method should be used with an `await using` statement. ```c# public class ShopService @@ -177,9 +177,9 @@ public class ShopService } ``` -In questo caso, all'interno dell'unità di lavoro (rappresentata dallo scope definito dal costrutto `await using`) avvengono operazioni su più entità (sul carrello e sui prodotti che esso contiene), che vengono eseguite tutte insieme nel momento in cui viene chiamato il metodo `SaveAsync`. In questo modo, tutte le operazioni vanno a compimento insieme oppure falliscono insieme, impedendo che il database si trovi in uno stato incoerente (ad esempio, carrello segnato come completato ma il numero di vendite dei prodotti che esso contiene non è stato incrementato). +Here, within the unit of work (the scope defined by the `await using` statement), operations on several entities take place (on the cart and on the products it contains), and they are all executed together when `SaveAsync` is called. This way, all the operations either complete together or fail together, which prevents the database from being left in an inconsistent state (for example, a cart marked as completed while the sale count of its products has not been incremented). -Di default, è necessario chiamare il metodo `SaveAsync` quando si vuole effettuare il commit dei cambiamenti, ma esiste un'overload del metodo `Begin` che accetta un parametro `autoSave` di tipo `boolean` che, se impostato a `true`, effettua automaticamente l'invocazione a `SaveAsync` nel momento in cui lo scope definito dalla chiamata al metodo `Begin` termina. +By default, you must call `SaveAsync` when you want to commit the changes, but there is an overload of `Begin` that accepts a `boolean` `autoSave` parameter which, when set to `true`, automatically calls `SaveAsync` when the scope opened by `Begin` ends. ```c# await using (IUnitOfWork uow = _uowManager.Begin(autoSave: true, cancellationToken)) @@ -192,9 +192,9 @@ await using (IUnitOfWork uow = _uowManager.Begin(autoSave: true, cancellationTok } ``` -> Nota: è possibile invocare il metodo `SaveAsync` più volte all'interno dello stesso scope. +> Note: `SaveAsync` can be called more than once within the same scope. -Il secondo modo per utilizzare l'interfaccia `IUnitOfWork` è quello di richiederne direttamente un'istanza tramite Dependency Injection. In questo caso, verrà creata una singola unità di lavoro per l'intero contesto di esecuzione (in altre parole, `IUnitOfWork` è registrato con `ServiceLifetime.Scoped`). +The second way to use the `IUnitOfWork` interface is to request an instance directly through Dependency Injection. In this case, a single unit of work is created for the whole execution context (in other words, `IUnitOfWork` is registered with `ServiceLifetime.Scoped`). ```c# public class ShopService @@ -223,53 +223,53 @@ public class ShopService } ``` -## Associazioni e aggregati +## Associations and aggregates -Nell'esempio precedente, le entità `Cart` e `Product` venivano gestite da due repository diversi (`ICartRepository` e `IProductRepository`, rispettivamente). Il Domain Driven Design (DDD) introduce il concetto di aggregato, ovvero di insieme di entità relazionate tra loro e trattate come una sola, in cui esiste un'entità, chiamata Aggregate Root, dalla quale accedere all'intero aggregato. +In the previous example, the `Cart` and `Product` entities were managed by two different repositories (`ICartRepository` and `IProductRepository`, respectively). Domain Driven Design (DDD) introduces the concept of an aggregate: a set of related entities treated as a single unit, in which one entity, called the Aggregate Root, gives access to the whole aggregate. -In questo caso, si può pensare di costruire un aggregato formato da `Cart` e `Product` legati da una relazione 1-a-molti (un carrello contiene più prodotti), in cui `Cart` è l'Aggregate Root. Per far ciò, oltre a fare in modo che ci sia almeno una proprietà di navigazione tra le due entità (ad esempio, `Cart.Products` o `Product.Cart`), è necessario istruire il Data Access Layer in modo tale che tenga conto di queste relazioni quando vengono effettuate delle letture o delle scritture. +In this case, we can build an aggregate made of `Cart` and `Product` linked by a one-to-many relationship (a cart contains many products), with `Cart` as the Aggregate Root. To do so, besides having at least one navigation property between the two entities (for example, `Cart.Products` or `Product.Cart`), the Data Access Layer must be told to take these relationships into account when reading and writing. -Ovviamente, se due entità sono relazionate (e quindi c'è almeno una proprietà di navigazione) non è detto che facciano parte dello stesso aggregato. In generale, le associazioni si distinguono in due tipi. +Of course, two related entities (and therefore with at least one navigation property) are not necessarily part of the same aggregate. In general, there are two kinds of association. -1. Intra-aggregato: quando le due entità fanno parte dello stesso aggregato e la seconda è gerarchicamente dipendente dalla prima -2. Inter-aggregato: quando le due entità non fanno parte dello stesso aggregato oppure le due entità sono gerarchicamente indipendenti +1. Intra-aggregate: the two entities are part of the same aggregate and the second one depends hierarchically on the first +2. Inter-aggregate: the two entities are not part of the same aggregate, or they are hierarchically independent -Per quanto riguarda le molteplicità, sono supportati i seguenti tipi di relazione: +As for multiplicity, the following relationship types are supported: -- Uno-a-uno: sia intra-aggregato che inter-aggregato -- Uno-a-molti: sia intra-aggregato che inter-aggregato -- Molti-a-molti: solo inter-aggregato, in quanto sempre gerarchicamente indipendenti +- One-to-one: both intra-aggregate and inter-aggregate +- One-to-many: both intra-aggregate and inter-aggregate +- Many-to-many: inter-aggregate only, since the entities are always hierarchically independent -### Lettura +### Reading -Per quanto riguarda la lettura dal database, non c'è distinzione tra i due tipi di associazione, in quanto il metodo `FindAsync` presenta due overload: il primo accetta solo l'id dell'entità e non legge le varie proprietà di navigazione, mentre il secondo accetta una funzione che specifica, in maniera fluente, le proprietà di navigazione da includere nella query. +For reads, there is no difference between the two kinds of association, because the `FindAsync` method has two overloads: the first accepts only the entity id and does not load the navigation properties, while the second accepts a function that specifies, fluently, the navigation properties to include in the query. ```cs ICartRepository repo = ...; -Cart cart1 = await repo.FindAsync(id1); // cart1.Products è null +Cart cart1 = await repo.FindAsync(id1); // cart1.Products is null Cart cart2 = await repo.FindAsync(id2, _ => _ - .Include(cart => cart.Products)); // cart2.Products non è null + .Include(cart => cart.Products)); // cart2.Products is not null ``` -Questo comportamento è identico, che l'associazione sia intra-aggregato o inter-aggregato. +This behavior is the same whether the association is intra-aggregate or inter-aggregate. -### Scrittura +### Writing -Per quanto riguarda le scritture, InsertAsync e UpdateAsync hanno un comportamento diverso in base al tipo di associazione. +For writes, InsertAsync and UpdateAsync behave differently depending on the kind of association. -Tenendo in considerazione il fatto che l'Aggregate Root è l'oggetto dal quale si accede a tutto l'aggregato, l'operazione di Insert fatta sull'Aggregate Root provocherà la Insert di tutte le proprietà e sub-proprietà di navigazione che rientrano nell'aggregato (intra-aggregate). Invece, la Insert non verrà provocata per le proprietà di navigazione che non rientrano nell'aggregato (inter-aggregate); queste entità saranno considerate già esistenti e verrà creata solo la rappresentazione della relazione inter-aggregato a livello di database. Questo significa che verranno valorizzate eventuali foreign keys e record nelle tabelle associative (junction tables, in caso di relazioni molti-a-molti). L'operazione di Update funziona esattamente allo stesso modo. Invece il funzionamento dell'operazione di Delete è uguale a quello dell'operazione di Find; ciononostante, se si vuole predisporre l'eliminazione a cascata di tutte le entità dell'aggregato quando si elimina l'Aggregate Root, basterà impostare il delete behavior a livello di database. +Since the Aggregate Root is the object through which the whole aggregate is accessed, an Insert on the Aggregate Root inserts all the navigation properties and sub-properties that belong to the aggregate (intra-aggregate). Navigation properties that do not belong to the aggregate (inter-aggregate) are not inserted; those entities are assumed to already exist, and only the database representation of the inter-aggregate relationship is created. This means that foreign keys and rows in the association tables (junction tables, for many-to-many relationships) are filled in. Update works exactly the same way. Delete, on the other hand, works like Find; nevertheless, if you want all the entities of the aggregate to be deleted in cascade when the Aggregate Root is deleted, set the delete behavior at the database level. -### Configurazione +### Configuration -Per la configurazione delle associazioni, vedere le rispettive sezioni della documentazione dei due provider: +To configure associations, see the corresponding sections of the documentation of the two providers: -- [Entity Framework Core](efcore.md#associazioni) -- [ADO.NET](adonet.md#associazioni) +- [Entity Framework Core](efcore.md#associations) +- [ADO.NET](adonet.md#associations) ## Seeding -Per effettuare il seeding del database è possibile estendere la classe base `DataSeed`: +To seed the database, extend the `DataSeed` base class: ```cs public class ApplicationDataSeed : DataSeed @@ -288,11 +288,11 @@ public class ApplicationDataSeed : DataSeed ## Mapped repository -Uno dei compiti principali di un ORM è quello di semplificare lo sviluppo del proprio modello delle entità, permettendo di accedere al database utilizzando un modello dati che è progettato secondo le regole di business. Per questo motivo, la struttura delle classi che vengono persistite non rispecchia perfettamente la struttura tabellare propria di un database. L'ORM si occupa di colmare questa differenza attraverso opportuni mapping. +One of the main jobs of an ORM is to simplify the development of the entity model, letting you access the database through a data model designed around the business rules. For this reason, the structure of the persisted classes does not exactly match the tabular structure of a database. The ORM bridges this gap through suitable mappings. -Qualora, però, si voglia avere il controllo completo sul processo di mapping, oppure il mapping sia troppo complicato per le capacità di un ORM, è possibile utilizzare la classe base `MappedRepository` (nelle sue due varianti `EFCoreMappedRepository` e `AdoNetMappedRepository`), che prevede la definizione di un livello di mapping personalizzato tra classi che modellano esattamente la struttura del database ed entità di business. Si dovranno implementare i due metodi astratti `EntityToTable` e `TableToEntity` in cui verrà effettuato il mapping. +If, however, you want full control over the mapping process, or the mapping is too complex for what an ORM can do, you can use the `MappedRepository` base class (in its two variants `EFCoreMappedRepository` and `AdoNetMappedRepository`), which lets you define a custom mapping layer between classes that model the database structure exactly and the business entities. You implement the two abstract methods `EntityToTable` and `TableToEntity`, where the mapping takes place. -Ad esempio, considerando l'entità `Product` e il suo equivalente lato database `ProductTable` e il provider ADO.NET, il repository verrebbe modificato nel seguente modo: +For example, given the `Product` entity, its database-side counterpart `ProductTable` and the ADO.NET provider, the repository changes as follows: ```cs using CodeArchitects.Platform.Data.AdoNet; @@ -316,13 +316,13 @@ public class ProductRepository : MappedAdoNetRepository Nota: l'overload del metodo FindAsync che permette di specificare le proprietà di navigazione da includere tramite lambda, non è supportato dai mapped repository. Ciononostante, questo metodo (come tutti i metodi delle classi base dei repository) sono virtuali ed è possibile definirne un override secondo necessità. +> Note: the FindAsync overload that specifies the navigation properties to include through a lambda is not supported by mapped repositories. Nevertheless, this method (like all the methods of the repository base classes) is virtual, so you can override it as needed. ## How-to -### Impostare un livello di persistenza specificando l'ORM +### Set up a persistence layer by specifying the ORM -Analizziamo lo yml di un microservizio di nome Store. +Let's look at the yml of a microservice called Store. ```yml name: Store @@ -373,17 +373,17 @@ associations: navigation: Category ``` -La sezione `components` contiene un elemento di tipo `database` con provider `sqlserver`, e questo genera un container Docker con SQLServer per lo sviluppo locale nel file `docker-compose`, oltre ad istruire il livello di persistenza ad utilizzare SQLServer. +The `components` section contains an element of type `database` with provider `sqlserver`; this generates a SQL Server Docker container for local development in the `docker-compose` file, and also tells the persistence layer to use SQL Server. -La sezione `data` contiene il campo `orm` che specifica che si vuole utilizzare Entity Framework Core come ORM. Questo installerà i pacchetti di `CodeArchitects.Platform.Data.EntityFrameworkCore.*` e genererà i repository utilizzando la rispettiva classe base `EFCoreRepository`. +The `data` section contains the `orm` field, which specifies Entity Framework Core as the ORM. This installs the `CodeArchitects.Platform.Data.EntityFrameworkCore.*` packages and generates the repositories with the corresponding `EFCoreRepository` base class. -La sezione `entities` definisce le entità, e per `Product` verrà generato anche un repository. +The `entities` section defines the entities, and a repository is also generated for `Product`. -La sezione `associations` definisce l'associazione uno-a-molti tra l'entità `Category` e l'entità `Product`. +The `associations` section defines the one-to-many association between the `Category` entity and the `Product` entity. -### Utilizzare database multipli +### Use multiple databases -Per utilizzare database multipli, è sufficiente indicare più components di tipo `database`. +To use multiple databases, declare several components of type `database`. ```yml name: Store @@ -399,9 +399,9 @@ components: name: store_postgres ``` -Questo farà in modo che venga generato il codice che servirà ad abilitare la possibilità di utilizzare un database o l'altro secondo delle particolari condizioni. +This generates the code needed to use one database or the other depending on specific conditions. -In particolare, in `appsettings.Development.json` verranno generate le stringhe di connessione ai database e una proprietà chiamata `Provider` che servirà a selezionare il database da utilizzare: +In particular, `appsettings.Development.json` gets the database connection strings and a property called `Provider` that selects the database to use: ```json "Provider": "sqlserver", @@ -411,13 +411,13 @@ In particolare, in `appsettings.Development.json` verranno generate le stringhe } ``` -La proprietà `Provider` potrà assumere valori diversi a seconda dell'ambiente, in modo tale da poter selezionare il database giusto in ogni circostanza. +The `Provider` property can take different values in each environment, so the right database is selected in every situation. -In modalità split (`generationMode: split`), utilizzando Entity Framework Core, verranno creati anche dei progetti che conterranno le migrazioni di ciascun provider. Questi progetti saranno nominati secondo la convenzione {Prefisso}.{NomeSoluzione}.Data.{NomeSoluzione}_{NomeProvider} (ad esempio Ca.ShoppingCart.Store.Data.Store_postgres). Per effettuare le migrazioni, impostare il progetto eseguibile come progetto di startup e includere i riferimenti ai progetti in cui si vogliono effettuare le migrazioni. Selezionare poi questi come progetto di default nel package manager quando si lancia il comando Add-Migration. +In split mode (`generationMode: split`), with Entity Framework Core, projects holding the migrations of each provider are also created. These projects are named following the convention {Prefix}.{SolutionName}.Data.{SolutionName}_{ProviderName} (for example Ca.ShoppingCart.Store.Data.Store_postgres). To add migrations, set the executable project as the startup project and reference the projects where you want the migrations. Then select one of them as the default project in the Package Manager Console when you run the Add-Migration command. -### Creare table and domain entities +### Create table and domain entities -Ci sono due modi per utilizzare la feature delle table entities. Il primo è quello di creare separatamente l'entità di dominio e la rispettiva table. +There are two ways to use the table entities feature. The first is to create the domain entity and its table separately. ```yml entities: @@ -444,7 +444,7 @@ entities: description: Price ``` -Avendo specificato `useMappedRepository`, verrà creato un repository che eredita da (supponendo di utilizzare EFCore) `EFCoreMappedRepository`. Bisognerà definire gli override dei metodi di mapping; in questo esempio, il mapping viene effettuato a mano, ma si potrebbe utilizzare AutoMapper, richiedendo un'istanza di `IMapper` tramite Dependency Injection. +Since `useMappedRepository` is specified, a repository is created that inherits from (assuming EFCore is used) `EFCoreMappedRepository`. You need to override the mapping methods; in this example the mapping is written by hand, but you could use AutoMapper by requesting an `IMapper` instance through Dependency Injection. ```cs using CodeArchitects.Platform.Data.EntityFrameworkCore; @@ -478,7 +478,7 @@ public class ProductRepository : EFCoreMappedRepository(...); ``` -è necessario configurare l'intero DAL tramite il seguente metodo: +configure the whole DAL with the following method: ```cs services.AddData(); @@ -25,13 +25,13 @@ services.AddData(); ## Seeding -Per effettuare il seeding del database, è sufficiente utilizzare l'overload di `AddData` che consente di specificare ulteriori opzioni e successivamente utilizzare il metodo `UseSeed`: +To seed the database, use the `AddData` overload that accepts additional options and then call the `UseSeed` method: ```cs services.AddData(options => options.UseSeed()); ``` -per poi effettuare il seeding tramite il metodo di estensione `Seed`, tipicamente dopo la chiamata al metodo `Migrate` (o `EnsureCreated` se non si utilizzano le migrazioni). +then run the seeding with the `Seed` extension method, typically after the call to `Migrate` (or `EnsureCreated` if you do not use migrations). ```cs using (IServiceScope scope = app.ApplicationServices.CreateScope()) @@ -44,28 +44,28 @@ using (IServiceScope scope = app.ApplicationServices.CreateScope()) ## CAEP extensions -L'estensione CAEP per EFCore permette di abilitare una serie di funzionalità, tra cui: +The CAEP extension for EFCore enables a set of features, including: - Multitenancy - Soft delete -Per utilizzare questa estensione, utilizzare il metodo di estensione `UseCaep` del `DbContextOptionsBuilder` all'interno della chiamata ad `AddDbContext` (o all'interno del metodo `OnConfiguring` di `DbContext`). +To use this extension, call the `UseCaep` extension method of `DbContextOptionsBuilder` inside the call to `AddDbContext` (or inside the `OnConfiguring` method of the `DbContext`). ```cs services.AddDbContext(options => options .UseSqlServer(...) - .UseCaep()); // Da inserire dopo la chiamata all'estensione del provider (UseSqlServer in questo caso) + .UseCaep()); // Must come after the call to the provider extension (UseSqlServer in this case) ``` ## Multitenancy -Il multi-tenancy è un concetto utilizzato per descrivere la capacità di un'applicazione web di fornire servizi a più clienti (noti come "tenant") all'interno di un'unica istanza di software. Ciò significa che un'unica versione dell'applicazione è in grado di gestire dati e configurazioni per più clienti, utilizzando meccanismi di isolamento per garantire che i dati e le configurazioni dei singoli tenant non si interfaccino tra loro. +Multi-tenancy describes the ability of a web application to serve multiple customers (known as "tenants") from a single software instance. A single version of the application manages data and configuration for many customers, using isolation mechanisms to ensure that the data and configuration of each tenant do not interfere with one another. -Il multi-tenancy può essere implementato in diversi modi, a seconda delle esigenze dell'applicazione. Ad esempio, può essere realizzato utilizzando database separati per ogni tenant (separate database), oppure utilizzando schemi separati all'interno dello stesso database (shared database, separate schema) per isolare i dati dei tenant. +Multi-tenancy can be implemented in several ways, depending on the needs of the application. For example, it can use a separate database for each tenant (separate database), or separate schemas within the same database (shared database, separate schema) to isolate tenant data. -Quando non è possibile adottare nessuna delle due soluzioni, è necessario utilizzare meccanismi di isolamento a livello di codice per garantire che le azioni di un tenant non interferiscano con gli altri. +When neither solution is possible, code-level isolation mechanisms are needed to ensure that the actions of one tenant do not interfere with the others. -Per abilitare la separazione a livello di codice, si può usare l'estensione `UseMultitenancy` all'interno di `UseCaep`: +To enable code-level separation, use the `UseMultitenancy` extension inside `UseCaep`: ```cs services.AddDbContext(options => options @@ -73,7 +73,7 @@ services.AddDbContext(options => options .UseCaep(caep => caep.UseMultitenancy())); ``` -Per indicare che un'entità è multi-tenant è sufficiente utilizzare il metodo di estensione `IsMultiTenant` all'interno del metodo `OnModelCreating`. Supponendo di avere la seguente entità: +To mark an entity as multi-tenant, use the `IsMultiTenant` extension method inside the `OnModelCreating` method. Given the following entity: ```cs public class MyEntity @@ -84,7 +84,7 @@ public class MyEntity } ``` -Si avrebbe: +you would write: ```cs modelBuilder.Entity(entity => @@ -93,15 +93,15 @@ modelBuilder.Entity(entity => }); ``` -Una volta fatto ciò, le query effettuate su questa entità saranno automaticamente filtrate secondo il tenant dell'utente corrente. +From then on, queries on this entity are automatically filtered by the tenant of the current user. -> Nota: Di default, è necessario utilizzare l'interfaccia IIdentityProfile per recuperare il tenant id dai claims dell'utente corrente. +> Note: By default, the IIdentityProfile interface must be used to retrieve the tenant id from the claims of the current user. ## Soft delete -Il soft delete è un meccanismo che consente di eliminare logicamente un record dal database senza eliminarlo fisicamente. Invece di eliminare fisicamente il record dal database, viene impostato un indicatore che segnala che il record è stato eliminato. Questo meccanismo consente di mantenere una traccia dei record eliminati e di poterli eventualmente ripristinare in futuro. Il soft delete può essere implementato utilizzando una colonna di stato (ad esempio, una colonna `IsDeleted` con valori `true` e `false`). +Soft delete is a mechanism that removes a record from the database logically, without deleting it physically. Instead of physically deleting the record, a flag is set to indicate that the record has been deleted. This keeps track of deleted records and makes it possible to restore them later. Soft delete can be implemented with a status column (for example, an `IsDeleted` column with values `true` and `false`). -Per abilitare il meccanismo di soft delete, utilizzare il metodo `UseSoftDelete` all'interno di `UseCaep`: +To enable soft delete, use the `UseSoftDelete` method inside `UseCaep`: ```cs services.AddDbContext(options => options @@ -109,7 +109,7 @@ services.AddDbContext(options => options .UseCaep(caep => caep.UseSoftDelete())); ``` -Nel metodo `OnModelCreating`, utilizzare il metodo `IsSoftDelete` per marcare la proprietà da utilizzare come colonna di stato: +In the `OnModelCreating` method, use the `IsSoftDelete` method to mark the property to use as the status column: ```cs modelBuilder.Entity(entity => @@ -118,13 +118,13 @@ modelBuilder.Entity(entity => }); ``` -## Associazioni +## Associations -I tipi di associazione vengono definite attraverso i metodi di estensione `Aggregate()` e `Associate()`, da usare in congiunzione con le fluent API di configurazione di EFCore. +Association types are defined with the `Aggregate()` and `Associate()` extension methods, used together with the EFCore fluent configuration API. -### Configurazione associazione uno-a-uno, intra-aggregato +### Configuring a one-to-one, intra-aggregate association -Supponiamo di avere le entità `Customer` e `Address`, in relazione uno-a-uno e facenti parte dello stesso aggregato, con `Customer` come Aggregate Root. +Suppose we have the entities `Customer` and `Address`, in a one-to-one relationship and part of the same aggregate, with `Customer` as the Aggregate Root. ```cs public class Customer @@ -140,7 +140,7 @@ public class Address } ``` -La relazione è modellabile con il seguente yml: +The association can be modelled with the following yml: ```yml associations: @@ -154,7 +154,7 @@ associations: navigation: Customer ``` -Che genererà la seguente configurazione: +This generates the following configuration: ```cs public class MyDbContext : DbContext @@ -173,9 +173,9 @@ public class MyDbContext : DbContext } ``` -### Configurazione associazione uno-a-uno, inter-aggregato +### Configuring a one-to-one, inter-aggregate association -Supponiamo di avere le entità `Customer` e `Address`, in relazione uno-a-uno e non facenti parte dello stesso aggregato. +Suppose we have the entities `Customer` and `Address`, in a one-to-one relationship and not part of the same aggregate. ```cs public class Customer @@ -191,7 +191,7 @@ public class Address } ``` -La relazione è modellabile con il seguente yml: +The association can be modelled with the following yml: ```yml associations: @@ -205,7 +205,7 @@ associations: navigation: Customer ``` -Che genererà la seguente configurazione: +This generates the following configuration: ```cs public class MyDbContext : DbContext @@ -224,9 +224,9 @@ public class MyDbContext : DbContext } ``` -### Configurazione associazione uno-a-molti, intra-aggregato +### Configuring a one-to-many, intra-aggregate association -Supponiamo di avere le entità `Cart` e `Product`, in relazione uno-a-molti e facenti parte dello stesso aggregato, con `Cart` come Aggregate Root. +Suppose we have the entities `Cart` and `Product`, in a one-to-many relationship and part of the same aggregate, with `Cart` as the Aggregate Root. ```cs public class Cart @@ -242,7 +242,7 @@ public class Product } ``` -La relazione è modellabile con il seguente yml: +The association can be modelled with the following yml: ```yml associations: @@ -256,7 +256,7 @@ associations: navigation: Cart ``` -Che genererà la seguente configurazione: +This generates the following configuration: ```cs public class MyDbContext : DbContext @@ -274,9 +274,9 @@ public class MyDbContext : DbContext } ``` -### Configurazione associazione uno-a-molti, inter-aggregato +### Configuring a one-to-many, inter-aggregate association -Supponiamo di avere le entità `Cart` e `Product`, in relazione uno-a-molti e non facenti parte dello stesso aggregato. +Suppose we have the entities `Cart` and `Product`, in a one-to-many relationship and not part of the same aggregate. ```cs public class Cart @@ -292,7 +292,7 @@ public class Product } ``` -La relazione è modellabile con il seguente yml: +The association can be modelled with the following yml: ```yml associations: @@ -306,7 +306,7 @@ associations: navigation: Cart ``` -Che genererà la seguente configurazione: +This generates the following configuration: ```cs public class MyDbContext : DbContext @@ -324,9 +324,9 @@ public class MyDbContext : DbContext } ``` -### Configurazione associazione molti-a-molti +### Configuring a many-to-many association -Supponiamo di avere le entità `Student` e `Teacher`, in relazione molti-a-molti. +Suppose we have the entities `Student` and `Teacher`, in a many-to-many relationship. ```cs public class Student @@ -342,7 +342,7 @@ public class Teacher } ``` -La relazione è modellabile con il seguente yml: +The association can be modelled with the following yml: ```yml associations: @@ -356,7 +356,7 @@ associations: navigation: Students ``` -Che genererà la seguente configurazione: +This generates the following configuration: ```cs public class MyDbContext : DbContext diff --git a/docs/mongodb.md b/docs/mongodb.md index fc60734..8e3e327 100644 --- a/docs/mongodb.md +++ b/docs/mongodb.md @@ -144,7 +144,7 @@ public class CartItem // no [Collection], no Id: not an entity Embedded entities **do not have their own collection** and cannot be reached through a dedicated repository: an entity that needs a repository is, by definition, an aggregate root. -Writes follow the semantics already documented in the [DAL](dataaccesslayer.md#associazioni-e-aggregati): +Writes follow the semantics already documented in the [DAL](dataaccesslayer.md#associations-and-aggregates): `Insert` and `Update` on the aggregate root write the entire document, including embedded entities, while inter-aggregate entities are not written, only the reference is persisted. `Remove` deletes the document and its embedded entities; there is **no** cascade through references, because MongoDB @@ -156,7 +156,7 @@ and puts the collection name into the data. ## Unit of work and transactions The use of `IUnitOfWorkManager` and `IUnitOfWork` is identical to the other providers -([DAL](dataaccesslayer.md#il-pattern-unit-of-work)). There are two differences. +([DAL](dataaccesslayer.md#the-unit-of-work-pattern)). There are two differences. **Writes are deferred.** Within the UnitOfWork, operations accumulate and are applied on `SaveAsync` (or on `Dispose` with `autoSave: true`), all in **a single** MongoDB transaction. diff --git a/docs/net6.md b/docs/net6.md index d50e7d0..0bc8358 100644 --- a/docs/net6.md +++ b/docs/net6.md @@ -1,8 +1,8 @@ # .NET 6 -Il nuovo template che utilizza .NET 6 prevede alcune differenze con i template precedenti, legate all'introduzione dei top-level statements. In particolare, la classe Startup non è più necessaria, in quanto la configurazione dell'applicazione avviene direttamente in Program.cs. Ciononostante, il nuovo template definisce un file, chiamato App.cs che conterrà i metodi che configurano i vari servizi nel container di Dependency Injection, in maniera tale da identificare semanticamente i vari pezzi della configurazione e rendere pulito il codice all'interno di Program.cs. +The new template based on .NET 6 differs from the previous templates because of the introduction of top-level statements. In particular, the Startup class is no longer needed, since the application is configured directly in Program.cs. Nevertheless, the new template defines a file called App.cs that contains the methods that register the various services in the Dependency Injection container, so that each part of the configuration is identified by name and the code in Program.cs stays clean. -Per utilizzare .NET 6, impostare la versione 6 del framework .NET nello yml del microservizio. +To use .NET 6, set version 6 of the .NET framework in the microservice yml. ```yml target: @@ -10,7 +10,7 @@ target: version: 6 ``` -Un esempio di Program generato con il template .NET 6 è il seguente. +An example of a Program generated with the .NET 6 template follows. ```cs WebApplicationBuilder builder = WebApplication.CreateBuilder(args); @@ -48,4 +48,4 @@ app.UseEndpoints(endpoints => app.Run(); ``` -Mentre, le implementazioni dei metodi di estensione `AddCaepServices`, eccetera si troveranno nel file App.cs. +The implementations of the extension methods `AddCaepServices` and the others are in the App.cs file.