0

Having written this question out, and created a MCVE, it sounds a bit like homework, but really it isn't... I'm unfortunately too old to be set homework.

I'm trying to write a small app to populate a database I'm working on with a 'pyramid' structure. There's one original member, who refers 10 members. Each of these referrals can have 10 referrals. Each of those, has 10 referrals. And so on...

I'm trying to fill the database with a maximum number of members (which is supplied)

If I set the maximum number of members to 100,000 - this works. 200,000 however, throws a StackOverflow exception.

I'm pretty sure it's down to me not terminating the 'fanning out' early enough. But I can't for the life of me figure out where.

Note, my MCVE below is using Dapper for simplicity sake, hence the simple INSERT INTO statement

public class MemberPopulator
{
    private readonly SqlConnection connection;

    private const string MemberSql = @"
            INSERT INTO Members (FirstName, LastName, ReferralId, Active, Created) 
            VALUES (@FirstName, @LastName, @ReferralId, @Active, @Created);
            SELECT CAST(SCOPE_IDENTITY() as int)";

    private int TotalMemberCount;
    private const int MaxMemberCount = 200000;

    public MemberPopulator()
    {
        connection = new SqlConnection("Data Source=localhost;Initial Catalog=MyTestDb;Integrated Security=True");
    }

    public void CreateMembers()
    {
        //clear members
        connection.Execute("TRUNCATE TABLE Members");

        //create the 'original' member (top of pyramid)
        var originalMemberId = connection.Query<int>(MemberSql, new Member
        {
            FirstName = "FirstName Goes Here",
            ReferralId = 0
        }).Single();

        //now we have 1 total members
        TotalMemberCount = 1;

        //recursively create members, starting with original member,
        RecursiveCreate(new[] { originalMemberId });
    }

    private void RecursiveCreate(IEnumerable<int> referralMemberIds)
    {
        //don't recurse if we've already got enough members
        if (TotalMemberCount >= MaxMemberCount)
            return;

        foreach (var referralId in referralMemberIds)
        {
            //Create 10 members
            var refs = CreateReferredMembers(referralId, 10);

            RecursiveCreate(refs);
        }
    }

    private IEnumerable<int> CreateReferredMembers(int referralId, int numberOfReferrals)
    {
        var referredMemberIds = new List<int>();

        for (var i = 0; i < numberOfReferrals; i++)
        {
            if (TotalMemberCount >= MaxMemberCount)
                break;

            var member = new Member
            {
                FirstName = "FirstName Goes Here",
                ReferralId = referralId
            };

            var memberId = connection.Query<int>(MemberSql, member).Single();
            referredMemberIds.Add(memberId);
            TotalMemberCount++;
        }

        return referredMemberIds;
    }
}
  • 1
    Recursion is only viable when you know the recursion level is minimal. 200,000 is a lot! Remember that recursion is nice, but can always be replaced with other loops. It is never necessary. – TyCobb Apr 17 '17 at 19:37
  • ^ What he said. But if you insist on using recursion, you can try [setting the stack size](http://stackoverflow.com/questions/2556938/how-to-change-stack-size-for-a-net-program) to a larger value. – John Wu Apr 17 '17 at 19:58

1 Answers1

1

The stack in C# is set to 1MB for 32bit applications or 4MB for 64bit applications by default. This is suitable for most applications. In case you need more please follow the guidance in the net (for example this one).

In case you do not know exactly the level of recursion I would suggest to simulate recursion by using a Stack or Queue datatype.

public class MemberPopulator
{
    private readonly SqlConnection connection;

    private const string MemberSql = @"
            INSERT INTO Members (FirstName, LastName, ReferralId, Active, Created) 
            VALUES (@FirstName, @LastName, @ReferralId, @Active, @Created);
            SELECT CAST(SCOPE_IDENTITY() as int)";

    private int TotalMemberCount;
    private const int MaxMemberCount = 200000;

    public MemberPopulator()
    {
        connection = new SqlConnection("Data Source=localhost;Initial Catalog=MyTestDb;Integrated Security=True");
    }

    public void CreateMembers()
    {
        //clear members
        connection.Execute("TRUNCATE TABLE Members");

        //create the 'original' member (top of pyramid)
        var originalMemberId = connection.Query<int>(MemberSql, new Member
        {
            FirstName = "FirstName Goes Here",
            ReferralId = 0
        }).Single();

        //now we have 1 total members
        TotalMemberCount = 1;

        //recursively create members, starting with original member,
        NonRecursiveCreate(originalMemberId);
    }

    private void NonRecursiveCreate(int root)
    {
        Queue<int> members = new Queue<int>();

        members.Enqueue(root);

        while (members.Any() && TotalMemberCount < MaxMemberCount)
        {

            var referralId = members.Dequeue();
            //Create 10 members
            var refs = CreateReferredMembers(referralId, 10);
            foreach (int i in refs)
            {
                members.Enqueue(i);
            }
        }
    }

    private IEnumerable<int> CreateReferredMembers(int referralId, int numberOfReferrals)
    {
        var referredMemberIds = new List<int>();

        for (var i = 0; i < numberOfReferrals; i++)
        {
            if (TotalMemberCount >= MaxMemberCount)
                break;

            var member = new Member
            {
                FirstName = "FirstName Goes Here",
                ReferralId = referralId
            };

            var memberId = connection.Query<int>(MemberSql, member).Single();
            referredMemberIds.Add(memberId);
            TotalMemberCount++;
        }

        return referredMemberIds;
    }
}
Community
  • 1
  • 1
Stephen Reindl
  • 5,659
  • 2
  • 34
  • 38