System.StackOverflowException

Posts   
1  /  2
 
    
BringerOD
User
Posts: 70
Joined: 15-Jul-2006
# Posted on: 26-Aug-2008 01:50:44   


        public override IQueryable<BusinessObject> GetQueryable()
        {
            //          return _transaction.CreateLinqProvider()
            //              .EntityIndex
            //              .Select(p =>  new BusinessObject
            //                               {
            //                                   ID = p.Identifier,
            //                                   Created = p.Created,
            //                                   Modified = p.Modified,
            //                                   ObjectType = (BusinessObjectType) p.TypeId
            //                               });

            return _transaction.CreateLinqProvider()
                .EntityIndex
                .Select(p => ConvertToModal(p));
        }

        public override BusinessObject ConvertToModal(IEntity2 entity)
        {
            var typeEntity = (EntityIndexEntity) entity;
            return new BusinessObject
                       {
                           ID = typeEntity.Identifier,
                           Created = typeEntity.Created,
                           Modified = typeEntity.Modified,
                           ObjectType = (BusinessObjectType) typeEntity.TypeId
                       };
        }

Once I call the .ToList() after getting the GetQueryable I get "An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll"

This code fails, but if I replace it with the commented out section above it works.

It seems this should work great.

I must be doing something wrong. Been working on it for hours. Hopefully someone can help me out.

Thanks,

Bryan

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 26-Aug-2008 08:37:40   

Hi Brian,

I think that is related to this:

Linq queries against a database, using the LinqMetaData queryable object can still contain in-memory constructs, like creating Lists, arrays, calling a method etc. Linq to LLBLGen Pro tries as much as possible to execute this code locally in-memory, if appropriate: either when the data is fetched and placed into objects, or when the query is constructed.

It can be that this isn't always possible, for example if you specify a method call on an in-memory object which relies on data known only during the query execution in the database. Typically this occurs when Linq to Object queries are merged with Linq to LLBLGen Pro queries. It's recommended that you don't merge these queries, unless you're sure it will work (and not throw an exception at runtime): keep database queries separated from in-memory (Linq to Objects) queries as much as possible: not only is it more clear what's executed where, but it's also less error prone.

David Elizondo | LLBLGen Support Team
BringerOD
User
Posts: 70
Joined: 15-Jul-2006
# Posted on: 26-Aug-2008 17:15:39   

Hmmm . .

"Typically this occurs when Linq to Object queries are merged with Linq to LLBLGen Pro queries."

This is not what is happening. All that I am doing is, as you can see, is creating a new object. I am not calling another Linq to Object construct.

This is a very simple projection.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 26-Aug-2008 17:51:26   

No, it's not a simple projection with the method included. simple_smile

The problem I think is that the entity you pass into the method is never materialized: the projection as seen in the tree is that the entity data has to be passed to the method.

Still the stack overflow is weird. I'll see if I can repro that.

Frans Bouma | Lead developer LLBLGen Pro
BringerOD
User
Posts: 70
Joined: 15-Jul-2006
# Posted on: 26-Aug-2008 17:58:31   

Thanks for the quick reply.

What I am trying to do is map my modal objects to my LLBLGen objects.

What I dont want to do is contantly keep writing the same mapping code for each entity.

This was my second attempt. I first used a method extension, but that ended with the same error.

Bryan

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 26-Aug-2008 18:23:30   

I found the stack overflow, though fixing that doesn't help, as I explained above: the data which is fed into the projector is simply a set of values, and it passes that to the method, which isn't going to work.

This does work though: public override IQueryable<BusinessObject> GetQueryable() { return (_transaction.CreateLinqProvider() .EntityIndex.ToList()).Select(p=>ConvertToModal(p)); }

This simply fetches the entities and then converts them to BO's.

Frans Bouma | Lead developer LLBLGen Pro
BringerOD
User
Posts: 70
Joined: 15-Jul-2006
# Posted on: 26-Aug-2008 18:31:32   

I see.

One of the design goals was to return an IQueryable of the modal. If I first convert it to a List the query is executed and filled. This would be a problem from large tables where I would want to filter them further.

I still don't get why when I create the BusinessObject in the select why that works and is different than calling a function to do it?

Can I write the function differently?

Bryan

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 26-Aug-2008 18:59:20   

BringerOD wrote:

I see.

One of the design goals was to return an IQueryable of the modal. If I first convert it to a List the query is executed and filled. This would be a problem from large tables where I would want to filter them further.

That's indeed a disadvantage.

I still don't get why when I create the BusinessObject in the select why that works and is different than calling a function to do it?

Can I write the function differently?

