与实体框架的条件关联

时间:2012-03-27 13:12:22

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

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

---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...

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

2 个答案:

答案 0 :(得分:3)

我认为你可能能够达到的最接近于关系方和面向对象方的是将数据映射和对象模型到数据库中的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

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

现在也许你真的想要一个属性,返回值类型为“object”的值,就像在你的例子中一样。好吧,我们现在可以使用一个简单的抽象属性来做到这一点:

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属性/列,那么您可以添加它,但是您必须确保将其设置为某个适当的值,因为它不需要映射。

答案 1 :(得分:0)

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

首先,我尝试将Property值实现为泛型类型。然后我实现了每个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; }
}

这是该属性的类型。它有类似字符串的简单类型,它在db中保存为string,int等。例如,这个属性类型可以是“Name”,其简单类型是字符串,或者可以是“Price”,其简单类型将是浮。

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");
            });

所以,我的问题解决了。我想分享一下。