EF6:多个相关实体的单一关系表

时间:2016-05-10 17:17:58

标签: entity-framework entity-framework-6

我有一个包含许多实体的EF模型,如节点,属性,标签等。

还有一个“别名”实体,几乎所有其他实体都可以与别名建立多对多关系。其中一个不受欢迎的事情是为跟踪这些关系而创建的表的数量(例如NodeAlias,AttributeAlias等)。

是否有任何设计方案可以将Alias映射到单个表中的所有其他实体?如果可能的话,我想的可能就是这些:

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

2 个答案:

答案 0 :(得分: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步:由于关系发生变化,MyDbContext可能已经简化,因为[ForeignKey]数据注释已足够。

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

答案 1 :(得分:0)

你在谈论多对多的关系但是阅读你的帖子我觉得它更可能是一个特殊的一对多关系#34;关系,实际上&#34;结合多个一对一&#34;关系,我发现Alias可以映射到单个Node和/或单个Attribute和/或单个Tag

我想我找到了这种情况的解决方案。

如果情况并非如此,Alias可以映射到多个Node和/或多个Attribute和/或多个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);
    }
}
相关问题