The code might compile but as it is converted to an expression tree which is converted to a query, that's not a guarantee teh code also makes sense. simple_smile

The commented out part, which creates the object in the projection, will simply result in a projection to custom class, which will instantiate a new object every row and pass the values through projectors to the new instance.

With the method call, a single projector is created, with a delegate which is a call to the method. At runtime the complete resultset is passed to the delegate.

I'll see if I can rewrite the method a bit. I'll then also attach the new build so you don't get a stackoverflow anymore .

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 26-Aug-2008 19:19:07   

I can't get it to work, because the projection engine simply pulls the first value from the resultset and converts it to the entity type at hand, and THEN passes it to method. This will always fail.

The problem is that if the entity has to be materialized first, you effectively get TWO projections: one from resultset to entity and one from entity to call to method with passing entity (the actual projection).

The workaround you obviously already have looked at and which is less ideal (in the light of extensibility of entity fields) is:

[Test]
public void CustomProjectionThroughMethod()
{
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        LinqMetaData metaData = new LinqMetaData(adapter);
        var q = from o in metaData.Order
                where o.CustomerId == "CHOPS"
                select ConvertToTarget(o.OrderId, o.CustomerId, o.OrderDate);

        int count = 0;
        foreach(var v in q)
        {
            Assert.AreEqual("CHOPS", v.CustomerId);
            count++;
        }
        Assert.AreEqual(8, count);
    }
}

private TargetObject ConvertToTarget(int orderId, string customerId, DateTime? orderDate)
{
    return new TargetObject() { OrderId = orderId, CustomerId=customerId, OrderDate = orderDate};
}

I've attached a debug build with the fix for your stackoverflow (however, I don't think you'll need it, as it's not doable)

Frans Bouma | Lead developer LLBLGen Pro
BringerOD
User
Posts: 70
Joined: 15-Jul-2006
# Posted on: 26-Aug-2008 21:23:43   

The following works in Linq to SQL. In the end its the same result though.



        public IQueryable<BusinessObject> GetQueryable()
        {
            var db = new LinqToSqlDataContext();

            return db.EntityIndexes
                .Select(p => Test1(p));
        }

        public BusinessObject Test1(EntityIndex entity)
        {
            var modal = new BusinessObject()
            {
                ID = entity.Identifier,
                Created = entity.Created,
                Modified = entity.Modified,
                ObjectType = (BusinessObjectType)entity.TypeID
            };

            return modal;
        }


This test below works in Linq to SQL, but fails in LLBLGen. This is irrelevant for me. Also, its not a bug in LLBLGen. Its just a difference. Now that I think about it, I like that it



     [TestMethod]
        public void test2()
        {
            var repos = new BusinessObjectRepository();
            var list = repos.GetQueryable().ToList();
        }

Now the next test I run fails on Linq to SQL. Which is what I was trying to do. . . I think I get it now.

How I am intereting this is there is no way for the LINQ engine to look inside the method and see what else it might be doing. Makes sense, but is disappointing.



        [TestMethod]
        public void test1()
        {
            var repos = new BusinessObjectRepository();
            var qry = repos.GetQueryable().Where(p => p.ObjectType == BusinessObjectType.Publisher);
            var list = qry.ToList();
        }


Thanks for your help, as always! simple_smile

Bryan

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 27-Aug-2008 10:45:52   

BringerOD wrote:

The following works in Linq to SQL. In the end its the same result though.



        public IQueryable<BusinessObject> GetQueryable()
        {
            var db = new LinqToSqlDataContext();

            return db.EntityIndexes
                .Select(p => Test1(p));
        }

        public BusinessObject Test1(EntityIndex entity)
        {
            var modal = new BusinessObject()
            {
                ID = entity.Identifier,
                Created = entity.Created,
                Modified = entity.Modified,
                ObjectType = (BusinessObjectType)entity.TypeID
            };

            return modal;
        }


This works in Linq to sql because they don't use a separate projection engine for the materialization of an object, they build one for every query in memory (hence the 'compiled query' feature)

Yesterday I've looked into making this possible, as it looks like a cool feature, but it would be a big change, if it was possible at all, because it requires a hard-coded codepath for 'if entity passed to method in projection then... '.

This test below works in Linq to SQL, but fails in LLBLGen. This is irrelevant for me. Also, its not a bug in LLBLGen. Its just a difference. Now that I think about it, I like that it



     [TestMethod]
        public void test2()
        {
            var repos = new BusinessObjectRepository();
            var list = repos.GetQueryable().ToList();
        }

Still, could you explain what exactly failed with our code? simple_smile I hate to have bugs / flaws in our code simple_smile

