Pergunta

EDIT: eu perdi um ponto crucial: .NET 2.0

Considere o caso onde eu tenho uma lista de itens não ordenados, por uma questão de simplicidade de um tipo como este:

class TestClass
{
    DateTime SomeTime;
    decimal SomePrice;

    // constructor
}

Eu preciso criar um relatório semelhante a saída, onde os preços totais para cada dia são acumulados. Deve haver uma linha para cada item, folled pelas linhas de resumo apropriados.

Tome este dados de teste:

List<TestClass> testList = new List<TestClass> { 
new TestClass(new DateTime(2008,01,01), 12),
new TestClass(new DateTime(2007,01,01), 20),
new TestClass(new DateTime(2008,01,01), 18)
};

A saída desejada seria algo como isto:

2007-01-01: 
20
Total: 20

2008-01-01: 
12
18
Total: 30

Qual é a melhor maneira de abordar tais situações? No caso de tal lista uma, gostaria de implementar a interface IComparable para TestClass, de modo que a lista pode ser classificada.

Para criar o próprio relatório, algo como isso poderia ser usado (vamos supor que temos métodos para tarefas como acumular os preços, mantendo o controle da data atual etc):

for (int i=0;i<testList.Count;i++)
{
    if (IsNewDate(testList[i]))
    {
        CreateSummaryLine();
        ResetValuesForNewDate();
    }

    AddValues(testList[i]);
}

// a final summary line is needed to include the data for the last couple of items.
CreateSummaryLine();

Isso funciona bem, mas eu tenho um sentimento estranho, tanto quanto o segundo "CreateSummaryLines" está em causa.

De que forma você lidar com tais situações (especialmente considerando o fato, o que precisamos para trabalhar com uma lista de <> de itens em vez de um pré-categorizados dicionário ou algo parecido)?

Foi útil?

Solução

[editar] Uma vez que você estiver usando o .NET 2.0 com C # 3.0, você pode usar LINQBridge para permitir isso.

LINQ; algo como:

        var groups = from row in testList
                  group row by row.SomeTime;
        foreach (var group in groups.OrderBy(group => group.Key))
        {
            Console.WriteLine(group.Key);
            foreach(var item in group.OrderBy(item => item.SomePrice))
            {
                Console.WriteLine(item.SomePrice);
            }
            Console.WriteLine("Total" + group.Sum(x=>x.SomePrice));
        }

Outras dicas

Ok, então se você não pode usar LINQ:

(estou usando var para economizar espaço, mas é fácil de traduzir para C # 2.0, se necessário ...)

var grouped = new SortedDictionary<DateTime, List<TestClass>>();
foreach (TestClass entry in testList) {
  DateTime date = entry.SomeTime.Date;
  if (!grouped.ContainsKey(date)) {
    grouped[date] = new List<TestClass>();
  }
  grouped[date].Add(entry);
}

foreach (KeyValuePair<DateTime, List<TestClass>> pair in testList) {
  Console.WriteLine("{0}: ", pair.Key);
  Console.WriteLine(BuildSummaryLine(pair.Value));
}

pergunta crucial: você está usando .NET 3.5, permitindo assim LINQ para ser usado? Se assim for, você pode agrupar por SomeTime.Date e depois processar cada data separadamente.

Se você tem enormes quantidades de dados, você pode encontrar o meu Premir LINQ projeto útil - mas LINQ outro modo direto para objetos é a sua melhor aposta .

Qual versão do C # /. NET você está usando? Se você é até à data, ter um olhar para LINQ. Isso permite agrupar seus dados. No seu caso, você poderia grupo por data:

var days = from test in testList group test by test.SomeTime;
foreach (var day in days) {
    var sum = day.Sum(x => x.SomePrice);
    Report(day, sum);
}

Desta forma, você pode relatar uma lista de valores para cada day, juntamente com a soma de dinheiro para esse dia.

A sua saída desejada sente como você quiser fazer uma estrutura de dados que representa seus dados de resumo. Você deseja emparelhar um encontro com uma lista de objetos testclass, em seguida, ter uma coleção desses. Você poderia usar um HashSet ou um dicionário para a coleção.

Para a síntese, estrutura de dados par pode implementar ToString para você.

Independentemente de como você gerar a saída do relatório, se você vai estar fazendo isso regularmente em sua aplicação considerar montar uma interface de relatórios e implementação de classes para esta interface para localizar a lógica para criar o relatório em um só lugar, em vez de dispersão o código para criar relatórios em toda a sua aplicação. Na minha experiência, as aplicações normalmente têm (ou crescer) requisitos para vários relatórios e, eventualmente, vários formatos.

Se você só tem um relatório, não se preocupe com isso até que você escreva o seu segundo, , mas, em seguida, começar a trabalhar em uma arquitetura de comunicação que faz com que seja mais fácil de estender e manter.

Por exemplo (e usando o código de @ Marc Gravell):

public interface IReport
{
   string GetTextReportDocument();
   byte[] GetPDFReportDocument();  // needing this triggers the development of interface
}

public class SalesReport : IReport
{
   public void AddSale( Sale newSale )
   {
       this.Sales.Add(newSale);  // or however you implement it
   }

   public string GetTextReportDocument()
   {
       StringBuilder reportBuilder = new StringBuilder();
       var groups = from row in this.Sales
                    group row by row.SomeTime.Date;
       foreach (var group in groups.OrderBy(group => group.Key))
       {            
          reportBuilder.AppendLine(group.Key);
          foreach(var item in group.OrderBy(item => item.SomePrice))            
          {
             reportBuilder.AppendLine(item.SomePrice);
          }
          reportBuilder.AppendLine("Total" + group.Sum(x=>x.SomePrice));
      }

      return reportBuilder.ToString();
   }

   public byte[] GetPDFReportDocument()
   {
        return PDFReporter.GenerateDocumentFromXML( this.ConvertSalesToXML() );
   }

... o resto é deixado como um exercício para o leitor ...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top