Условная ассоциация с 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...
Но таким образом, если я хочу добавить другой тип значения, мне придется изменить структуру таблицы, и мне придется обновить сущность и объектные модели в моем проекте. Я не могу использовать сериализованные объекты, потому что мне нужно фильтровать данные по значению.Редактирование завершено
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 в сущности / таблице 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; }
}
Это тип имущества. Он имеет простой тип, такой как string, который хранится в 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
}
Это абстрактный базовый класс для таблиц значений, который Артур дал идею:
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");
});
Итак, моя проблема была решена. И я хотел поделиться этим.