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

本文关键字:对象 LINQ 使用 | 更新日期: 2023-09-27 17:57:59

我有一个带有子集合的对象集合。为了使用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>

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

我会进入以下内容:

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

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

例如(并将您的第二个Person类重命名为PersonDto):

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

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

Mapper.AssertConfigurationIsValid();

然后使用从数据库中映射您的结果

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

以下是一个答案,或者根据您的观点使用代码隐藏来解决问题
我试图避免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
}

这是伪代码,可能无法编译。

自动将要素行作为列进行"枢轴旋转"仍然很有用。