Now the next test I run fails on Linq to SQL. Which is what I was trying to do. . . I think I get it now.

How I am intereting this is there is no way for the LINQ engine to look inside the method and see what else it might be doing. Makes sense, but is disappointing.

Yes, though in theory it shouldn't be that problematic. the problem is rooted in the fact that the projection code has a method call which passes on an entity object, but due to the translation of the expression tree, that call is transfered into getting the fields from the entity from the DB and pass them to whatever is in the projection, not doing an entity materialization first. The reason it's not doing that is that materialization only takes place in the very outside of the query, i.e. the method call. simple_smile

It now compiles a lambda which reads the values from the datareader row into an object, but that would have to change into a call to a routine which materializes an entity. As this is far away from the linq code, (linq to sql has it in the same codebase), it's not directly possible to do that unfortunately.

Frans Bouma | Lead developer LLBLGen Pro
BringerOD
User
Posts: 70
Joined: 15-Jul-2006
# Posted on: 27-Aug-2008 17:51:43   

There is no bug.

I did not mean to say there was.

In my opinion, LLBLGen rocks!

simple_smile

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 27-Aug-2008 18:46:55   

BringerOD wrote:

There is no bug.

I did not mean to say there was.

In my opinion, LLBLGen rocks!

simple_smile

heh smile . I meant: you said: it failed, so I wanted to know: why/how (crash, wrong values, the error discussed in teh topicstart)... simple_smile

Frans Bouma | Lead developer LLBLGen Pro
BringerOD
User
Posts: 70
Joined: 15-Jul-2006
# Posted on: 27-Aug-2008 20:09:53   


       public override IQueryable<BusinessObject> GetQueryable()
        {
            return _transaction.CreateLinqProvider()
                .EntityIndex
                .Select(p => ConvertToModal(p));
        }



[TestMethod]
        public void test2()
        {
            var repos = new BusinessObjectRepository();
            var list = repos.GetQueryable().ToList();
        }

This works in Linq to SQL and fails in LINQ to LLBLGen. But, as soon as I try and add something to the where clause the Linq to SQL fails as well.

I actually would prefer it to fail. There reason is that it is the clearer answer. When Linq to SQL returns a non failure on the IQueryable, then fails when I query it, I think that is a bug. LLBLGen falls returning the IQueryable before I get a chance to fail.

Hope that makes sense.

Bryan

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 28-Aug-2008 10:38:40   

Ah I now understand what you mean! simple_smile .

Frans Bouma | Lead developer LLBLGen Pro
BringerOD
User
Posts: 70
Joined: 15-Jul-2006
# Posted on: 29-Aug-2008 09:40:48   

I think I might be closer to a solution.

This code works.



  public override IQueryable<Entity> GetQueryable()
        {
            var db = _transaction.CreateLinqProvider();
            var entity = db
                .EntityIndex
                .Select(TestMap()).AsQueryable();

            return entity;
        }

        public Func<EntityIndexEntity,Entity> TestMap()
        {
            return (p => new Entity
                             {
                                 ID = p.Identifier,
                                 Created = p.Created,
                                 Modified = p.Modified,
                                 ObjectType = (EntityType) p.TypeId
                             });
        }


Bryan

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 29-Aug-2008 10:31:52   

Whoa! Hats off, I never thought of that, that looks great!

Frans Bouma | Lead developer LLBLGen Pro
BringerOD
User
Posts: 70
Joined: 15-Jul-2006
# Posted on: 29-Aug-2008 18:24:54   

Spoke to soon. It starts to break when you set criteria for a joined object.

So I decided to test this with Linq to SQL. Turns out this all works in Linq to SQL. So I think I have found a bug.

Here is what I have as my test repository in Linq to SQL


 public IQueryable<Data.Contact> GetQueryable()
        {
            var db = new LinqToSqlDataContext();

            var qry = db.Contacts.Select(ContactEntityMap());

            return qry.AsQueryable();
        }

        public Func<Contact, Data.Contact> ContactEntityMap()
        {
            return (c => new Data.Contact
                             {
                                 ID = c.EntityIndex.Identifier,
                                 Created = c.EntityIndex.Created,
                                 Modified = c.EntityIndex.Modified,
                                 ObjectType = (EntityType)c.EntityIndex.TypeID,
                                 EmailHome = c.EmailHome,
                                 FirstName = c.FirstName,
                                 LastName = c.LastName,
                                 MiddleName = c.MiddleName,
                                 PhoneHome = c.PhoneHome,
                                 Salutation = c.Salutation,
                                 Suffix = c.Suffix,
                                 Title = c.Title,
                                 HomeAddress = c.EntityIndex.Addresses
                                                .Where(a => a.Identifier == c.Identifier
                                                            && a.AddressTypeID == Convert.ToInt32(AddressType.Home))
                                                            .Select(AddressEntityMap()).Single()
                             });
        }

        public Func<Address, Data.Address> AddressEntityMap()
        {
            return (a => new Data.Address
            {
                ID = a.Identifier,
                AddressType = (AddressType)a.AddressTypeID,
                Attention = a.Attention,
                Street1 = a.Street1,
                Street2 = a.Street2,
                City = a.City,
                StateOrProvince = a.StateOrProvince,
                PostalCode = a.PostalCode,
                Country = a.Country
            });
        }



