【问题标题】:Conditional Association with Entity Framework与实体框架的条件关联
【发布时间】:2012-04-11 00:35:45
【问题描述】:

我想创建与实体框架的条件关联。据我所知,我们无法创建条件外键,所以我无法在数据库服务器级别解决这个问题。 我有这样的表:

---Property---
int     id
string  Name
int     TypeId      --> Condition on this.
int     ValueId

---ValueString---
int     id
string  Value

---ValueInteger---
int     id
int     Value

---ValueBoolean---
int     id
bool    Value

现在,Property 表中的 TypeId 字段包含值的类型。例如,如果TypeId == 0,那么ValueId 指向ValueString 表,如果TypeId == 1,那么ValueId 指向ValueInteger 表等等。

我做了一些解决方法,但我卡在了某个地方:

我有一个这样的枚举:

public enum PropertyType
{
    String = 0, 
    Integer = 1,
    Boolean = 2,
    DateTime = 3,
    List = 4 
}

我实现了一个这样的部分类:

public partial class ProductProperty : EntityObject
{
    public object Value
    {
        get
        {
            switch (Property.Type)
            {
                case PropertyType.String:
                    return ValueString.Where(w => w.id == this.ValueId ).Select(s => s);//how to return?
                    break;
                case PropertyType.Integer:
                    return ValueInteger.Where(w => w.id == this.ValueId ).Select(s => s) //how to return?
                    break;
                case PropertyType.Boolean:
                    return ValueBoolean.Where(w => w.id == this.ValueId ).Select(s => s) //how to return?
                    break;
                case PropertyType.DateTime:
                    return ValueDateTime.Where(w => w.id == this.ValueId ).Select(s => s) //how to return?
                    break;
                default:
                    return null;
                    break;
            }
        }
        set
        {

        }
    }
}

但我不知道如何在 EntityObject 中访问上下文对象,因此无法访问 Property EntityObject 中的 Value* 表。

那么,这种方法是正确的还是我应该怎么做?如果是真的,如何在 EntityObject 中获取实体上下文对象?

编辑:如果你不建议这种方法,你会建议什么?请与我们分享您的意见。我认为,这种方法的最佳替代方案可能是这样的:

---Property---
int     id
string  ValueString
int     ValueInteger
bool    ValueBoolean
etc...

但是这样,如果我想添加另一个值类型,我将不得不更改表结构,并且我将不得不更新我的项目中的实体和对象模型。我不能使用序列化对象,因为我需要过滤值上的数据。 编辑结束

【问题讨论】:

  • 一些数据库 (Oracle) 允许这样的密钥。您没有提及您正在使用哪个数据库。话虽如此,这种设计并不适合标准 SQL DB 或 EF。我建议您以不同的方式解决您的问题。
  • 谢谢 Craig,我正在使用 MS SQL Server。我可以通过代码中的额外公共函数来解决这个问题。但这只会让我的团队成员感到困惑,而且它会使事情变得更复杂。我需要这样的方法来使用(string)SomeProperty.Value 或(int)SomeProperty.Value。我不想实现额外的函数来获取属性的值。

标签: entity-framework foreign-keys associations conditional entityobject


【解决方案1】:

我认为您可能能够在关系方面和面向对象方面最接近的方法是将对象模型映射到数据库中的 TPC 抽象,这已经非常接近表了你有的结构。为简单起见,我将在 EF 4.3.1 中使用 Code First 来展示这一点。

让我们像这样定义一个简单的对象模型:

public class Property
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int ValueId { get; set; }
    public virtual Value Value { get; set; }
}

public abstract class Value
{
    public int Id { get; set; }
}

public class ValueString : Value
{
    public string Value { get; set; }

    public override string ToString()
    {
        return "String value of " + Value;
    }
}

public class ValueInteger : Value
{
    public int Value { get; set; }

    public override string ToString()
    {
        return "Integer value of " + Value;
    }
}

public class ValueBoolean : Value
{
    public bool Value { get; set; }

    public override string ToString()
    {
        return "Boolean value of " + Value;
    }
}

