0

I am working on a ASP.NET MVC project, with C#, and EF code first.

I am required to add dynamic properties to entities. For example -

I have a car as a base object. I can add custom properties for it like engine power, length, color etc etc.

Properties can be boolean, int, string or select options (ie, i have to create checkbox, input or select html elements when a user inputs values for those properties).

Properties must have custom validation rules (i.e., required, number only, range only etc).

Can you guys give me any clues or direction how to accomplish this?

Thank you

Arghya C
  • 9,805
  • 2
  • 47
  • 66
Tomislav
  • 1
  • 1
  • 1
  • I suggest you to use ViewModel and then map to your entity. see this http://www.codeproject.com/Articles/1043977/Mapping-ViewModel-to-Model-in-ASP-NET-MVC-using-Im – Joel R Michaliszen Nov 21 '15 at 19:00
  • You should map the EP model to view model; add data annotation over the properties of view model. – user1672994 Nov 21 '15 at 19:01
  • The problem is i need to generate dynamic model from the database. The properties in the model can be added dynamicly and they have all data types (int, string, bool, List). Also, i need to save those properties in a table and that is the part I don't know how to do. – Tomislav Nov 21 '15 at 19:04
  • This pattern is called Entity-Attribute-Value (EAV). Some call it an anti-pattern, but there are situations where there's simply no better alternative. In short, you store the properties (Attributes) in one table and the Values in another one. The Values table has foreign keys to Attribute and the Entity. – Gert Arnold Nov 21 '15 at 22:10
  • Thank you, EAV is just what i need. – Tomislav Nov 22 '15 at 09:25

2 Answers2

2

If you truly have dynamic properties you won't be able to do this (directly) with EF6 since EF6 assumes a relation database. and a relational database needs to know which columns to expect.

Now you got 2 options.

Option1: use a non-relational database with EF 7. you can look here https://msdn.microsoft.com/en-us/magazine/dn890367.aspx for some more details about EF7 but basically in a non relation database you can store any json blob - so also your dynamic properties

Option 2: Use a key value pair within your object. and store those properties

class KeyValuePair {
   int Id {get; set;}
   string name {get; set;}
   string stringValue {get; set;}

}

class BaseObject {

    int Id {get; set;}
    list<KeyValuePair> dynamicProperties {get; set;}

}

Now your car can just inherit from this baseobject. You still need to do the work to create your KeyValuePair objects. (And if you want to store strings, ints etc you can make Different KeyValuePair types, one for each storage type)

Be carefull with performance though if you use dynamic properties like this.

Update:

If you want to validate a dynamic object like this you want to implement IValidatableObject

so you get

 class Car: BaseObject, IValidatableObject {
      public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
        /* code to validate your properties here, for example you need at least 1 engine, 4 wheels etc */

        yield return ValidationResult.Success;
    }
 }
Batavia
  • 2,497
  • 14
  • 16
  • I have never worked with EF7, how can I add properties of a different type (i.e. int, string, boolean, select items) and validate them through DataAnnotations? Is it possilbe to do this through some kind of polymorphism? – Tomislav Nov 21 '15 at 19:11
  • i don't think you can validate properties through dataannotations directly using dynamic properties. i did add IValidableObject, something that you can use to validate your object. – Batavia Nov 21 '15 at 19:19
  • also yes you would get KeyStringValie, KeyIntValue, etc all inherriting from BaseKeyValue. and polymorphism does apply there – Batavia Nov 21 '15 at 19:20
0

You can create and use tables in DB dynamically, although it's not so simply.

First, you'll need to store metadata about your tables — what are their names, what are properties they have, what are the types of those properties, and so on.

Second, you'll need to generate entities to access these tables, and also, EntityTypeConfiguration classes, like here:

public class Foo
{
    public int Id { get; set; }

    public string Name { get; set; }
}

public class FooTypeConfiguration : EntityTypeConfiguration<Foo>
{
    public FooTypeConfiguration()
    {
        ToTable("Foos");
        HasKey(t => t.Id);
        Property(t => t.Name).HasMaxLength(200)
                             .IsRequired();
    }
}

You can generate DLL dynamically without intermediate C# code with help of System.Reflection.Emit. Or you can generate C# code and use System.CodeDom.Compiler to compile it (this way is simpler). You can also try Roslyn compiler (but I don't have enough experience to advise it).

Third, you'll need to load compiled DLL and create DbContext using modelBuilder.Configurations.AddFromAssembly(...).

You can find required type in assembly and use it to access data:

string typeName = ...;
var type = dynamicLoadedAssembly.GetType(typeName);
var set = dbContext.Set(type); // non-generic DB Set

You can use System.Reflection or dynamic typing to work with these objects. Finally, if you'll generate C# code, you can generate properties and implementation of some interface to access these properties by names:

public interface IAccessorByName : IReadOnlyDictionary<string, object>
{
    object this[string name] { get; set; }
}

public Foo : IAccessorByName
{
    private static readonly IDictionary<string, Func<Foo, object>> getters = new Dictionary<string, Func<Foo, object>>
    {
        { "Id", (foo) => foo.Id },
        { "Name", (foo) => foo.Name },
    };

    private static readonly IDictionary<string, Action<Foo, object>> setters = new Dictionary<string, Action<Foo, object>>
    {
        { "Id", (foo, value) => { foo.Id = (int)value; } },
        { "Name", (foo, value) => { foo.Name = (string)value; } },
    };

    public int Id { get; set; }

    public string Name { get; set; }

    public object this[string name]
    {
       get { return getters[name](this); }
       set { setters[name](this, value); }
    }
}

With similar interface you can create, read, update, and delete objects dynamically:

string typeName = "Foo";
var fooType = dynamicLoadedAssembly.GetType(typeName);
var foo = (IAccessorByName)Activator.CreateInstance(fooType);

foo["Id"] = 1;
foo["Name"] = "Jon Skeet";

var fooSet = dbContext.Set(fooType);
fooSet.Add(foo);
dbContext.SaveChanges();
Mark Shevchenko
  • 7,937
  • 1
  • 25
  • 29
  • Would this apply to my problem: http://stackoverflow.com/questions/11486286/asp-net-mvc-3-editor-for-dynamic-property – Tomislav Nov 21 '15 at 20:10
  • @Tomislav, Razor problem, I think, can be solved with code like `if(model.GetType() == typeof(int)) ... else if (model.GetType() == typeof(bool)) ...` and so on. – Mark Shevchenko Nov 21 '15 at 20:18