web-dev-qa-db-fra.com

Relations entre Entity Framework entre différents DbContext et différents schémas

J'ai donc deux objets principaux, membre et guilde. Un membre peut posséder une guilde et une guilde peut avoir plusieurs membres.

J'ai la classe Members dans un DbContext et une bibliothèque de classes séparés. Je prévois de réutiliser cette bibliothèque de classes dans plusieurs projets et de différencier les éléments en définissant le schéma de base de données sur "acc". J'ai largement testé cette bibliothèque et peux ajouter, supprimer et mettre à jour des membres dans la table acc.Members.

La classe de guilde est comme telle:

public class Guild
{
    public Guild()
    {
        Members = new List<Member>();
    }

    public int ID { get; set; }
    public int MemberID { get; set; }
    public virtual Member LeaderMemberInfo { get; set; }
    public string Name { get; set; }
    public virtual List<Member> Members { get; set; }
}

avec une cartographie de:

internal class GuildMapping : EntityTypeConfiguration<Guild>
{
    public GuildMapping()
    {
        this.ToTable("Guilds", "dbo");
        this.HasKey(t => t.ID);
        this.Property(t => t.MemberID);
        this.HasRequired(t => t.LeaderMemberInfo).WithMany().HasForeignKey(t => t.MemberID);
        this.Property(t => t.Name);
        this.HasMany(t => t.Members).WithMany()
            .Map(t =>
            {
                t.ToTable("GuildsMembers", "dbo");
                t.MapLeftKey("GuildID");
                t.MapRightKey("MemberID");
            });
    }
}

Mais lorsque j'essaie de créer une nouvelle guilde, cela signifie qu'il n'y a pas de dbo.Members.

J'ai eu une référence au projet EF du membre et ajouté le mappage de la classe Members au DbContext dont fait partie la classe Guild. modelBuilder.Configurations.Add(new MemberMapping()); (Pas sûr que ce soit la meilleure façon.)

Cela a entraîné avec cette erreur:

{"The member with identity 'GuildProj.Data.EF.Guild_Members' does not exist in the metadata collection.\r\nParameter name: identity"}

Comment puis-je utiliser la clé étrangère entre ces deux tables croisées DbContexts et avec différents schémas de base de données?

METTRE &AGRAVE; JOUR

J'ai réduit la cause de l'erreur. Lorsque je crée une nouvelle guilde, je règle l'identifiant de membre du chef de guilde sur MemberID. Cela fonctionne bien. Mais lorsque j'essaie ensuite d'ajouter l'objet membre de ce chef à la liste des membres de la guilde, c'est ce qui cause l'erreur.

MISE À JOUR 2

Voici le code de la façon dont je crée le contexte dans lequel se trouve la classe de guilde. (À la demande de Hussein Khalil)

public class FSEntities : DbContext
{
    public FSEntities()
    {
        this.Configuration.LazyLoadingEnabled = false;
        Database.SetInitializer<FSEntities>(null);
    }

    public FSEntities(string connectionString)
        : base(connectionString)
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new GuildMapping());
        modelBuilder.Configurations.Add(new KeyValueMappings());
        modelBuilder.Configurations.Add(new LocaleMappings());

        modelBuilder.Configurations.Add(new MemberMapping());
    }

    public DbSet<Guild> Guilds { get; set; }
    public DbSet<KeyValue> KeyValues { get; set; }
    public DbSet<Locale> Locales { get; set; }
}

Voici comment je l'enregistre dans le dépôt:

    public async Task CreateGuildAsync(Guild guild)
    {
        using (var context = new FSEntities(_ConnectionString))
        {
            context.Entry(guild.Members).State = EntityState.Unchanged;
            context.Entry(guild).State = EntityState.Added;
            await context.SaveChangesAsync();
        }
    }

RÉSOLUTION FINALE

J'ai donc dû ajouter des mappages à Member, Role et Permission dans DbContext contenant Guild. J'ai dû ajouter un rôle et une autorisation car le membre avait List<Role> Roles et chaque rôle avait List<Permission> Permissions.

Cela m'a rapproché de la solution. J'avais toujours des erreurs comme:

{"The member with identity 'GuildProj.Data.EF.Member_Roles' does not exist in the metadata collection.\r\nParameter name: identity"}

Ici, lorsque vous tirez un membre de la Session, vous obtenez quelque chose comme ceci:

System.Data.Entity.DynamicProxies.Member_FF4FDE3888B129E1538B25850A445893D7C49F878D3CD40103BA1A4813EB514C

