Bug 24452 - yield statement used in foreach returns first record only
Summary: yield statement used in foreach returns first record only
Status: RESOLVED FIXED
Alias: None
Product: Class Libraries
Classification: Mono
Component: System.Data (show other bugs)
Version: 3.2.x
Hardware: PC Linux
: --- normal
Target Milestone: Untriaged
Assignee: Marek Safar
URL:
Depends on:
Blocks:
 
Reported: 2014-11-11 17:11 UTC by eetasoft
Modified: 2014-11-21 04:30 UTC (History)
2 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
Visual Studio solution to reproduce the issue (768.08 KB, application/x-zip-compressed)
2014-11-11 17:15 UTC, eetasoft
Details

Description eetasoft 2014-11-11 17:11:46 UTC
Yield statement in foreach loop returns only first record data on dynamic data.

Steps to reproduce:

Run attached MVC4 solution.

Observed output in browser:

A
A

Expected output:

A
B

Reported also in

https://github.com/npgsql/npgsql/issues/295
Comment 1 eetasoft 2014-11-11 17:15:44 UTC
Created attachment 8695 [details]
Visual Studio solution to reproduce the issue
Comment 2 eetasoft 2014-11-11 17:21:15 UTC
In Mono ASP .NET MVC4 application, WebGrid shows npgsql dynamic data last row value in every row.

In Mono code below and from attached solution returns

?column?
B
B

( in bug descrition it was wrong )

If running in windows it returns proper result:

?column?
A
B

Controller:

    public ActionResult Test()
    {
        var data = TestData();
        return View("ReportData", new ReportDataViewModel(data, ""));
    }

    IEnumerable<dynamic> TestData()
    {
        using (var connection = new NpgsqlConnection(ConnectionString()))
        {
            connection.Open();
            DbCommand command = (DbCommand)connection.CreateCommand();
            command.CommandText = "select 'A' union select 'B'";
            using (command)
            {
                IEnumerable<string> columnNames = null;
                using (DbDataReader reader = command.ExecuteReader())
                {
                    foreach (DbDataRecord record in reader)
                    {
                        if (columnNames == null)
                            columnNames = GetColumnNames(record);
                        yield return new CustomDynamicRecord(columnNames, record);
                    }
                }
            }
        }
    }

    static IEnumerable<string> GetColumnNames(DbDataRecord record)
    {
        for (int i = 0; i < record.FieldCount; i++)
        {
            yield return record.GetName(i);
        }
    }

    static string ConnectionString()
    {
        NpgsqlConnectionStringBuilder csb = new NpgsqlConnectionStringBuilder()
        {
            SearchPath = "public",
            Host = "localhost",
            Database = "eeva",
            UserName = "postgres",
            Password = "secret"
        };
        return csb.ConnectionString;
    }

CustomDynamicRecord class:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Dynamic;
using System.Globalization;
using System.Linq;
using WebMatrix.Data.Resources;

public sealed class CustomDynamicRecord : DynamicObject, ICustomTypeDescriptor
{
    public CustomDynamicRecord(IEnumerable<string> columnNames, IDataRecord record)
    {
        Columns = columnNames.ToList();
        Record = record;
    }

    public IList<string> Columns { get; private set; }

    private IDataRecord Record { get; set; }

    public object this[string name]
    {
        get
        {
            return GetValue(Record[name]);
        }
    }

    public object this[int index]
    {
        get
        {
            return GetValue(Record[index]);
        }
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = this[binder.Name];
        return true;
    }

