Obfusquer une application Entity Framework Core sans casser votre couche de données
EF Core mappe vos types CLR vers des tables par leurs noms. Renommez une entité ou une propriété avec un obfuscateur et, par défaut, le schéma attendu par EF se déplace avec eux : votre DbContext cherche désormais une table « a » avec une colonne « c », les migrations ne correspondent plus et l'accès aux propriétés par chaîne lève à l'exécution. La solution n'est pas d'exclure toute votre couche de données de l'obfuscation. C'est de comprendre exactement quels noms EF lit à l'exécution, de rendre votre mapping explicite pour découpler le schéma des noms CLR, puis de laisser Nebula.NET renommer librement les membres. Voici le tableau complet, avec les règles de renommage et les motifs EF qui rendent cela sûr.
Entity Framework Core est une couche de mapping, et ce depuis quoi il mappe, par défaut, ce sont vos noms. Appelez une classe Invoice avec une propriété Status et — sans aucune configuration — EF décide qu”il existe une table Invoices avec une colonne Status, car la convention dérive le schéma des identifiants CLR. Cette convention est merveilleuse pour la productivité et une mine pour l”obfuscation. Dès que Nebula.NET renomme Invoice en a et Status en b, la convention conclut fidèlement que votre base de données a une table a avec une colonne b, et plus rien ne correspond à la réalité.
La réaction habituelle est d”exclure toute la couche de données de l”obfuscation. Cela fonctionne et c”est un mauvais marché : vos entités et votre DbContext sont souvent la description la plus claire de votre domaine que vous livrez, et vous venez de les laisser en clair pour protéger un schéma de base de données qui n”a jamais été la partie sensible. Il existe plutôt un correctif précis. Trouvez chaque endroit où EF lit un nom CLR à l”exécution, rendez le schéma explicite pour qu”il cesse de dériver de ces noms, puis laissez Nebula renommer les membres. Cet article est cet inventaire et ces motifs.
Où EF lit réellement un nom
Le renommage est sûr exactement là où EF ne dépend pas de l”identifiant d”origine à l”exécution, et risqué là où il en dépend. Il y a quatre endroits où il en dépend :
- Noms de table et de colonne par convention. Sans mapping explicite, le nom de table est le nom de la propriété
DbSet(ou le nom du type) et chaque colonne est le nom de la propriété. Renommez le membre et le nom de schéma dérivé se déplace avec lui. - Accès aux membres par chaîne.
EF.Property<T>("Status"),entry.Property("Status")et les recherches de propriétés fantômes résolvent un membre par chaîne. Une chaîne ne participe pas au renommage, donc après l”obfuscation elle nomme un membre qui n”existe plus et lève. - Migrations. Une migration générée et l”instantané du modèle contiennent les noms de schéma sous forme de littéraux de chaîne. S”ils ont été produits par convention, ils encodent les noms d”origine ; après un renommage, le modèle en cours dérive des noms différents et les deux ne concordent pas.
- Tout ce qui réfléchit sur votre modèle par nom — quelques sérialiseurs, des couches d”audit, ou une fonctionnalité de fournisseur qui s”appuie sur les noms de propriétés de navigation.
LINQ, notamment, n”est pas dans cette liste. Where(i => i.Status == open) se compile en un arbre d”expression contenant un MemberInfo, pas la chaîne "Status". Quand Nebula renomme la propriété, ce MemberInfo pointe vers le membre renommé, et EF le traduit via votre mapping. Le renommage cohérent — que Nebula garantit sur tout l”assembly — garde chaque requête compilée intacte. La casse concerne toujours des noms résolus depuis des chaînes, jamais l”accès typé aux membres.
Épinglez le schéma, puis obfusquez librement
Toute la stratégie tient en une idée : découpler le nom de schéma du nom CLR. Une fois que chaque nom de table, de colonne, de clé et d”index est un littéral de chaîne dans votre configuration, les identifiants CLR ne portent plus aucune signification de schéma, et Nebula peut les renommer en a.b sans effet sur la base de données.
protected override void OnModelCreating(ModelBuilder model)
{
model.Entity<Invoice>(e =>
{
e.ToTable("Invoices"); // table name is now a literal
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("Id");
e.Property(x => x.Status).HasColumnName("Status");
e.Property(x => x.Total).HasColumnName("Total").HasPrecision(18, 2);
e.HasIndex(x => x.Status).HasDatabaseName("IX_Invoices_Status");
// Navigation + FK column, pinned the same way.
e.HasMany(x => x.Lines)
.WithOne(l => l.Invoice)
.HasForeignKey(l => l.InvoiceId)
.HasConstraintName("FK_InvoiceLines_Invoices");
});
}
Ces littéraux de chaîne — "Invoices", "Status", "IX_Invoices_Status" — sont exactement le genre de chose que le chiffrement de chaînes de Nebula protège dans la sortie, mais ils ne sont jamais renommés, car ce sont des données, pas des identifiants. Les lambdas x => x.Status sont de l”accès typé aux membres, donc elles suivent le renommage automatiquement. Le résultat net : Invoice.Status peut devenir a.b dans le binaire livré tandis que le SQL émis par EF lit et écrit toujours la colonne Status.
Les annotations de données réalisent le même découplage si vous les préférez sur le type :
[Table("Invoices")]
public class Invoice
{
[Column("Id")] public int Id { get; set; }
[Column("Status")] public InvoiceStatus Status { get; set; }
[Column("Total")] public decimal Total { get; set; }
}
Dans les deux cas, la règle d”obfuscation dans nebula.json est simplement que votre couche de données n”est pas spéciale : elle s”obfusque avec tout le reste :
{
"preservePublicApi": true,
"encryptStrings": true,
"controlFlowObfuscation": true,
"renaming": {
"rules": [
{ "pattern": "MyApp.Data.*", "action": "rename" }
]
}
}
Aucune exclusion globale pour MyApp.Data.*. Les entités sont renommées ; le schéma est épinglé dans la configuration ; les deux ne se touchent plus.
Supprimez l”accès par chaîne
Les motifs qui cassent vraiment sont ceux qui résolvent un membre depuis une chaîne, car une chaîne ne suit jamais un renommage. Trouvez-les et passez à la forme typée.
// ✗ Breaks: "Status" names a CLR member that obfuscation renamed to "b".
var q1 = db.Invoices.Where(i => EF.Property<InvoiceStatus>(i, "Status") == open);
// ✓ Typed member access — compiles to a MemberInfo that follows the rename.
var q2 = db.Invoices.Where(i => i.Status == open);
Les propriétés fantômes sont le seul cas où une chaîne est légitime — le membre n”existe pas du tout sur le type CLR — donc il n”y a rien à renommer ni rien à casser. Déclarez-les explicitement et accédez-y par leur nom de schéma, que vous contrôlez :
model.Entity<Invoice>().Property<DateTime>("LastModifiedUtc"); // shadow, no CLR member
// ...
var ts = db.Entry(invoice).Property("LastModifiedUtc").CurrentValue; // safe: not a CLR name
Le SQL brut est l”autre endroit à auditer. Une requête brute doit nommer la colonne (Status), jamais la propriété CLR — et puisque vous avez épinglé le nom de colonne à un littéral, ceux-ci s”alignent en permanence quelle que soit l”obfuscation :
// Names the column, which is fixed — safe under any rename.
var rows = db.Invoices.FromSqlInterpolated(
$"SELECT * FROM Invoices WHERE Status = {(int)open}");
Migrations : générez-les contre le modèle mappé
Les migrations encodent les noms de schéma sous forme de littéraux, donc elles sont sûres si ces littéraux sont vos noms épinglés. Elles ne deviennent un danger que lorsque l”instantané a été généré par convention et a donc capturé des noms dérivés de CLR qui bougent ensuite. Deux règles gardent les migrations propres :
- Générez les migrations depuis la build non obfusquée. Le tooling de design réfléchit sur votre modèle pour produire la migration et l”instantané ; exécutez-le contre l”assembly
Debugnormal, jamais contre l”artefact obfusqué. La migration que vous validez contient alors vos littéraux de schéma explicites. - Ne mettez jamais
nameof(SomeProperty)dans une migration éditée à la main.nameofcapture le nom CLR à la compilation, donc après un renommage il émet"b"là où vous vouliez dire la colonne"Status". Écrivez le nom de colonne comme littéral.
// In a migration — literal column names, matching your explicit mapping.
migrationBuilder.CreateIndex(
name: "IX_Invoices_Status",
table: "Invoices",
column: "Status"); // literal, not nameof(Invoice.Status)
Comme le binaire obfusqué et la migration parlent tous deux le même vocabulaire de schéma fixe, context.Database.Migrate() au démarrage se comporte à l”identique que l”assembly ait été obfusqué ou non.
La rare exclusion chirurgicale
Parfois une fonctionnalité précise réfléchit encore sur un nom CLR à l”exécution — un composant d”audit qui enregistre le nom de la propriété de navigation, ou une extension de fournisseur qui s”appuie dessus. Quand cela arrive, préservez ce membre, pas l”assembly. Nebula vous permet d”exclure un seul membre avec un attribut, ce qui garde l”exclusion visible dans le code où vit la dépendance :
public class Invoice
{
// A downstream component reflects over this navigation's name at runtime.
[Obfuscation(Feature = "renaming", Exclude = true)]
public List<InvoiceLine> Lines { get; set; } = new();
}
C”est là toute l”échappatoire — une propriété, documentée par son propre attribut — au lieu d”un joker qui épargnerait tout votre modèle de domaine. La discipline en vaut la peine : l”intérêt d”obfusquer une app pilotée par les données est que la logique précieuse est dans les entités et la couche de requêtes, et une exclusion globale n”en protège rien.
Mise en œuvre
EF Core ne casse sous l”obfuscation que là où il résout un nom depuis une chaîne à l”exécution, et chacun de ces endroits a une alternative découplée. Épinglez tables, colonnes, clés et index avec un mapping explicite pour que le schéma cesse de dériver des identifiants CLR ; remplacez EF.Property et nameof-dans-les-migrations par les formes typée ou littérale ; générez les migrations depuis la build propre. Faites cela et vos types MyApp.Data.* s”obfusquent aux côtés du reste de l”app — renommés, chaînes chiffrées, flux de contrôle durci — tandis que la base de données voit exactement le schéma qu”elle a toujours vu. Vous protégez le modèle de domaine qui méritait d”être protégé, et vous ne cédez rien à la couche de mapping.
Essayez Nebula.NET
Renforcez votre code .NET en quelques minutes — commencez avec l'édition gratuite.