(我添加了一些 ToString 方法只是为了方便我们在使用这些类时查看发生了什么。)

这可以使用 TPC 进行映射,这样每种类型都有自己的表:

public class PropertyAndValuesContext : DbContext
{
    public DbSet<Property> Properties { get; set; }
    public DbSet<Value> Values { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ValueString>().Map(
            m =>
            {
                m.MapInheritedProperties();
                m.ToTable("ValueString");
            });

        modelBuilder.Entity<ValueInteger>().Map(
            m =>
            {
                m.MapInheritedProperties();
                m.ToTable("ValueInteger");
            });

        modelBuilder.Entity<ValueBoolean>().Map(
            m =>
            {
                m.MapInheritedProperties();
                m.ToTable("ValueBoolean");
            });
    }
}

所以现在我们的表格与您在问题开始时提供的布局相匹配,只是缺少 TypeId 条件列,因为这里不需要它。

让我们编写一个初始化程序和一个控制台应用程序来添加一些测试数据并显示它:

public class TestInitializer : DropCreateDatabaseAlways<PropertyAndValuesContext>
{
    protected override void Seed(PropertyAndValuesContext context)
    {
        new List<Property>
        {
            new Property { Name = "PropWithBool", Value = new ValueBoolean { Id = 1, Value = true } },
            new Property { Name = "PropWithString1", Value = new ValueString { Id = 2, Value = "Magic" } },
            new Property { Name = "PropWithString2", Value = new ValueString { Id = 3, Value = "Unicorn" } },
            new Property { Name = "PropWithInt1", Value = new ValueInteger { Id = 4, Value = 6 } },
            new Property { Name = "PropWithInt2", Value = new ValueInteger { Id = 5, Value = 7 } },
        }.ForEach(p => context.Properties.Add(p));
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        Database.SetInitializer(new TestInitializer());

        using (var context = new PropertyAndValuesContext())
        {
            foreach (var property in context.Properties)
            {
                Console.WriteLine("{0} with {1}", property.Name, property.Value);
            }
        }
    }
}

运行此打印输出:

PropWithBool with Boolean value of True
PropWithString1 with String value of Magic
PropWithString2 with String value of Unicorn
PropWithInt1 with Integer value of 6
PropWithInt2 with Integer value of 7

您可以看到,我们可以轻松地添加不同类型的值,将它们存储在适当的表中,然后再查询这些值。

现在,也许您真的想要一个属性,该属性返回键入为“对象”的值,如您的示例中所示。好吧,我们现在可以用一个简单的抽象属性来做到这一点:

public abstract class Value
{
    public int Id { get; set; }

    public abstract object TheValue { get; set; }
}

public class ValueString : Value
{
    public string Value { get; set; }

    public override object TheValue
    {
        get { return Value; }
        set { Value = (string)value; }
    }

    public override string ToString()
    {
        return "String value of " + Value;
    }
}

public class ValueInteger : Value
{
    public int Value { get; set; }

    public override object TheValue
    {
        get { return Value; }
        set { Value = (int)value; }
    }

    public override string ToString()
    {
        return "Integer value of " + Value;
    }
}

public class ValueBoolean : Value
{
    public bool Value { get; set; }

    public override object TheValue
    {
        get { return Value; }
        set { Value = (bool)value; }
    }

    public override string ToString()
    {
        return "Boolean value of " + Value;
    }
}

如果你愿意,你也可以想象这样做:

public class Property
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int ValueId { get; set; }
    public virtual Value Value { get; set; }

    public object TheValue
    {
        get { return Value.TheValue; }
        set { Value.TheValue = value;  }
    }
}

最后,如果您确实需要 Property 实体/表中的 TypeId 属性/列,则可以添加它,但您必须确保将其设置为适当的值,因为它不需要映射。

【讨论】:

  • 哇,很好的答案。我现在正在阅读它,并试图通过在我的项目中应用来理解。成功后,我会接受这个作为答案。非常感谢。
【解决方案2】:

亚瑟的答案就是答案,但我想分享我的结果。