Here is the tests I run on both a LLBLGen example of this and the Linq to SQL



var lqry1 = linqtoSQL.GetQueryable();
var lqry3 = lqry1.Where(p => p.HomeAddress.AddressType == AddressType.Home).ToList();
var lqry2 = lqry1.Where(p => p.ID == entity.ID).ToList();


This above code and test works for Linq to SQL. I have the exact same scenario setup for LLBLGen which fails. Also, this scenario fails for LLBLGen even if I don't use my mapping functions above.

Test method LeadManager.DataTests.ContactTests.Contact_Query_Random threw exception: SD.LLBLGen.Pro.ORMSupportClasses.ORMQueryExecutionException: An exception was caught during the execution of a retrieval query: An object or column name is missing or empty. For SELECT INTO statements, verify each column has a name. For other statements, look for empty alias names. Aliases defined as "" or [] are not allowed. Change the alias to a valid name.. Check InnerException, QueryExecuted and Parameters of this exception to examine the cause of this exception. ---> System.Data.SqlClient.SqlException: An object or column name is missing or empty. For SELECT INTO statements, verify each column has a name. For other statements, look for empty alias names. Aliases defined as "" or [] are not allowed. Change the alias to a valid name..

This error was generated when running the query against LLBLGen. This only occurs when I run a where against the joined object as in above.

This is a game changer for my scenario. I want to be able to map my domain objects and still return a real IQueryable. I cannot return a List that is IQueryable becuase I would always pull back all records. There are millions of records.

Bryan

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 29-Aug-2008 18:47:41   

COuld you formulate a small repro case for me? The query is now quite complex and I'm not sure if I can reproduce the query here exactly as you have it. The smaller the better.

Btw, if something breaks in our code and not in linq to sql could be possible, the opposite is also true, so it's not a given that our code has a bug per se, it could be that it's impossible to formulate the query because of the structure of our code and framework

It would also be great if you could post the query sql which goes wrong. It could be that it is caused by a bug which was fixed this week. The latest temp build is in this post: http://www.llblgen.com/tinyforum/GotoMessage.aspx?MessageID=78990&ThreadID=14189

Frans Bouma | Lead developer LLBLGen Pro
BringerOD
User
Posts: 70
Joined: 15-Jul-2006
# Posted on: 29-Aug-2008 18:50:29   

Completely understand!

I will have to get this later today. I will provide a apples to apples case as clear as I can.

Bryan

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 30-Aug-2008 10:41:10   

BringerOD wrote:

Completely understand!

I will have to get this later today. I will provide a apples to apples case as clear as I can.

Bryan

No worries. Though please try the linq provider I referred to in my previous post. That temp build makes sure the relations needed to reach related entities (which you use too in your where clause) are propagated upwards.

Frans Bouma | Lead developer LLBLGen Pro
BringerOD
User
Posts: 70
Joined: 15-Jul-2006
# Posted on: 30-Aug-2008 17:11:14   

Using the new DLL.

Failed to queue test run XXXXXXXXXXXX: Test Run deployment issue: The location of the file or directory 'c:\XXXXXXXXXX\bin\debug\SD.LLBLGen.Pro.LinqSupportClasses.NET35.dll' is not trusted.

FYI

BringerOD
User
Posts: 70
Joined: 15-Jul-2006
# Posted on: 30-Aug-2008 17:33:16   

caspol -machine -addfulltrust SD.LLBLGen.Pro.LinqSupportClasses.NET35.dll

Fixes it.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 30-Aug-2008 20:56:19   

So your problem is now solved? Or just the rights issue (which isn't something in the dll I think)

Frans Bouma | Lead developer LLBLGen Pro
BringerOD
User
Posts: 70
Joined: 15-Jul-2006
# Posted on: 30-Aug-2008 22:12:13   

The rights issue.

Still working on a example.

1  /  2