Entity Framework ne semble pas bien fonctionner avec cela. Pourquoi? Je ne suis pas sûr, mais je pense que c'est parce que ContextM crée un proxy de membre et qu'en clonant le membre dans un nouvel objet Member, ContextM n'a plus d'association. Cela, je pense, permet à ContextG d’utiliser librement le nouvel objet Member. J'ai essayé de définir ProxyCreationEnabled = false dans mes DbContexts, mais l'objet membre extrait de la session continuait d'être de type System.Data.Entity.DynamicProxies.Member.

Alors, ce que j'ai fait était:

Member member = new Member((Member)Session[Constants.UserSession]);

J'ai dû cloner chaque Role et chaque Permission également dans leurs constructeurs respectifs.

Cela m'a fourni 99% du chemin. Je devais modifier mon rapport et comment je sauvegardais l'objet Guild.

            context.Entry(guild.LeaderMemberInfo).State = EntityState.Unchanged;
            foreach(var member in guild.Members)
            {
                context.Entry(member).State = EntityState.Unchanged;
            }
            context.Entry(guild).State = EntityState.Added;
            await context.SaveChangesAsync();
15
ScubaSteve

C'est un code de travail:

En Assemblée "M":

public class Member
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class MemberMapping : EntityTypeConfiguration<Member>
{
    public MemberMapping()
    {
        this.HasKey(m => m.Id);
        this.Property(m => m.Name).IsRequired();
    }
}

En assemblée "G":

  • votre classe Guild
  • votre mappage Guild, quoique avec WillCascadeOnDelete(false) dans le mappage LeaderMemberInfo.
  • modelBuilder.Configurations.Add(new GuildMapping()); et modelBuilder.Configurations.Add(new MemberMapping());

Code:

var m = new Member { Name = "m1" };
var lm = new Member { Name = "leader" };
var g = new Guild { Name = "g1" };
g.LeaderMemberInfo = lm;
g.Members.Add(lm);
g.Members.Add(m);
c.Set<Guild>().Add(g);
c.SaveChanges();

SQL exécuté:

INSERT [dbo].[Members]([Name])
VALUES (@0)
SELECT [Id]
FROM [dbo].[Members]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
-- @0: 'leader' (Type = String, Size = -1)

INSERT [dbo].[Guilds]([MemberID], [Name])
VALUES (@0, @1)
SELECT [ID]
FROM [dbo].[Guilds]
WHERE @@ROWCOUNT > 0 AND [ID] = scope_identity()
-- @0: '1' (Type = Int32)
-- @1: 'g1' (Type = String, Size = -1)

INSERT [dbo].[GuildsMembers]([GuildID], [MemberID])
VALUES (@0, @1)
-- @0: '1' (Type = Int32)
-- @1: '1' (Type = Int32)

INSERT [dbo].[Members]([Name])
VALUES (@0)
SELECT [Id]
FROM [dbo].[Members]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
-- @0: 'm1' (Type = String, Size = -1)

INSERT [dbo].[GuildsMembers]([GuildID], [MemberID])
VALUES (@0, @1)
-- @0: '1' (Type = Int32)
-- @1: '2' (Type = Int32)

Cela fonctionne également lors de l'association d'objets existants.


Réponse originale pour un cas plus général:

Vous ne pouvez pas combiner des types de contextes différents dans un même graphe d'objets. Cela signifie que vous ne pouvez pas faire quelque chose comme

from a in context.As
join b in context.Bs on ...

... car il y a toujours un contexte qui devrait créer la requête SQL complète, il devrait donc avoir toutes les informations de mappage requises.

Vous pouvez cependant enregistrer le même type dans deux contextes différents, même à partir d'assemblages différents. Pour que vous puissiez mapper Member dans le contexte de l’assemblée de Guild, appelons-le contextG, mais seulement si

  1. Member ne fait pas référence à d'autres types qui ne sont pas mappés dans contextG. Cela peut impliquer que les propriétés de navigation dans Member doivent être explicitement ignorées.
  2. Member ne peut pas faire référence à des types dans contextG, car ces types ne font pas partie du contexte de Member.

Si l'une de ces conditions ne peut pas être remplie, vous pouvez au mieux créer une nouvelle classe Member dans Assembly de Guild et enregistrer sa correspondance dans le contexte. Peut-être voudrez-vous utiliser un nom différent pour éviter toute ambiguïté, mais il s'agit de la seule alternative qui reste.

11
Gert Arnold