首先,我尝试将属性值实现为泛型类型。然后我实现了每个 ValueString、ValueInteger 等。从这个泛型类型驱动的类。它有效,但泛型类型方法在使用中造成了很多强制转换。所以我一直坚持对象值。

这是保存值和类型的属性类:

public class ProductProperty
{
    public int ProductPropertyId { get; set; }
    public Product Product { get; set; }
    public int TypeId { get; set; }
    public int ValueId { get; set; }
    [ForeignKey("TypeId")]
    public PropertyType Type { get; set; }
    [ForeignKey("ValueId")]
    public PropertyValue Value { get; set; }
}

这是属性的类型。它具有像字符串这样的简单类型,以字符串、整数等形式保存在数据库中。例如,此属性类型可以是“名称”,其简单类型是字符串,也可以是“价格”,其简单类型是浮动。

public class PropertyType
{
    public int PropertyTypeId { get; set; }
    [MaxLength(150)]
    public string Name { get; set; }

    //For before EF 5, there is no enum support
    [Column("SimpleType")]
    public int InternalSimpleType { get; set; }
    [NotMapped]
    public SimpleType SimpleType
    {
        get { return (SimpleType)InternalSimpleType; }
        set { InternalSimpleType = (int)value; }
    }

    public ICollection<ProductProperty> ProductProperties { get; set; }
}

public enum SimpleType : int
{
    String = 1, 
    Integer = 2,
    Float = 4,
    Boolean = 8,
    DateTime = 16,
    List = 32
}

这是 Arthur 提出的值表的抽象基类:

public abstract class PropertyValue
{
    [Key]
    public int PropertyValueId { get; set; }
    [NotMapped]
    public abstract object Value { get; set; }
}

这些是值类/表:

public class PropertyValueString : PropertyValue
{
    [Column("Value", TypeName="ntext")]
    public string InternalValue { get; set; }
    public override object Value
    {
        get
        {
            return (string)InternalValue;
        }
        set
        {
            InternalValue = (string)value;
        }
    }
}

public class PropertyValueInteger : PropertyValue
{
    [Column("Value")]
    public int InternalValue { get; set; }
    public override object Value
    {
        get
        {
            return (int)InternalValue;
        }
        set
        {
            InternalValue = (int)value;
        }
    }
}

public class PropertyValueBoolean : PropertyValue
{
    [Column("Value")]
    public bool InternalValue { get; set; }
    public override object Value
    {
        get
        {
            return (bool)InternalValue;
        }
        set
        {
            InternalValue = (bool)value;
        }
    }
}

public class PropertyValueFloat : PropertyValue
{
    [Column("Value")]
    public float InternalValue { get; set; }
    public override object Value
    {
        get
        {
            return (float)InternalValue;
        }
        set
        {
            InternalValue = (float)value;
        }
    }
}

public class PropertyValueDateTime : PropertyValue
{
    [Column("Value", TypeName = "datetime2")]
    public DateTime InternalValue { get; set; }
    public override object Value
    {
        get
        {
            return (DateTime)InternalValue;
        }
        set
        {
            InternalValue = (DateTime)value;
        }
    }
}

这将在从 DbContext 驱动的 conext 类中:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<PropertyValueString>().Map(
            m =>
            {
                m.MapInheritedProperties();
                m.ToTable("PropertyValueString");
            });

        modelBuilder.Entity<PropertyValueInteger>().Map(
            m =>
            {
                m.MapInheritedProperties();
                m.ToTable("PropertyValueInteger");
            });

        modelBuilder.Entity<PropertyValueBoolean>().Map(
            m =>
            {
                m.MapInheritedProperties();
                m.ToTable("PropertyValueBoolean");
            });

        modelBuilder.Entity<PropertyValueFloat>().Map(
            m =>
            {
                m.MapInheritedProperties();
                m.ToTable("PropertyValueFloat");
            });

        modelBuilder.Entity<PropertyValueDateTime>().Map(
            m =>
            {
                m.MapInheritedProperties();
                m.ToTable("PropertyValueDateTime");
            });

所以,我的问题解决了。我想分享这个。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多