2016-05-10 10 views
0

複数の関連するエンティティのための単一関係表は、など、タグ属性EF6:私はノードのように、多くの企業でEFのモデルを持っている

あり、「エイリアス」の実体でもある、と

他のほとんどすべての他のエンティティエイリアスと多対多の関係を持つことができます。これに関する望ましくないことの1つは、これらの関係を追跡するために作成されるテーブルの数です(例えば、NodeAlias、AttributeAliasなど)。

エイリアスを1つのテーブル内の他のすべてのエンティティにマップできる設計方法はありますか?それが可能だ場合、私は多分、これらの線に沿って何かを考えていた:

+---------+--------+-------------+-----------+ 
| AliasId | NodeId | AttributeId | TagId | 
+---------+--------+-------------+-----------+ 
|  1 |  1 |   2 |   3 | 
+---------+--------+-------------+-----------+ 

答えて

1

を見るためにコンソールアプリケーションを作成しました。

私はこれを別の回答として意図的に投稿しました。私の以前の回答は、誰かが必要な場合でもここに残ります。

ステップ#1:私は便利な方法で反射を利用して、プロパティ値を取得および設定のための拡張メソッドを作成しました:

public static class ObjectExtensions 
{ 
    public static TResult GetPropertyValue<TResult>(this object entity, string propertyName) 
    { 
     object propertyValue = entity?.GetType().GetProperty(propertyName)?.GetValue(entity); 

     try 
     { 
      return (TResult)propertyValue; 
     } 
     catch 
     { 
      return default(TResult); 
     } 
    } 

    public static void SetPropertyValue(this object entity, string propertyName, object value) 
    { 
     entity?.GetType().GetProperty(propertyName)?.SetValue(entity, value); 
    } 
} 

ステップ#2:を私は多くの対を提供するために、モデルを更新しました多くの関係。

public class Node 
{ 
    [Key] 
    public int NodeId { get; set; } 

    public string Name { get; set; } 

    public virtual ICollection<AliasMapping> AliasMappings { get; set; } 
} 

public class Attribute 
{ 
    [Key] 
    public int AttributeId { get; set; } 

    public string Name { get; set; } 

    public virtual ICollection<AliasMapping> AliasMappings { get; set; } 
} 

public class Tag 
{ 
    [Key] 
    public int TagId { get; set; } 

    public string Name { get; set; } 

    public virtual ICollection<AliasMapping> AliasMappings { get; set; } 
} 

public class Alias 
{ 
    [Key] 
    public int AliasId { get; set; } 

    public string Name { get; set; } 

    public virtual ICollection<AliasMapping> AliasMappings { get; set; } 
} 

public class AliasMapping 
{ 
    [Key] 
    public int Id { get; set; } 

    [ForeignKey("Alias")] 
    public int AliasId { get; set; } 

    public Alias Alias { get; set; } 

    [ForeignKey("Node")] 
    public int? NodeId { get; set; } 

    public virtual Node Node { get; set; } 

    [ForeignKey("Attribute")] 
    public int? AttributeId { get; set; } 

    public virtual Attribute Attribute { get; set; } 

    [ForeignKey("Tag")] 
    public int? TagId { get; set; } 

    public virtual Tag Tag { get; set; } 
} 

ステップ#3:ための関係には[ForeignKey]データ注釈が十分にあるようMyDbContextが簡素化されている可能性が変化します。

public class MyDbContext : DbContext 
{ 
    public DbSet<Node> Nodes { get; set; } 
    public DbSet<Attribute> Attributes { get; set; } 
    public DbSet<Tag> Tags { get; set; } 
    public DbSet<Alias> Aliases { get; set; } 
    public DbSet<AliasMapping> AliasMappings { get; set; } 
} 

ステップ#4:エイリアスマッピングを作成し、削除できるように、私はまた、拡張メソッドを更新しました。