    static object GetValue(object value)
    {
        if (DBNull.Value == value || value == null)
          return null;
       return value;
    }

    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return Columns;
    }

    private void VerifyColumn(string name)
    {
        if (!Columns.Contains(name, StringComparer.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException(
                String.Format(CultureInfo.CurrentCulture,
                              "Invalid Column Name " + name));
        }
    }

    AttributeCollection ICustomTypeDescriptor.GetAttributes()
    {
        return AttributeCollection.Empty;
    }

    string ICustomTypeDescriptor.GetClassName()
    {
        return null;
    }

    string ICustomTypeDescriptor.GetComponentName()
    {
        return null;
    }

    TypeConverter ICustomTypeDescriptor.GetConverter()
    {
        return null;
    }

    EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
    {
        return null;
    }

    PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
    {
        return null;
    }

    object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
    {
        return null;
    }

    EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
    {
        return EventDescriptorCollection.Empty;
    }

    EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
    {
        return EventDescriptorCollection.Empty;
    }

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
    {
        return ((ICustomTypeDescriptor)this).GetProperties();
    }

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
    {
        var properties = from columnName in Columns
                         let columnIndex = Record.GetOrdinal(columnName)
                         let type = Record.GetFieldType(columnIndex)
                         select new DynamicPropertyDescriptor(columnName, type);

        return new PropertyDescriptorCollection(properties.ToArray(), readOnly: true);
    }

    object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
    {
        return this;
    }

    private class DynamicPropertyDescriptor : PropertyDescriptor
    {
        private static readonly Attribute[] _empty = new Attribute[0];
        private readonly Type _type;

        public DynamicPropertyDescriptor(string name, Type type)
            : base(name, _empty)
        {
            _type = type;
        }

        public override Type ComponentType
        {
            get { return typeof(EevaDynamicRecord); }
        }

        public override bool IsReadOnly
        {
            get { return true; }
        }

        public override Type PropertyType
        {
            get { return _type; }
        }

        public override bool CanResetValue(object component)
        {
            return false;
        }

        public override object GetValue(object component)
        {
            EevaDynamicRecord record = component as EevaDynamicRecord;
            // REVIEW: Should we throw if the wrong object was passed in?
            if (record != null)
            {
                return record[Name];
            }
            return null;
        }

        public override void ResetValue(object component)
        {
            throw new InvalidOperationException(
                String.Format(CultureInfo.CurrentCulture,
                              "DataResources.RecordIsReadOnly", Name));
        }

        public override void SetValue(object component, object value)
        {
            throw new InvalidOperationException(
                String.Format(CultureInfo.CurrentCulture,
                              "DataResources.RecordIsReadOnly", Name));
        }

        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }
    }
}

View:

@inherits ViewBase<ViewModels.ReportDataViewModel>
@using System.Web.Helpers

@{ Layout = null;
 var gd = new WebGrid(source: Model.Rows, canSort: false , rowsPerPage:100 );
}

<!DOCTYPE html>
<html>
<head>
</head>
<body>
  @gd.GetHtml()
</body>
</html>
Model:

namespace ViewModels
{
    public class ReportDataViewModel : ViewModelBase
    {
        public IEnumerable<dynamic> Rows { get; set; }

        public ReportDataViewModel(IEnumerable<dynamic> rows)
        {
            Rows = rows;
        }
    }
}
Comment 3 Marek Safar 2014-11-18 08:18:38 UTC
Attached sample fails to build for me with

Controllers/ReportController.cs(24,59): error CS0030: Cannot convert type `Npgsql.NpgsqlCommand' to `System.Data.Common.DbCommand'
Comment 4 eetasoft 2014-11-18 13:55:52 UTC
Complete testcase is in 

http://wikisend.com/download/851310/yieldstatementinforeachreturnsfirstrecord.zip

link is valid for 90 days.
This is 11MB and bugziiila did'nt allow to attach it.
Fixing compiler erorr is easy, just change type to interface in few places.
Comment 5 Marek Safar 2014-11-20 11:00:54 UTC
I still cannot reproduce it. What url should I hit to see the error?
Comment 6 eetasoft 2014-11-20 11:05:20 UTC
Url is  Report/Test

This  is specified in solution settings. This is the only url in this solution. There is single controller Report which contains single method Test
Comment 7 Marek Safar 2014-11-21 04:29:55 UTC
Fixed in master and 3.12 branch

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.


Create a new report for Bug 24452 on Developer Community or GitHub if you have new information to add and do not yet see a matching report.

  • Export the original title and description: Developer Community HTML or GitHub Markdown
  • Copy the title and description into the new report. Adjust them to be up-to-date if needed.
  • Add your new information.

In special cases on GitHub you might also want the comments: GitHub Markdown with public comments


Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.

Related Links: