使用LINQ将子对象展平为属性

时间:2014-04-11 15:25:30

标签: c# linq-to-objects

我有一个带有子集合的对象集合。 我希望将其展平以用于LINQ的报告目的。

我不知道这是否可能。

例如,人物对象列表,每个人物对象都有一个子列表。

伪代码:

public sealed class Person
{
    public Name { get; set;}
    IEnumerable<Feature> Features
}

public sealed class Feature
{
    public FeatureName { get; set;} 
    public FeatureValue { get; set;}    
}

数据:

John
    Height 183
    Sex    Male

Jane
    Height 160
    Sex    Female
    Additional Test

必需的输出:

Name  Height  Sex      Additional

John  183     Male

Jane  160     Female   Test

实际上我想绑定到:

class Person
{
    public Name { get; set;}
    public Height { get; set;}
    public Sex { get; set;}
    public Additional { get; set;}
}

编辑:使用带有Activator.CreateInstance的动态类型,如下所示,调用一个采用基本Person类型的构造函数:

_results = from person in _people select Activator.CreateInstance(_personWithFeaturesType, person);

创建以下答案:

System.Linq.Enumerable.WhereSelectEnumerableIterator<Person,object>

调试时扩展结果视图是我创建并存储在_personWithFeaturesType成员变量中的新类型的列表。

我不知道返回了什么,是一个用对象键入的Person对象列表? 第三方网格中的WPF绑定似乎无法处理:

System.Linq.Enumerable.WhereSelectEnumerableIterator<Person,object>

但确实处理:

IEnumerable<Person>

3 个答案:

答案 0 :(得分:1)

我会进入类似的事情:

List<Person> persons = new List<Person>();
persons.Add(new Person()
{
    Name = "John",
    Features = new List<Feature>()
    {
        new Feature() { FeatureName = "Height", FeatureValue = "183" },
        new Feature() { FeatureName = "Sex", FeatureValue = "Male" }
    }
});
persons.Select(p => new MappedPerson()
{
   Name = p.Name,
   Height = p.Features.Where(f => f.FeatureName == "Height").DefaultIfEmpty(Feature.NullFeature).First().FeatureValue,
   Sex = p.Features.Where(f => f.FeatureName == "Sex").DefaultIfEmpty(Feature.NullFeature).First().FeatureValue
});

答案 1 :(得分:1)

您无法自动执行此操作。一旦从数据库中检索到,您就需要设置映射以在对象级别执行此操作。您可以考虑使用类似AutoMapper的内容来配置映射,因为这至少可以让您测试是否为每个属性设置了映射。

例如(并重命名第二个Person类PersonDto):

Mapper.CreateMap<Person, PersonDto>()
    .ForMember(dest => dest.Height, opt => opt.MapFrom(
        src => src.Features.FirstOrDefault(f => f.FeatureName == "Height").FeatureValue);

将自动映射具有相同名称的属性。您可能需要处理不单独存在的功能 - 我不记得是否自动处理了这个功能。您可以通过调用以下方法验证是否已将所有属性映射到目标对象上:

Mapper.AssertConfigurationIsValid();

然后使用以下方法从数据库映射结果:

var results = context.People.Include(p => p.Features).ToList();
var report = Mapper.Map<IEnumerable<PersonDto>>(results);

答案 2 :(得分:0)

以下是根据您的观点使用代码的答案或解决方法 我试图通常为MVVM避免代码,但在这种情况下使用大量可怕的代码似乎没什么意义。

该列在第三方网格的_Loaded事件中动态添加:

DataGrid.Column featureFromList = new DataGrid.Column();
featureFromList.DisplayMemberBindingInfo = new DataGrid.DataGridBindingInfo();
featureFromList.DisplayMemberBindingInfo.Path = new PropertyPath("Features", null);
featureFromList.DisplayMemberBindingInfo.Path.Path = "Features";
featureFromList.DisplayMemberBindingInfo.ReadOnly = true;
featureFromList.DisplayMemberBindingInfo.Converter = new Converters.FlattenedPersonConverter();
featureFromList.DisplayMemberBindingInfo.ConverterParameter = "Height"; //hard coded, but would be read from database/other objects

转换器获取值列表并使用ConverterParameter作为关键字获取相关值:

[ValueConversion(typeof(object), typeof(object))]
class FlattenedPersonConverter: IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        if (value == null || ((IEnumerable<Feature>)value).Count == 0)
        {
            return null;
        }
        else
        {
            return ((Feature)((IEnumerable<Feature>)value)[parameter.ToString()]).FeatureValue;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }

    #endregion
}

这是伪造的,可能无法编译。

对于&#34; pivot&#34;它仍然有用。功能行自动作为列。