简体   繁体   English

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

[英]flatten child objects into properties with LINQ

I have a collection of objects with a child collection. 我有一个带有子集合的对象集合。 I want to flatten it for reporting purposes with LINQ. 我想使用LINQ对其进行展平。

I don't know if this is possible. 我不知道这是否可能。

For example a list of person objects, each with a child list of features. 例如,一个人物对象列表,每个对象都有一个特征子列表。

Pseudocode: 伪代码:

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

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

Data: 数据:

John
    Height 183
    Sex    Male

Jane
    Height 160
    Sex    Female
    Additional Test

Required output: 要求的输出:

Name  Height  Sex      Additional

John  183     Male

Jane  160     Female   Test

Effectively I want to bind to: 实际上,我想绑定到:

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

Edit: Using a dynamic type with Activator.CreateInstance as follows to call a constructor that takes the base Person type: 编辑:将动态类型与Activator.CreateInstance一起使用,如下所示以调用采用基本Person类型的构造函数:

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

Creates the following answer: 创建以下答案:

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

Expanding the resultsview when debugging it is a list of the new type I've created and stored in the _personWithFeaturesType member variable. 调试时扩展resultsview是我创建并存储在_personWithFeaturesType成员变量中的新类型的列表。

I don't understand what's being returned, it that a list of Person objects keyed with an object? 我不明白返回了什么,那是用对象键控的Person对象列表吗? The WPF binding in the 3rd party grid doesn't seem to handle: 第三方网格中的WPF绑定似乎无法处理:

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

but does handle: 但可以处理:

IEnumerable<Person>

I would go into something like: 我会遇到类似:

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

You won't be able to do this automatically. 您将无法自动执行此操作。 You'll need to set up a mapping to do this at the object level once it's been retrieved from the database. 一旦从数据库中检索到它,就需要设置一个映射以在对象级别执行此操作。 You could consider using something like AutoMapper to configure your mappings, as this will at least allow you to test that you have mappings set up for each property. 您可以考虑使用类似AutoMapper的工具来配置映射,因为这至少将允许您测试是否已为每个属性设置了映射。

For example (and renaming your second Person class PersonDto): 例如(并重命名第二个Person类PersonDto):

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

The properties with the same names will be mapped automatically. 具有相同名称的属性将自动映射。 You may need to handle the feature not being present separately - I don't remember if this gets handled by automapper automatically. 您可能需要处理不单独存在的功能-我不记得此功能是否由automapper自动处理。 You can verify that you have all properties mapped on your destination object by calling: 您可以通过调用以下命令来验证是否已将所有属性映射到目标对象上:

Mapper.AssertConfigurationIsValid();

Then map your results from the database using: 然后使用以下方法从数据库映射结果:

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

Here's an answer or work around using code behind depending on your viewpoint. 这是一个答案或根据您的观点在后面使用代码的变通方法。
I'm trying to avoid code behind generally for MVVM, but there seems little point using lots of horrendous code instead in this case. 我试图避免通常为MVVM编写的代码落后,但在这种情况下,使用大量可怕的代码似乎毫无意义。

The column is dynamically added in the _Loaded event for the 3rd party grid: 该列动态添加到第三方网格的_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

A converter takes the list of values and gets the relevant value using the ConverterParameter as the key: 转换器获取值列表,并使用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
}

This is psuedocode and might not compile. 这是伪代码,可能无法编译。

It still would be useful to "pivot" the rows of features as columns across automatically. 自动将要素行“透视”为列仍然很有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM