Gå til innhold

ADR-019: Konvensjonsbasert EF-kolonnemapping⚓︎

Kolonnenavn i KaiDbContext utledes av en konvensjon — snake_case med tapsfri æøå-translitterering (æ→ae, ø→oe, å→aa) av CLR-egenskapsnavnet. Håndskrevne HasColumnName beholdes kun som bevisste unntak. Redundante IsRequired(), HasConversion<int>() og HasColumnType("text") er fjernet, og verdiobjekt-ID-konvertere registreres én gang i ConfigureConventions.

Kontekst⚓︎

Entitetskonfigurasjonene hadde ~500 av 752 linjer som håndmappet det EF allerede kan utlede: HasColumnName("saksnummer") for Saksnummer, IsRequired() på ikke-nullbare felt (NRT gir dette gratis), HasConversion<int>() på enum-er (EF sitt standardvalg), og HasColumnType("text") på strenger (Npgsql sitt standardvalg). Å legge til én kolonne kostet en linje i entiteten pluss en mappinglinje. Verre: kolonner uten eksplisitt HasColumnName beholdt EF sitt PascalCase-navn, så skjemaet var en blanding av snake_case og PascalCase (regelresultater."RunAt" ved siden av sak_id).

Beslutning⚓︎

  • Kolonnekonvensjon. SnakeCaseColumnNamingConvention (en IModelFinalizingConvention registrert i KaiDbContext.ConfigureConventions) setter hvert kolonnenavn til snake_case + translitterering av egenskapsnavnet. Den kjører på ConfigurationSource.Convention, så et eksplisitt HasColumnName(...) i en konfigurasjon vinner alltid. Konvensjonen gjelder kun Kais egne tabeller (Enova.Kai.*); tredjeparts skjemaer som TickerQ (ticker) beholder sine egne navn.
  • Tapsfri translitterering. æ→ae, ø→oe, å→aa (ikke enkelttegns-fold ø→o), så to ulike norske ord aldri kolliderer til samme kolonnenavn.
  • Redundans fjernet. IsRequired() på ikke-nullbare felt, HasConversion<int>() på enum-er og HasColumnType("text") på strenger er slettet — verifisert med en tom probe-migrasjon.
  • Verdiobjekt-ID-er. SakId, RegelId, RegelresultatId (Guid) og SaksnummerId (string) konverteres via configurationBuilder.Properties<T>().HaveConversion<...>() i ConfigureConventions, ikke per entitet.
  • Engangs-normalisering. Migrasjonen SnakeCaseKolonnekonvensjon gir de gjenværende PascalCase-kolonnene snake_case-navn (RENAME COLUMN, metadata-only i Postgres).

Bevisste unntak (beholder eksplisitt HasColumnName)⚓︎

Kolonne(r) Egenskap Hvorfor
sak_key, regel_id, sak_id CaseKey, RuleId, CaseId Engelsk egenskapsnavn, norsk kolonne — ikke utledbart.
type, navn (på regler) Kind, Name Norsk kolonne for engelsk egenskap.
soknad_sendt_dato Søknadsdato Speiler Mimir-gold-kolonnen (konvensjonen ville gitt soeknadsdato).
statsstotteordning_*, soknadsrunde_id, tabellen virkemiddel_soknadsrunde Statsstøtteordning*, SøknadsrundeId Speiler Mimir-gold-navnene (ø→o). Se VirkemiddelMasterdataKildeAdapter.
okonomisk_paavirkning ØkonomiskPåvirkning Speiler YAML-nøkkelen i regel-authoring (RegelDefinisjon).
belop_*, soker_*, {klima,energi,effekt,innovasjon}_omsokt, prosjektperiode-kolonner Komplekse typer (Støttebeløp, Søker, Resultat, Prosjektperiode) Strukturelle prefiks-navn, delvis gold-speilende.

applicable_ers_statuses beholder også eksplisitt navn fordi skygge-egenskapen heter _applicableErsStatuses (konvensjonen ville tatt med understreken). SøknadsrundeId.IsRequired() beholdes fordi NRT-required ikke utledes for skalar i en eid samling (OwnsMany).

Konsekvenser⚓︎

  • En ny kolonne koster nå én linje i entiteten; ingen mappinglinje med mindre den er et unntak.
  • Nye kolonner med æøå får deterministisk, tapsfritt navn uten at utvikleren tenker på det.
  • Unntakene er få og har hver sin dokumenterte grunn (ekstern kontrakt eller struktur), så «hvorfor er akkurat denne håndmappet» er alltid svarbart.
  • Regresjonsvern: en tom probe-migrasjon (dotnet ef migrations add Probe) skal alltid være tom. Er den ikke det, har noen brutt konvensjonen eller lagt til et udokumentert avvik.