Dynamically build an object from a strongly typed

2019-06-11 16:59发布

问题:

Currently, am adding the properties and values to the object manually like this example and sending to Dapper.SimpleCRUD to fetch data from Dapper Orm. This is the desired output I would like to achieve.

object whereCriteria = null;
whereCriteria = new
{
    CountryId = 2,
    CountryName = "Anywhere on Earth",
    CountryCode = "AOE",
    IsActive = true
};

The following class should build the object in the above mentioned format and return the ready-made object.

public static class WhereClauseBuilder
{
    public static object BuildWhereClause(object model)
    {
        object whereObject = null;
        var properties = GetProperties(model);

        foreach (var property in properties)
        {
            var value = GetValue(property, model);

            //Want to whereObject according to the property and value. Need help in this part!!!
        }

        return whereObject;
    }

    private static object GetValue(PropertyInfo property, object model)
    {
        return property.GetValue(model);
    }

    private static IEnumerable<PropertyInfo> GetProperties(object model)
    {
        return model.GetType().GetProperties();
    }
}

This function WhereClauseBuilder.BuildWhereClause(object model) should return the object in expected format (mentiond above). Here is the implementation of how I would like to use.

public sealed class CountryModel
{
    public int CountryId { get; set; }
    public string CountryName { get; set; }
    public string CountryCode { get; set; }
    public bool IsActive { get; set; }
}

public class WhereClauseClass
{
    public WhereClauseClass()
    {
        var model = new CountryModel()
        {
            CountryCode = "AOE",
            CountryId = 2,
            CountryName = "Anywhere on Earth",
            IsActive = true
        };

        //Currently, won't return the correct object because the implementation is missing.
        var whereClauseObject = WhereClauseBuilder.BuildWhereClause(model);
    }
}

回答1:

Maybe something like that:

private const string CodeTemplate = @"
    namespace XXXX
    {
        public class Surrogate
        {
    ##code##
        }
    }";

public static Type CreateSurrogate(IEnumerable<PropertyInfo> properties)
{
    var compiler = new CSharpCodeProvider();
    var compilerParameters = new CompilerParameters { GenerateInMemory = true };
    foreach (var item in AppDomain.CurrentDomain.GetAssemblies().Where(x => !x.IsDynamic))
    {
        compilerParameters.ReferencedAssemblies.Add(item.Location);
    }

    var propertiesCode = 
        string.join("\n\n", from pi in properties
                            select "public " + pi.PropertyType.Name + " " + pi.Name + " { get; set; }");

    var source = CodeTemplate.Replace("##code##", propertiesCode);

    var compilerResult = compiler.CompileAssemblyFromSource(compilerParameters, source);
    if (compilerResult.Errors.HasErrors)
    {
        throw new InvalidOperationException(string.Format("Surrogate compilation error: {0}", string.Join("\n", compilerResult.Errors.Cast<CompilerError>())));
    }

    return compilerResult.CompiledAssembly.GetTypes().First(x => x.Name == "Surrogate");
}

And now use it:

public static object BuildWhereClause(object model)
{
    var properties = GetProperties(model);
    var surrogateType = CreateSurrogate(properties);
    var result = Activator.CreateInstance(surrogateType);

    foreach (var property in properties)
    {
        var value = GetValue(property, model);
        var targetProperty = surrogateType.GetProperty(property.Name);
        targetProperty.SetValue(result, value, null);
    }

    return result;
}

I didn't compile that. It's only written here. Maybe there are some errors. :-)

EDIT:

To use ExpandoObject you can try this:

public static object BuildWhereClause(object model)
{
    var properties = GetProperties(model);
    var result = (IDictionary<string, object>)new ExpandoObject();

    foreach (var property in properties)
    {
        var value = GetValue(property, model);
        result.Add(property.Name, value);
    }

    return result;
}

But I don't know whether this will work for you.