public static class AliasExtensions 
{ 
    public static void CreateMapping(this MyDbContext context, object entity, Alias alias) 
    { 
     if (entity == null || alias == null) 
     { 
      return; 
     } 

     string mappingEntityPropertyName = entity.GetType().Name; 
     string entityKeyPropertyName = String.Concat(mappingEntityPropertyName, "Id"); 
     int entityId = entity.GetPropertyValue<int>(entityKeyPropertyName); 

     AliasMapping[] mappings = 
      context 
       .AliasMappings 
       .Where(mapping => mapping.AliasId == alias.AliasId) 
       .ToArray(); 

     if (mappings.Any(mapping => mapping.GetPropertyValue<int?>(entityKeyPropertyName) == entityId)) 
     { 
      // We already have the mapping between the specified entity and alias. 
      return; 
     } 

     bool usableMappingExists = true; 
     var usableMapping = mappings.FirstOrDefault(mapping => mapping.GetPropertyValue<int?>(entityKeyPropertyName) == null); 

     if (usableMapping == null) 
     { 
      usableMappingExists = false; 
      usableMapping = new AliasMapping() 
      { 
       Alias = alias 
      }; 
     } 

     usableMapping.SetPropertyValue(mappingEntityPropertyName, entity); 
     usableMapping.SetPropertyValue(entityKeyPropertyName, entityId); 

     if (!usableMappingExists) 
     { 
      context.AliasMappings.Add(usableMapping); 
     } 

     // This step is required here, I think due to using reflection. 
     context.SaveChanges(); 
    } 

    public static void RemoveMapping(this MyDbContext context, object entity, Alias alias) 
    { 
     if (entity == null || alias == null) 
     { 
      return; 
     } 

     string mappingEntityPropertyName = entity.GetType().Name; 
     string entityKeyPropertyName = String.Concat(mappingEntityPropertyName, "Id"); 
     int entityId = entity.GetPropertyValue<int>(entityKeyPropertyName); 

     AliasMapping[] mappings = 
      context 
       .AliasMappings 
       .Where(mapping => mapping.AliasId == alias.AliasId) 
       .ToArray(); 

     AliasMapping currentMapping = mappings.FirstOrDefault(mapping => mapping.GetPropertyValue<int?>(entityKeyPropertyName) == entityId); 

     if (currentMapping == null) 
     { 
      // There is no mapping between the specified entity and alias. 
      return; 
     } 

     currentMapping.SetPropertyValue(mappingEntityPropertyName, null); 
     currentMapping.SetPropertyValue(entityKeyPropertyName, null); 

     // This step is required here, I think due to using reflection. 
     context.SaveChanges(); 
    } 
} 

ステップ#5:は変更とそれを整列するコンソールアプリケーションの手順を更新しました。

class Program 
{ 
    static void Main(string[] args) 
    { 
     // Consider specify the appropriate database initializer! 
     // I use DropCreateDatabaseAlways<> strategy only for this example. 
     Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>()); 

     var aliases = 
      Enumerable 
       .Range(1, 9) 
       .Select(index => new Alias() { Name = String.Format("Alias{0:00}", index) }) 
       .ToList(); 

     var attributes = 
      Enumerable 
       .Range(1, 5) 
       .Select(index => new Attribute() { Name = String.Format("Attribute{0:00}", index) }) 
       .ToList(); 

     var nodes = 
      Enumerable 
       .Range(1, 5) 
       .Select(index => new Node() { Name = String.Format("Node{0:00}", index) }) 
       .ToList(); 

     var tags = 
      Enumerable 
       .Range(1, 5) 
       .Select(index => new Tag() { Name = String.Format("Tag{0:00}", index) }) 
       .ToList(); 

