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
.
La question est donc de savoir si nous avons
Child
instance et savonsSchool.Id
existe-t-il un moyen de mettre à jour cette relation sans appel supplémentaire à la base de données pour obtenir l'instanceSchool
.
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();
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
SchoolId
sur Child
)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.
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();