【问题标题】:flatten child objects into properties with LINQ使用 LINQ 将子对象展平为属性
【发布时间】:2014-04-11 15:25:30
【问题描述】:

我有一个带有子集合的对象集合。 我想将其展平以使用 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 对象列表吗? 第 3 方网格中的 WPF 绑定似乎无法处理:

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

但确实处理:

IEnumerable<Person>

【问题讨论】:

  • 每个人都有相同的特征,还是有些人有一个特征而有些人有其他?
  • 实际上是一个键值列表,有些是通用的,有些不是。
  • 特征列表也是用户可定义的,并且来自数据库表。我希望避免在运行时使用 PropertyBuilder 等为每个功能动态创建一个具有属性的类

标签: c# linq-to-objects


【解决方案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
});

【讨论】:

    【解决方案2】:

    您将无法自动执行此操作。一旦从数据库中检索到对象级别,您就需要设置一个映射来执行此操作。您可以考虑使用 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);
    

    【讨论】:

    • 我需要查看 AutoMapper、Value Injecter 等作为练习,这可能需要一些时间。所以这可能会在稍后阶段被标记为答案......
    【解决方案3】:

    根据您的观点,这里有一个答案或使用代码解决方法。
    我试图避免 MVVM 的一般代码落后,但在这种情况下使用大量可怕的代码似乎没什么意义。

    列在 _Loaded 事件中为 3rd 方网格动态添加:

    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
    }
    

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

    自动将特征行作为列“透视”仍然很有用。

    【讨论】:

    • 这种在单元格后面有一个值列表的方法的缺陷是,您可能需要做更多的工作才能使用网格的内置功能,例如摘要。
    猜你喜欢
    • 1970-01-01
    • 2021-02-19
    • 2021-11-24
    • 1970-01-01
    • 2015-02-03
    • 2018-12-21
    • 2017-06-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多