C# ASP.Net MVC dynamic object properties

2019-09-19 07:28发布

问题:

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

回答1:

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


回答2:

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();