Условная ассоциация с Entity Framework

Я хочу создать условную ассоциацию с 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

Теперь поле 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, поэтому мне не удалось получить доступ к таблицам Value* в свойстве EntityObject.

Итак, верен ли этот подход или что мне делать? если это правда, как я могу получить объект контекста сущности в EntityObject?

Изменить. Если вы не предлагаете этот подход, что бы вы предложили? Пожалуйста, поделитесь с нами своим мнением. Я думаю, что лучшей альтернативой этому подходу может быть что-то вроде этого:

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

Но таким образом, если я захочу добавить еще один тип значения, мне придется изменить структуру таблицы, и мне придется обновить модели сущностей и объектов в моем проекте. Я не могу использовать сериализованные объекты, потому что мне нужно фильтровать данные по значению. Редактирование завершено


person oruchreis    schedule 27.03.2012    source источник
comment
Некоторые БД (Oracle) позволяют использовать такой ключ. Вы не указываете, какую БД вы используете. Сказав это, этот дизайн не подходит ни для стандартной базы данных SQL, ни для EF. Я бы посоветовал вам решить вашу проблему по-другому.   -  person Craig Stuntz    schedule 27.03.2012
comment
Спасибо, Крейг, я использую MS SQL Server. Я могу решить эту проблему с помощью дополнительных общедоступных функций в коде. Но это только сбивает с толку членов моей команды и делает вещи более сложными. Мне нужен такой подход для использования, например (string)SomeProperty.Value или (int)SomeProperty.Value. Я не хочу реализовывать дополнительные функции для получения значения свойства.   -  person oruchreis    schedule 27.03.2012


Ответы (2)


Я думаю, самое близкое, что вы можете сделать, чтобы это соответствовало как реляционной стороне, так и объектно-ориентированной стороне, - это сопоставить и объектную модель с абстракцией TPC в базе данных, которая уже очень близка к структуре таблицы, которая у вас есть. . Для простоты я покажу это с помощью Code First в EF 4.3.1.

Давайте определим простую объектную модель следующим образом:

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

Наконец, если вам действительно нужно свойство/столбец TypeId в объекте/таблице свойств, вы можете добавить его, но вам нужно убедиться, что вы установили для него какое-то подходящее значение, поскольку оно не требуется для сопоставления.

person Arthur Vickers    schedule 28.03.2012
comment
Вау, отличный ответ. Я читаю это сейчас, и я пытаюсь понять, применяя в своем проекте. После успеха я приму это как ответ. Большое спасибо. - person oruchreis; 29.03.2012

Ответ Артура - это ответ, но я хочу поделиться своими результатами.

Во-первых, я попытался реализовать значения свойств как универсальный тип. И затем я реализовал каждую 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
}

Это абстрактный базовый класс для таблиц значений, идею которого подал Артур:

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

Это будет в классе conext, который управляется из DbContext:

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

Итак, моя проблема была решена. И я хотел поделиться этим.

person oruchreis    schedule 29.03.2012