The closest I think you may be able to get to that fits both on the relational side and on the object-oriented side is to map and object model to a TPC abstraction in the database, which is already very close to the table structure you have. For simplicity I'll show this using Code First in EF 4.3.1.
Let's define a simple object model like so:
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;
}
}
(I put in some ToString methods just to make it easy to see what's going on when we use these classes.)
This can be mapped using TPC such that each type gets its own table:
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");
});
}
}
So now the tables we have match the layout you provided at the start of your question, except that the TypeId conditional column is missing since it is not needed here.
Let's write an initializer and a console app to add some test data and display it:
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);
}
}
}
}
Running this prints out:
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
You can see that we're easily able to add different types of values, have them stored in the appropriate tables, and then query these values back.
Now maybe you really want a property the returns the value typed as "object" as in your example. Well, we can now do this with a simple abstract property:
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;
}
}
You could also imagine doing this if you wanted:
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; }
}
}
Finally, if you really need the TypeId property/column in the Property entity/table, then you can add it, but you'll have to make sure you set it to some appropriate value since it isn't needed for the mapping.