Modelul entitate–atribut–valoare (EAV) este un model de date pentru entități cu multe atribute posibile, dintre care doar o parte se aplică fiecărei entități. Stocarea este eficientă pentru această structură, similară unei matrice rare.
Mai multe informații: https://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model
Entitate–atribut–valoare
Entity Attribute Value, numit EAV în acest document, permite atribuirea dinamică de atribute și valori obiectelor din baza de date, fără schimbarea structurii acesteia. Fără EAV, o proprietate nouă necesită un câmp în clasă și o coloană în baza de date. Cu EAV, suportul pentru proprietăți dinamice este introdus la proiectare, iar caracteristicile pot fi definite ulterior de utilizatori, fără o actualizare a schemei bazei de date.
Atributele unei categorii nu pot fi atribuite entității dacă acea categorie nu îi este asociată.
Mai întâi se asociază categoria entității; apoi se pot utiliza atributele definite în acea categorie.
1 Valori ale atributelor entităților
Funcționalitatea utilizează trei clase generice:
- Categoria entității (EntityCategory<TEntity>), numită clasă în alte sisteme;
- Definiția atributului (AttrDef<TEntityCategory>);
- Valoarea atributului (EAV<TEntity, TEntityCategory>).
Parametrul generic reprezintă tipul entității extinse.
Suportul EAV se adaugă prin interfața IHasEntityAttributeValues<TEntity>. Aceasta cere două proprietăți: EAV, lista valorilor, și EAV_Category, lista categoriilor asociate:
public List<EAV<TEntity, EntityCategory<TEntity>>> EAV { get; set; }
public List<EntityCategory<TEntity>> EAV_Category { get; set; }
În DbContext se adaugă și trei proprietăți DbSet:
public DbSet<EntityCategory<TEntity>> TEntity_Category { get; set; }
public DbSet<AttrDef<EntityCategory<TEntity>>> TEntity_AttrDef { get; set; }
public DbSet<EAV<TEntity, EntityCategory<TEntity>>> TEntity_EAV { get; set; }
În baza de date sunt create și alte tabele, inclusiv tabele de legătură.
1.1 Categoria entității
Categoria grupează definiții de atribute asociate. Atributele care trebuie să fie opționale pentru anumite entități pot fi separate în categorii distincte.
Pentru proprietăți dinamice, asociați entității categoriile necesare. Această asociere permite utilizarea atributelor lor. De exemplu, pentru atributul „Culoare”, definiți categoria „Produs cu culoare”, adăugați atributul și asociați categoria produselor relevante.
1.1.1 Implementare
Categoria este o entitate Diamond Node, cu părinți și copii.
Categoria definește atributele disponibile entităților asociate. Asocierea categoriei nu obligă la completarea tuturor atributelor sale.
Grupați în aceeași categorie numai atribute cu aceeași semnificație. De exemplu, nu combinați un atribut de lungime cu atribute despre culoare fără un motiv de business.
Categoria păstrează referințe, sub formă de listă, către entitățile cărora le este asociată.
1.2 Definiția atributului
Atributele sunt definite în categorii. Fiecare definiție păstrează referințele către categoriile din care face parte.
Eav Data Type stabilește tipul valorii, iar Dynamic Form Control Type stabilește afișarea controlului în interfață. Unele combinații nu sunt permise, de exemplu un Combo Box pentru un câmp numeric în configurația descrisă.
În funcție de tipul datelor, se configurează și alte proprietăți:
- Number of Decimals pentru zecimale; Min Value și Max Value pentru limite numerice;
- Validation Regex pentru șiruri de caractere și caractere;
- O referință Unit of Measure când atributul reprezintă o unitate de măsură;
- Reference Data Type Name, cu numele complet al tipului, pentru referințe la alte entități; Is Collection pentru referințe multiple.
1.2.1 Câmpuri de configurare a atributelor
Tabelele indică tipurile de date pentru care este valabilă fiecare configurație. Culorile înseamnă:
- Verde — utilizare fără condiții speciale;
- Galben — utilizare limitată;
- Roșu — configurația nu are semnificație pentru acel tip;
- Gri — configurația nu se aplică.
1.2.1.1 Name, Description, Label and Note
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Name, Description, Label și Note explică scopul atributului. Pentru evitarea erorilor, descrieți clar rolul și utilizarea sa în sistem.
1.2.1.2 Eav Data Type
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Eav Data Type este configurația principală: stabilește tipul de date reprezentat. Valorile sunt definite în enumerarea EavDataType:
- NULL
- SystemBooleanNullable
- SystemByteNullable
- SystemSByteNullable
- SystemCharNullable
- SystemInt32Nullable
- SystemUInt32Nullable
- SystemInt64Nullable
- SystemUInt64Nullable
- SystemInt16Nullable
- SystemUInt16Nullable
- SystemDecimalNullable
- SystemDoubleNullable
- SystemSingleNullable
- SystemString
- SystemGuidNullable
- ReferenceEntity
- SystemDateTimeOffsetNullable
- SystemDateTimeNullable
- JsonDocument
Unele tipuri sunt grupate în tabele: de exemplu, întregii cu semn și numerele în virgulă mobilă.
1.2.1.3 Dynamic Form Control Type
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Dynamic Form Control Type stabilește afișarea atributului. Anumite combinații cu Eav Data Type nu sunt permise, de exemplu Combo Box pentru un câmp numeric în configurația descrisă.
Tipurile de controale sunt definite în DynamicFormControlType. Mai jos sunt cele utilizabile pentru atribute:
- autocomplete (o singură referință din colecție)
- checkbox (numai valori bool)
- colorPicker
- comboBox (o singură referință din colecție)
- date
- dateInterval
- dateTimeInterval
- dateTimeLocal
- list (referință unică sau multiplă)
- number (numere întregi sau zecimale)
- password
- phone
- listBox (referință unică sau multiplă)
- radioButtons (o singură referință)
- searchHelp (referință unică sau multiplă)
- text
- time
- url
- dataTable (referințe unice sau multiple, numai pentru citire)
- html
Detaliile controalelor se găsesc în documentația de configurare a interfeței.
1.2.1.4 Reference Data Type Name
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Pentru o referință la altă entitate, acest câmp conține numele complet al tipului. Referenced Data Types poate oferi surse suplimentare, precum proiecții, pentru căutare și filtrare.
Sursele din Referenced Data Types trebuie să returneze același Id al entității principale indicate de Reference Data Type Name.
1.2.1.5 Referenced Data Types
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Câmpul stabilește seturile de date care pot furniza referința, de obicei proiecții asociate tipului principal.
Pentru comutarea între surse, utilizați controlul searchHelp.
1.2.1.6 Payload As Json and Json Document
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Pentru opțiuni de selecție, JSON-ul transmis interfeței trebuie să conțină colecția optionsForControl, cu elemente bazate pe EntityEnum:
{
"optionsForControl": [
{
"id": "3033b1e7-b64d-4a37-b463-c75ed16ff2d8",
"name": "Option One",
"value": 1
},
{
"id": "3033b1e7-b64d-4a37-b463-c75ed16ff2d9",
"name": "Option Two",
"value": 2
},
{
"id": "3033b1e7-b64d-4a37-b463-c75ed16ff2da",
"name": "Option Three",
"value": 3
}
]
}
Nu utilizați optionsForControl pentru referințe reale la entități. Pot fi configurate însă filtre pentru datele returnate de sursele Referenced Data Types.
Important: pentru opțiuni furnizate prin optionsForControl, configurați Eav Data Type ca ReferenceEntity. Interfața se comportă ca în cazul unei referințe.
EntityEnum poate fi extins cu câmpuri suplimentare. Pentru selecție multiplă, activați Is Collection.
Id-ul opțiunii selectate este stocat în câmpul Guid al Primitive Value.
Logica de business deserializează opțiunile și găsește valoarea referită prin Id.
Interfața de bază afișează numai Id-ul opțiunii selectate.
1.2.1.7 Unit of Measure
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Dacă valoarea reprezintă o unitate de măsură, asociați-o prin Unit of Measure. Acest lucru se aplică de regulă valorilor numerice și referințelor la entități.
1.2.1.8 Is Collection
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Is Collection se setează la true pentru valori sau referințe multiple. Alegeți un control compatibil, de regulă searchHelp sau listBox.
Pentru o colecție se creează câte o înregistrare EAV pentru fiecare valoare sau entitate referită.
1.2.1.9 Is Unique Value
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Setați Is Unique Value la true dacă valoarea trebuie să fie unică între toate entitățile care folosesc atributul.
Pentru tipurile Date Time, verificarea unicității poate produce rezultate incorecte.
1.2.1.10 Is Unique Entity Value
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Is Unique Entity Value permite o singură valoare a atributului pentru fiecare entitate. Fără această opțiune, entitatea poate avea mai multe valori ale aceluiași atribut.
La tipurile Date Time, verificarea unicității poate produce rezultate incorecte.
1.2.1.11 Is Mandatory
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Is Mandatory se setează la true pentru o valoare obligatorie.
1.2.1.12 Min Count and Max Count
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Min Count și Max Count limitează numărul minim și maxim de valori ale unei colecții.
Pentru atribute de tip string, această configurație limitează lungimea.
1.2.1.13 Validation Regex
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Validation Regex verifică datele introduse. Se utilizează în principal pentru șiruri și, cu limitări, pentru numere. De exemplu, pentru valori între 1 și 999:
([1-9]|[1-9][0-9]|[1-9][0-9][0-9])
1.2.1.14 Number Of Decimals
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Number Of Decimals stabilește numărul de zecimale afișate pentru valori în virgulă mobilă și colecțiile lor.
1.2.1.15 Min Value and Max Value
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Min Value și Max Value stabilesc limitele valorilor numerice și ale colecțiilor lor.
1.2.1.16 Order Index
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Order Index controlează ordinea afișării entităților și valorilor. În definiția atributului, stabilește ordinea atributelor în liste.
1.2.1.17 Attribute Definition Aggregate
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Câmpul stabilește modul de agregare a valorilor. Opțiunile sunt definite în AttributeDefinitionAggregate:
- Average
- Count
- Distinct
- Concat
- Min
- Max
- OrderBy
- OrderByDescending
- Sum
- Multiply
1.2.1.18 Definition Inheritance
Compatibilitatea configurației
| Boolean | Întreg cu semn | Întreg fără semn | Virgulă mobilă |
| Char | Byte | Guid | Șir de caractere |
| Referință la entitate | Dată și oră | Json | Colecții |
Definition Inheritance configurează moștenirea atributului de către părinții și/sau copiii entității.
Comportamentul se configurează utilizând relațiile de părinte și copil ale atributului.
Opțiunile sunt definite în AttributeDefinitionInheritanceMode:
- None
- ByParentOnly
- ByChildrenOnly
- ByBoth
1.2.2 Referințe la entități și opțiuni
Pentru un atribut care referă altă entitate, configurați:
- Reference Data Type Name — numele complet al tipului entității referite;
- Is Collection — true pentru referințe multiple, împreună cu un control de selecție multiplă precum searchHelp sau listBox;
- Referenced Data Types — sursele alternative disponibile pentru căutare;
- Is Mandatory — opțional, pentru o valoare obligatorie;
- Min Count și Max Count — limite pentru numărul de valori;
PayloadAsJson — lista de opțiuni când nu există o entitate referită, dar sunt disponibile valori predefinite.
1.3 Valoarea EAV
EAV păstrează referința la atribut și la entitatea căreia îi aparține valoarea.
Valoarea este stocată într-un Primitive Value. Baza de date are câte o coloană pentru fiecare tip, însă o înregistrare EAV utilizează o singură coloană; celelalte sunt null sau ignorate. Eav Data Type stabilește coloana citită sau scrisă. Lipsa configurației produce o excepție.
2 Extinderea EAV
Datorită complexității, nu este recomandată modificarea motorului EAV. Pentru câmpuri suplimentare, creați entități care referă categoria, definiția atributului sau valoarea EAV și păstrați extinderile în acestea.
Referințele folosesc tipul generic și Id-ul. ForeignKeyAttribute se aplică proprietății care referă tipul generic și indică identificatorul corespunzător.
Exemplu
Pentru activarea sau dezactivarea unor intrări de rețetă în funcție de atributele produsului, verificați valoarea EAV și permiteți intrarea BOM numai pentru valorile configurate.
Exemplul creează entitatea BillOfMaterialsEav, care referă atributele EAV ale rețetei.
[Table(nameof(BillOfMaterialsEav), Schema = nameof(Manufacturing))]
public class BillOfMaterialsEav : BaseEntity
{
public Guid? EavId { get; set; }
[UiDisplayAs(ErpExtensions.Ui.Form.DynamicFormControlType.searchHelp)]
[ForeignKey(nameof(EavId))]
public EAV<BillOfMaterials, EntityCategory<BillOfMaterials>> EAV
{ get; set; }
/// <summary>
/// A list of BOM inputs triggered by this EAV
/// </summary>
public List<BillOfMaterialsInput> BillOfMaterialsInput { get; set; }
}
Observați referința la clasa generică și identificatorul EavId. Tipul generic este înregistrat și ca DbSet în DbContext. Motorul EAV își păstrează comportamentul, deoarece extensia este gestionată separat.
Extensia trebuie să efectueze toate verificările de business necesare.