Azure / Azure/data-api-builder

[Enh]: For auto-config entities support `auto-relationships`

Ouverte
#3,207 0 commentaires 1 réaction 0 personnes assignées Voir sur GitHub
2.x auto-config
Langage dominant
C#
Étoiles
1.5k
Forks
370
Merge moyen
3 j 22 h
PR mergées (30 j)
9

Description

## What?

For auto-generated entities, add the option to create `one-to-many` and `many-to-one` cardinality relationships based on foreign key constraints defined in the database.

## Why?

Dynamic configurations already provide rich capabilities for REST endpoints. This feature allows those same configurations to provide richer GraphQL capabilities by enabling join operations across related entities.

## Configuration

```json
{
"autoentities": {
"catalog": {
"patterns": { },
"template": { },
"permissions": [ ],
"relationships": {
"enabled": true,
"name": {
"relationship": "{source.object}_to_{target.object}",
"reciprocal": "{target.object}_to_{source.object}"
},
"reciprocal": true
}
}
}
}
```

| Property | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `enabled` | (optional, default: true) Globally enable relationship creation for auto-generated entities. |
| `name.relationship` | (optional, default: `{source.object}_to_{target.object}`) Naming pattern for the relationship from source to target. |
| `name.reciprocal` | (optional, default: `{target.object}_to_{source.object}`) Naming pattern for the inverse relationship when `reciprocal` is enabled. |
| `reciprocal` | (optional, default: true) Whether the inverse relationship should be generated automatically. |

### Name placeholders

These placeholders may be used inside the relationship name patterns.

* `{entity}`: name of the entity
* `{source.schema}`: source table schema
* `{source.object}`: source table name
* `{target.schema}`: target table schema
* `{target.object}`: target table name
* `{constraint.name}`: foreign key constraint name

### Conditional relationship generation

Relationships are created only when both conditions are true:

* `runtime.graphql.enabled` = true
* `entities..graphql.enabled` = true

If either condition is false, the relationship is skipped. No error is produced.

### How to recognize one-to-many in SQL Server

In SQL Server, a foreign key constraint represents a **many-to-one relationship** from the referencing table to the referenced table.

#### Example

```
Orders.ProductId → Products.ProductId
```

This indicates that many orders may reference a single product. The inverse direction can be exposed as a **one-to-many** relationship.

A **true one-to-one relationship** can only be inferred if the foreign key columns are also constrained by a unique index or unique constraint.

```mermaid
erDiagram
PARENT_TABLE {
int ParentId PK
string Name
}

CHILD_TABLE {
int ChildId PK
int ParentFk
string Description
}

PARENT_TABLE ||--o{ CHILD_TABLE : "ParentId → ParentFk (Database constraint)"
CHILD_TABLE }o--|| PARENT_TABLE : "Logical reciprocal"

COMPOSITE_PARENT {
int ParentA PK
int ParentB PK
string Info
}

COMPOSITE_CHILD {
int ChildA PK
int ChildB PK
int ParentA_Fk
int ParentB_Fk
string Details
}

COMPOSITE_PARENT ||--o{ COMPOSITE_CHILD : "ParentA, ParentB → ParentA_Fk, ParentB_Fk"
COMPOSITE_CHILD }o--|| COMPOSITE_PARENT : "Logical reciprocal"
```

### How reciprocal relationships are handled

When `reciprocal` is true, DAB automatically creates the inverse relationship for each detected foreign key.

#### Example

Database constraint:

```
Orders.ProductId → Products.ProductId
```

Generated relationships:

```
Orders → Products (many-to-one)
Products → Orders (one-to-many)
```

If the database already contains a foreign key constraint in the opposite direction between the same tables, the metadata query detects it and marks the relationship as reciprocal so DAB does not generate duplicate relationships.

## Command Line

```
dab auto-config --relationships.enabled true
dab auto-config --relationships.name.relationship "xyz"
dab auto-config --relationships.name.reciprocal "xyz"
dab auto-config --relationships.reciprocal true
```

## Query to fetch metadata

This query returns all foreign key relationships between tables mapped to entities. It also detects reciprocal constraints and resolves relationship names using the configured naming patterns.

```sql
declare @entities nvarchar(max) =
'[
{ "entity": "Products", "schema": "dbo", "object": "Products" },
{ "entity": "Orders", "schema": "dbo", "object": "Orders" },
{ "entity": "Categories", "schema": "Categories" }
]';

declare @namePattern nvarchar(256) = '{entity}_{source.object}_to_{target.object}';
declare @reciprocalNamePattern nvarchar(256) = '{entity}_{target.object}_to_{source.object}';
declare @reciprocal bit = 1;

with entity_map as (
select
entity,
schema_name,
object_name
from openjson(@entities)
with (
entity sysname '$.entity',
schema_name sysname '$.schema',
object_name sysname '$.object'
)
),
fk as (
select
fk.object_id,
constraintName = fk.name,

parentSchema = s1.name,
parentTable = t1.name,

childSchema = s2.name,
childTable = t2.name,

parentObjectId = fk.referenced_object_id,
childObjectId = fk.parent_object_id,

sourceKeys =
string_agg(c1.name, ',') within group (order by fkc.constraint_column_id),

targetKeys =
string_agg(c2.name, ',') within group (order by fkc.constraint_column_id)

from sys.foreign_keys fk
join sys.foreign_key_columns fkc
on fk.object_id = fkc.constraint_object_id
join sys.tables t1
on fk.referenced_object_id = t1.object_id
join sys.schemas s1
on t1.schema_id = s1.schema_id
join sys.tables t2
on fk.parent_object_id = t2.object_id
join sys.schemas s2
on t2.schema_id = s2.schema_id
join sys.columns c1
on c1.object_id = fkc.referenced_object_id
and c1.column_id = fkc.constraint_column_id
join sys.columns c2
on c2.object_id = fkc.parent_object_id
and c2.column_id = fkc.parent_column_id
group by
fk.object_id,
fk.name,
s1.name, t1.name,
s2.name, t2.name,
fk.referenced_object_id,
fk.parent_object_id
)

select
sourceEntity = e1.entity,
targetEntity = e2.entity,

relationshipName =
replace(
replace(
replace(
replace(
replace(
replace(@namePattern,
'{entity}', e1.entity),
'{source.schema}', f.parentSchema),
'{source.object}', f.parentTable),
'{target.schema}', f.childSchema),
'{target.object}', f.childTable),
'{constraint.name}', f.constraintName),

reciprocalRelationshipName =
case
when @reciprocal = 1 and r.object_id is null
then
replace(
replace(
replace(
replace(
replace(
replace(@reciprocalNamePattern,
'{entity}', e2.entity),
'{source.schema}', f.parentSchema),
'{source.object}', f.parentTable),
'{target.schema}', f.childSchema),
'{target.object}', f.childTable),
'{constraint.name}', f.constraintName)
end,

is_reciprocal =
case when r.object_id is not null then 1 else 0 end,

f.sourceKeys,
f.targetKeys

from fk f
join entity_map e1
on e1.schema_name = f.parentSchema
and e1.object_name = f.parentTable

join entity_map e2
on e2.schema_name = f.childSchema
and e2.object_name = f.childTable

left join fk r
on r.parentObjectId = f.childObjectId
and r.childObjectId = f.parentObjectId

order by sourceEntity, targetEntity;
```

This query ensures that:

* Only tables present in the entity list are considered.
* Relationship keys are derived from the foreign key definition.
* Composite keys are supported.
* Reciprocal constraints are detected.
* Relationship names follow the configured naming pattern.

Guide de contribution

Ouvrir le guide de contribution

Piste de recherche

Commencez par suivre la gestion des relations d’auto-config et la requête de métadonnées SQL Server décrites dans l’issue, puis examinez comment les relations GraphQL sont représentées pour les entités générées. Validez la configuration et les options de CLI, y compris les cas de relations réciproques et de clés composites ; le travail est terminé lorsque les relations de clés étrangères sont générées uniquement lorsque les deux conditions GraphQL sont activées, sans relations réciproques en double.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
csharp, graphql, sql
Domaine
api, backend, databases
Type d'issue
Fonctionnalité
Difficulté
5/5
Temps estimé
Plus d'une semaine
Activité
À l'abandon
Clarté
Plutôt claire
Accessibilité débutants
38/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.