     using (var context = new MyDbContext()) 
     { 
      context.Aliases.AddRange(aliases); 
      context.Nodes.AddRange(nodes); 
      context.Attributes.AddRange(attributes); 
      context.Tags.AddRange(tags); 

      // Always save changes after adding an entity but before trying to create a mapping. 
      context.SaveChanges(); 

      // One Alias To Many Entities 
      context.CreateMapping(nodes[0], aliases[0]); 
      context.CreateMapping(nodes[1], aliases[0]); 
      context.CreateMapping(nodes[2], aliases[0]); 
      context.CreateMapping(nodes[3], aliases[0]); 
      context.CreateMapping(attributes[0], aliases[0]); 
      context.CreateMapping(attributes[1], aliases[0]); 
      context.CreateMapping(attributes[2], aliases[0]); 
      context.CreateMapping(tags[0], aliases[0]); 
      context.CreateMapping(tags[1], aliases[0]); 

      // One Entity To Many Aliases 
      context.CreateMapping(nodes[4], aliases[0]); 
      context.CreateMapping(nodes[4], aliases[1]); 
      context.CreateMapping(nodes[4], aliases[2]); 
      context.CreateMapping(attributes[3], aliases[1]); 
      context.CreateMapping(attributes[3], aliases[3]); 
      context.CreateMapping(tags[2], aliases[2]); 
      context.CreateMapping(tags[2], aliases[3]); 

      // Remove mapping 
      context.RemoveMapping(nodes[4], aliases[0]); 

      // Not really needed here as both 'CreateMapping' and 'RemoveMapping' save the changes 
      context.SaveChanges(); 
     } 

     Console.Write("Press any key to continue . . ."); 
     Console.ReadKey(true); 
    } 
} 

ご注意:RemoveMapping()には実体がそれに関連付けられていない場合であってもAliasMappingは削除されません!しかし、CreateMapping()は必要に応じて後で使用します。例えば。下のスクリーンショットを見て、チェックAliasMappingどこ実行結果についてId = 5

スクリーンショット:

AliasMapping

0

あなたは多対多の関係の話が、あなたの記事を読んでいた私は、それは可能性が高い「特別な1対多」の関係だと思います、 は、単一のNodeにAND/ORすることができ、単一のAttributeにAND/ORすることができ、単一のTagにマッピングすることができることが実際には「複数の1対1の」結合関係になります。

私はこのケースの解決策を見つけたと思います。

それはケースではないとAliasが複数のNodeにAND/OR複数のAttributeにAND/OR複数のTagにマッピングすることができるなら、私は下のこのソリューションはわずかな変化が必要だと思います。 :)

ステップ#1 - これらは私の例のモデルです

public class Node 
{ 
    [Key] 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public virtual AliasMapping AliasMapping { get; set; } 
} 

public class Attribute 
{ 
    [Key] 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public virtual AliasMapping AliasMapping { get; set; } 
} 

public class Tag 
{ 
    [Key] 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public virtual AliasMapping AliasMapping { get; set; } 
} 

public class Alias 
{ 
    [Key] 
    public int AliasId { get; set; } 
    public string Name { get; set; } 
    public virtual AliasMapping AliasMapping { get; set; } 
} 

ステップ#2 -

public class AliasMapping 
{ 
    [Key] 
    [ForeignKey("Alias")] 
    public int AliasId { get; set; } 

    public Alias Alias { get; set; } 

    [ForeignKey("Node")] 
    public int NodeId { get; set; } 

    public virtual Node Node { get; set; } 

    [ForeignKey("Attribute")] 
    public int AttributeId { get; set; } 

    public virtual Attribute Attribute { get; set; } 

    [ForeignKey("Tag")] 
    public int TagId { get; set; } 

    public virtual Tag Tag { get; set; } 
} 

カスタムマッピングテーブルを作成するステップ#3 - DbContext

public class MyDbContext : DbContext 
{ 
    public DbSet<Node> Nodes { get; set; } 
    public DbSet<Attribute> Attributes { get; set; } 
    public DbSet<Tag> Tags { get; set; } 
    public DbSet<Alias> Aliases { get; set; } 
    public DbSet<AliasMapping> AliasMappings { get; set; } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder 
      .Entity<AliasMapping>() 
      .HasOptional(mapping => mapping.Attribute) 
      .WithOptionalPrincipal(attribute => attribute.AliasMapping) 
      .Map(config => config.MapKey("AliasId")); 

     modelBuilder 
      .Entity<AliasMapping>() 
      .HasOptional(mapping => mapping.Node) 
      .WithOptionalPrincipal(node => node.AliasMapping) 
      .Map(config => config.MapKey("AliasId")); 

     modelBuilder 
      .Entity<AliasMapping>() 
      .HasOptional(mapping => mapping.Tag) 
      .WithOptionalPrincipal(tag => tag.AliasMapping) 
      .Map(config => config.MapKey("AliasId")); 
    } 
} 
を作成します