Sauf si vous indiquez explicitement que cette entité Member doit être mappée sur acc.Members, EF s'attend à ce qu'elle soit dans la table dbo schema Members. Pour cela, vous devez fournir soit EntityTypeConfiguration pour ce type, soit l'annoter avec System.ComponentModel.DataAnnotations.Schema.TableAttribute comme [Table("acc.Members")].

1
mcs_dodo

J'ai constaté que lorsque j'ai des problèmes avec Entity et que je construis des relations, c'est souvent parce que je vais à contre-courant du cadre ou que j'essaie de créer des relations ou des abstractions qui ne sont pas techniquement bien formées. Ce que je suggérerais ici, c’est de prendre un peu de recul et d’analyser quelques points avant d’approfondir ce sujet.

Tout d'abord, je suis curieux de savoir pourquoi vous utilisez un schéma différent ici pour ce qui est probablement une seule application accédant à un graphe d'objet. Plusieurs schémas peuvent être utiles dans certains contextes, mais je pense que Brent Ozar fait une remarque très importante dans cet article . Compte tenu de ces informations, je suis enclin à suggérer d’abord de fusionner plusieurs schémas en un et d’utiliser le même contexte de base de données pour la base de données.

La prochaine chose à aborder est le graphe d'objet. Au moins pour moi les plus grandes difficultés que j'ai eues avec la modélisation des données sont lorsque je ne découvre pas pour la première fois les questions de l'application sur la base de données. Ce que je veux dire par là est de déterminer quelles sont les données que l’application souhaite dans ses divers contextes, puis d’examiner comment optimiser ces structures de données afin d’optimiser les performances dans un contexte relationnel. Voyons comment cela pourrait être fait ...

Basé sur votre modèle ci-dessus, je peux voir que nous avons quelques termes/objets clés dans le domaine:

  • Collection de guildes
  • Collection de membres
  • Collection de chefs de guilde.

De plus, nous avons certaines règles métier qui doivent être implémentées:

  • Une guilde peut avoir 1 chef (et éventuellement plus d'un?)
  • Un chef de guilde doit être membre
  • Une guilde a une liste de 0 ou plus membres
  • Un membre peut appartenir à une guilde (et éventuellement plus de 1?)

Donc, étant donné ces informations, examinons les questions que votre application pourrait avoir à propos de ce modèle de données. Je peux l'application:

  • rechercher un membre et voir ses propriétés
  • rechercher un membre et voir ses propriétés et qu'il est un chef de guilde
  • rechercher une guilde et voir une liste de tous ses membres
  • rechercher une guilde et voir une liste des chefs de guilde
  • consulter la liste de tous les chefs de guilde

Ok, maintenant nous pouvons passer aux choses sérieuses, comme on dit ...

L'utilisation de la table de jointure entre guildes et membres est optimale dans ce cas. Il vous donnera la possibilité d'avoir des membres dans plusieurs guildes ou pas de guildes et fournira une stratégie de mise à jour peu verrouillable - si bon appel!

En passant aux chefs de guilde, il existe quelques choix qui pourraient avoir un sens. Même s'il peut ne jamais y avoir de cas pour les sergents de guilde, je pense qu'il est logique d'envisager une nouvelle entité appelée chef de guilde. Ce que permet cette approche est multiple. Vous pouvez mettre en cache la liste des identifiants de chef de guilde dans une application. Ainsi, plutôt que de faire un déplacement de base de données pour autoriser une action de guilde exécutée par un chef, vous pouvez accéder au cache de l'application locale contenant uniquement la liste des identifiants de chef, et non tout son objet. à l'inverse, vous pouvez obtenir la liste des dirigeants d'une guilde et, quel que soit le sens de la requête, vous pouvez accéder aux index clusterisés des entités principales ou à l'index intermédiaire facile à gérer de l'entité jointe.

Comme je l'ai noté au début de cette manière de "répondre", lorsque je rencontre des problèmes comme le vôtre, c'est généralement parce que je vais à l'encontre de l'entité. Je vous encourage à repenser votre modèle de données et comment, avec une approche à frottement réduit, lâchez le schéma multiple et ajoutez un objet intermédiaire guild_leader. À votre santé!

1
Bill Berry

Je réponds à votre question mise à jour:

essayez d'utiliser cette ligne avant de mettre à jour votre contexte

context.Entry(Guild.Members).State = Entity.EntityState.Unchanged

cela résoudra l'erreur que vous avez

0
Hussein Khalil