vwdewaal
vwdewaal

Reputation: 1005

Creating a class instance from Linq query result

I'm trying to create a class instance from my Linq query result. Since I have to do this for many classes, I'm trying to find the most suitable shortcut. I'm wondering whether I can make the "select" part of the query any shorter.

My class:

public class current_control_id_class
{
    public string asset_class { get; set; }
    public string region_code { get; set; }
    public string instance_code { get; set; }
    public int sdi_control_id { get; set; }
    public int rows_loaded { get; set; }  
}

My assignment function:

foreach (var results in query)
{
    foreach (PropertyInfo result in results.GetType().GetProperties())
    {
        string name = result.Name;

        foreach (PropertyInfo info in used.GetType().GetProperties())
        {
            if (result.Name == info.Name)
            {
                Console.WriteLine("Result {0} matches class {1} and the value is {2}", result.Name, info.Name, result.GetValue(results,null));
            }                        
        }
    }
}

My query (i know this works)

current_control_id_class used = new current_control_id_class();

var query =
    from result in t_sdi_current_control_id.AsQueryable()
    where result.asset_class == asset_class
    && result.region_code == region
    && result.instance_code == site
    select new current_control_id_class() { 
        rows_loaded = result.rows_loaded,
        sdi_control_id = result.sdi_control_id,
        asset_class = result.asset_class,
        hsbc_region_code = result.hsbc_region_code,
        hsbc_instance_code = result.hsbc_instance_code
    };

Upvotes: 1

Views: 1963

Answers (2)

Sam
Sam

Reputation: 42387

If you don't want to use a third-party library, here's some tested code to do it for you:

/// <summary>
/// Maps instances of <typeparam name="TSource"/> to new instances of
/// <typeparam name="TDestination"/> by copying across accessible public
/// instance properties whose names match.
/// </summary>
/// <remarks>
/// Internally uses a compiled Expression, so mapping should be quick at
/// the expense of <see cref="Mapper"/> initialisation.
/// </remarks>
public class Mapper<TSource, TDestination>
    where TDestination : new()
{
    readonly Func<TSource, TDestination> map;

    public Mapper()
    {
        this.map = GenerateMapping();
    }

    static Func<TSource, TDestination> GenerateMapping()
    {
        var sourceProperties = GetPublicInstancePropertiesWithAccessors<TSource>(property => property.GetGetMethod());
        var destinationProperties = GetPublicInstancePropertiesWithAccessors<TDestination>(property => property.GetSetMethod());

        var source = Expression.Parameter(typeof(TSource));
        var destination = Expression.Variable(typeof(TDestination));

        var copyPropertyValues = from sourceProperty in sourceProperties
                                 from destinationProperty in destinationProperties
                                 where sourceProperty.Name.Equals(destinationProperty.Name, StringComparison.Ordinal)
                                 select Expression.Assign(
                                     Expression.Property(destination, destinationProperty),
                                     Expression.Property(source, sourceProperty)
                                 );

        var variables = new[] { destination };
        var assignNewDestinationInstance = Expression.Assign(destination, Expression.New(typeof(TDestination)));
        var returnDestinationInstance = new Expression[] { destination };
        var statements =
            new[] { assignNewDestinationInstance }
            .Concat(copyPropertyValues)
            .Concat(returnDestinationInstance);
        var body = Expression.Block(variables, statements);
        var parameters = new[] { source };
        var method = Expression.Lambda<Func<TSource, TDestination>>(body, parameters);

        return method.Compile();
    }

    /// <summary>
    /// Gets public instance properties of <typeparamref name="T"/> that
    /// have accessible accessors defined by <paramref name="getAccessor"/>.
    /// </summary>
    static IEnumerable<PropertyInfo> GetPublicInstancePropertiesWithAccessors<T>(Func<PropertyInfo, MethodInfo> getAccessor)
    {
        var type = typeof(T);
        var publicInstanceProperties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
        return from property in publicInstanceProperties
               let accessor = getAccessor(property)
               where accessor != null
               select property;
    }

    public TDestination Map(TSource source)
    {
        return map(source);
    }
}

Use it like this:

//Keep this around so it gets re-used.
var mapper = new Mapper<t_sdi_current_control_id, current_control_id_class>();

var result = mapper.Map(value);

Upvotes: 0

Sam
Sam

Reputation: 42387

You might be able to use AutoMapper to map instances of t_sdi_current_control_id to instances of current_control_id_class:

First initialise the mapping:

Mapper.CreateMap<t_sdi_current_control_id, current_control_id_class>();

Then use it:

var query =
    from result in t_sdi_current_control_id.AsQueryable()
    where result.asset_class == asset_class
    && result.region_code == region
    && result.instance_code == site
    select Mapper.Map<current_control_id_class>(result);

Upvotes: 2

Related Questions