ステップ4 - リレーションシップの作成を容易にする拡張メソッドの作成

public static class AliasExtensions 
{ 
    public static void CreateMapping<TEntity>(this MyDbContext context, TEntity entity, Alias alias) 
    { 
     string mappingEntityPropertyName = typeof(TEntity).Name; 
     string entityKeyPropertyName = String.Concat(mappingEntityPropertyName, "Id"); 

     bool entityExists = true; 
     var mapping = context.AliasMappings.Find(alias.AliasId); 

     if (mapping == null) 
     { 
      entityExists = false; 
      mapping = new AliasMapping() 
      { 
       Alias = alias 
      }; 
     } 

     typeof(AliasMapping) 
      .GetProperty(mappingEntityPropertyName) 
      .SetValue(mapping, entity); 

     typeof(AliasMapping) 
      .GetProperty(entityKeyPropertyName) 
      .SetValue(mapping, typeof(TEntity).GetProperty("Id").GetValue(entity)); 

     if (!entityExists) 
     { 
      context.AliasMappings.Add(mapping); 
     } 
    } 
} 

ステップ#5 - 私はエイリアスおよび他のすべてのエンティティ間の多対多の関係を提供するために、私の解決策を更新し、この作業

class Program 
{ 
    static readonly Random rnd = new Random(DateTime.Now.TimeOfDay.Milliseconds); 

    static void Main(string[] args) 
    { 
     Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>()); 

     var aliases = 
      Enumerable 
       .Range(1, 9) 
       .Select(index => new Alias() { Name = String.Format("Alias{0:00}", index) }) 
       .ToList(); 

     var attributes = 
      Enumerable 
       .Range(1, 5) 
       .Select(index => new Attribute() { Name = String.Format("Attribute{0:00}", index) }) 
       .ToList(); 

     var nodes = 
      Enumerable 
       .Range(1, 5) 
       .Select(index => new Node() { Name = String.Format("Node{0:00}", index) }) 
       .ToList(); 

     var tags = 
      Enumerable 
       .Range(1, 5) 
       .Select(index => new Tag() { Name = String.Format("Tag{0:00}", index) }) 
       .ToList(); 

     using (var context = new MyDbContext()) 
     { 
      context.Aliases.AddRange(aliases); 
      context.Nodes.AddRange(nodes); 
      context.Attributes.AddRange(attributes); 
      context.Tags.AddRange(tags); 

      context.SaveChanges(); 

      // Associate aliases to attributes 
      attributes.ForEach(attribute => 
      { 
       var usableAliases = aliases.Where(alias => alias.AliasMapping?.Attribute == null).ToList(); 
       var selectedAlias = usableAliases[rnd.Next(usableAliases.Count)]; 
       context.CreateMapping(attribute, selectedAlias); 
      }); 

      // Associate aliases to nodes 
      nodes.ForEach(node => 
      { 
       var usableAliases = aliases.Where(alias => alias.AliasMapping?.Node == null).ToList(); 
       var selectedAlias = usableAliases[rnd.Next(usableAliases.Count)]; 
       context.CreateMapping(node, selectedAlias); 
      }); 

      // Associate aliases to tags 
      tags.ForEach(tag => 
      { 
       var usableAliases = aliases.Where(alias => alias.AliasMapping?.Tag == null).ToList(); 
       var selectedAlias = usableAliases[rnd.Next(usableAliases.Count)]; 
       context.CreateMapping(tag, selectedAlias); 
      }); 

      context.SaveChanges(); 
     } 

     Console.Write("Press any key to continue . . ."); 
     Console.ReadKey(true); 
    } 
} 
+0

うわー、ありがとうございました!ソリューションについての質問が1つあります。 ノードには、0または1ではなく、エイリアスのコレクションが必要ですか?エイリアステーブルで別のレコードを意味していたとしても、これを行うには滑らかな方法がありますか? – blgrnboy

+0

@blgrnboyエイリアスと他のすべてのエンティティとの多対多の関係を提供する新しい回答を追加しました。見てみな! ;) – Gabor

関連する問題