web-dev-qa-db-fra.com

Entity Framework Core: mise à jour de la relation avec l'ID uniquement sans appel supplémentaire

J'essaie de comprendre comment traiter le `` cas de propriété de navigation unique '' décrit dans ce document:

Disons que nous avons 2 modèles.

class School
{
   public ICollection<Child> Childrens {get; set;}
   ...
}

et

class Child
{
    public int Id {get; set;}
    ...
}

C'est donc une relation plusieurs-à-un créée par convention, sans clé étrangère explicite dans un Child.

La question est donc de savoir si nous avons Child instance et savons School.Id existe-t-il un moyen de mettre à jour cette relation sans appel supplémentaire à la base de données pour obtenir l'instance School.

13
silent_coder

La question est donc de savoir si nous avons Child instance et savons School.Id existe-t-il un moyen de mettre à jour cette relation sans appel supplémentaire à la base de données pour obtenir l'instance School.

Oui c'est possible. Vous pouvez créer une fausse instance stubSchool entité avec Id uniquement, Attach vers DbContext (de cette façon indiquant à l'EF qu'il s'agit de existant), Attach l'instance Child pour la même raison, puis ajoutez le Child au parent collecte et appelez SaveChanges:

Child child = ...;
var schoolId = ...;

var school = new School { Id = schoolId };
context.Attach(school);
context.Attach(child);
school.Childrens.Add(child);
context.SaveChanges();

Mise à jour: En fait, il existe un autre moyen plus propre, car même si l'entité n'a pas de navigation ou de propriété FK, EF Core vous permet d'accéder/modifier le soi-disant Propriétés de l'ombre

Les propriétés d'ombre sont des propriétés qui n'existent pas dans votre classe d'entité. La valeur et l'état de ces propriétés sont conservés uniquement dans le Change Tracker.

dès que vous connaissez le nom. Qui dans votre cas, sans configuration serait par convention "SchoolId".

Donc, aucune fausse instance d'entité School n'est nécessaire, assurez-vous simplement que Child est attachée, puis définissez simplement la propriété shadow via l'API ChangeTracker:

context.Attach(child);
context.Entry(child).Property("SchoolId").CurrentValue = schoolId;
context.SaveChanges();
12
Ivan Stoev

Basé sur la question mise à jour

Non, il n'y a pas AUCUNE façon dont vous pourriez le faire en utilisant ORM et en tapant fort que l'ORM vous propose, sans

  • Propriété de navigation bidirectionnelle
  • Au moins une propriété ForeignKey/Principal (SchoolId sur Child)
  • Avoir une clé étrangère fantôme pour le parent
  • effectuer une requête brute (qui bat l'idée d'avoir ORM pour un typage fort) et être agnostique DB en même temps

    // Bad!! Database specific dialect, no strong typing 
    ctx.Database.ExecuteSqlCommandAsync("UPDATE Childs SET schoolId = {0}", schoolId);
    

Lorsque vous choisissez d'utiliser un ORM, vous devez accepter certaines limitations techniques du cadre ORM en question.

Si vous souhaitez suivre la conception pilotée par domaine (DDD) et supprimer tous les champs spécifiques à la base de données de vos entités, il ne sera pas facile d'utiliser vos modèles de domaine en tant qu'entités.

DDD et ORM n'ont pas de très bonnes synergies, il existe de bien meilleures approches pour cela, mais nécessitent une approche architecturale différente (à savoir: CQRS + ES (Command Query Responsibility Segregation with Event Sourcing).

Cela fonctionne beaucoup mieux avec DDD, car les événements de EventSourcing ne sont que des classes de message simples (et immuables) qui peuvent être stockées sous forme de JSON sérialisé dans la base de données et relues pour reconstruire l'état de l'entité de domaine. Mais c'est une autre histoire et on pourrait écrire des livres entiers sur ce sujet.

Ancienne réponse

Le scénario ci-dessus n'est possible que dans une seule opération de base de données, si votre Child objecte une propriété de navigation/"référence arrière" au parent.

class School
{
   public ICollection<Child> Childrens {get; set;}
   ...
}

et

class Child
{
    public int Id {get; set;}
    // this is required if you want do it in a single operation
    public int SchoolId { get; set; }
    // this one is optional
    public School { get; set; }
    ...
}

Ensuite, vous pouvez faire quelque chose comme:

ctx.Childs.Add(new Child { Id = 7352, SchoolId = 5,  ... });

Bien sûr, vous devez d'abord connaître l'ID de l'école et savoir qu'il est valide, sinon l'opération lèvera une exception si SchoolId est une valeur non valide, donc je ne recommanderais pas cette approche.

Si vous n'avez que le childId et que vous n'ajoutez pas un tout nouvel enfant, vous devrez toujours le récupérer en premier.

// childId = 7352
var child = ctx.Childs.FirstOrDefault(c => c.Id == childId);
// or use ctx.Childs.Find(childId); if there is a chance that 
// some other operation already loaded this child and it's tracked

// schoolId = 5 for example
child.SchoolId = schoolId;
ctx.SaveChanges();
2
Tseng