Filling entity and related entities from Stored Procedure

Posts   
 
    
omborddata
User
Posts: 42
Joined: 18-Apr-2012
# Posted on: 04-Jun-2015 11:44:02   

Hi!

I have an entity for which I have created a stored procedure to fetch the data. We use adapter.FetchProjection and that works fine. Now we also want to fill a related entity with data from the same stored procedure, how can we do that?

Entity structure: DrillRoundEntity is the root entity. Each DrillRoundEntity has a relation to one DrillEntity.

So how do we fill DrillRoundEntity.Drill? We really need to use a stored procedure for the datafetching because we have complex filtering on that data that can't be done easily with QuerySpec.

Regards,

Christian

Walaa avatar
Walaa
Support Team
Posts: 14994
Joined: 21-Aug-2005
# Posted on: 04-Jun-2015 14:11:37   

How is data sent from the SP? One resultSet or 2 resultSets?

omborddata
User
Posts: 42
Joined: 18-Apr-2012
# Posted on: 04-Jun-2015 16:38:37   

The data is sent in 2 resultsets.

Walaa avatar
Walaa
Support Team
Posts: 14994
Joined: 21-Aug-2005
# Posted on: 04-Jun-2015 19:59:03   

Use the sample code in docs to fetch the 2 resultsets into 2 separate collections.

Then with a simple loop you can assign related entities from the 2nd collection into the first collection entities' related entity navigator, based on PK-Fk matching.

omborddata
User
Posts: 42
Joined: 18-Apr-2012
# Posted on: 05-Jun-2015 09:25:58   

Hi, yes that is how I'm doing it now. Thought it might be a way to do it automatically like LLBLGen normally do it.

Walaa avatar
Walaa
Support Team
Posts: 14994
Joined: 21-Aug-2005
# Posted on: 05-Jun-2015 11:27:46   

That's like hat LLBLGen does when it fetches a collection of entities and their related entities (prefetchPaths). It issues 2 fetches and internally sync entities together with their related entities.

omborddata
User
Posts: 42
Joined: 18-Apr-2012
# Posted on: 05-Jun-2015 11:28:58   

Ok, good to know! Then I will continue to do it like that.

Thanks for the quick response!

omborddata
User
Posts: 42
Joined: 18-Apr-2012
# Posted on: 23-Jun-2015 15:34:30   

A related issue. I've noticed that when using the adapter.FetchProjection, all the fetched entities has their IsNew property set to True.

Why is that and how do I not set them to True without looping through all the lists. They shouldn't be set to True since they are fetched from the database.

omborddata
User
Posts: 42
Joined: 18-Apr-2012
# Posted on: 23-Jun-2015 15:46:18   

The entities are also marked as IsDirty...

Walaa avatar
Walaa
Support Team
Posts: 14994
Joined: 21-Aug-2005
# Posted on: 23-Jun-2015 17:50:15   

May I ask, why don't you fetch entities and their related entities using normal FetchEntity/FetchEntityCollection methods, with prefetch Paths, why the need for StoredProcedures?

omborddata
User
Posts: 42
Joined: 18-Apr-2012
# Posted on: 23-Jun-2015 18:08:08   

It is a very complex sql statement. A SP has also proven to be A lot faster in this case..

Walaa avatar
Walaa
Support Team
Posts: 14994
Joined: 21-Aug-2005
# Posted on: 23-Jun-2015 18:15:08   

As far as I can see it, in this case, to the system you are not fetching entities, you are fetching records from a SP, and creating new entities at the client side where you project these records.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39888
Joined: 17-Aug-2003
# Posted on: 24-Jun-2015 08:40:28   

what projector are you using and what version of llblgen are you using?

Frans Bouma | Lead developer LLBLGen Pro
omborddata
User
Posts: 42
Joined: 18-Apr-2012
# Posted on: 24-Jun-2015 09:15:00   

Hi Frans,

I'm using the DataProjectorToIEntityCollection2 and the version of LLBLGen is the latest, 4.2

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39888
Joined: 17-Aug-2003
# Posted on: 24-Jun-2015 10:02:00   

omborddata wrote:

Hi Frans,

I'm using the DataProjectorToIEntityCollection2 and the version of LLBLGen is the latest, 4.2

Ok. It's a little unfortunate. The thing is, we can't change its behavior now as that would be a breaking change (as some code might rely on it). It's not that hard to work around it though. Below is the projector you're using, however I've adjusted it to set IsNew to false and to use the Forced write method so ischanged and isdirty isn't set.


////////////////////////////////////////////////////////////////////////////////////////////////////////
// LLBLGen Pro is (c) 2002-2011 Solutions Design. All rights reserved.
// http://www.llblgen.com
// The sourcecode for the ORM Support classes has been made available to LLBLGen Pro licensees
// so they can modify, update and/or extend it. Distribution of this sourcecode in textual, non-compiled, 
// non-binary form to non-licensees is prohibited. Distribution of binary compiled versions of this 
// sourcecode to non-licensees has been granted under the following license.
////////////////////////////////////////////////////////////////////////////////////////////////////////
// COPYRIGHTS:
// Copyright (c)2002-2011 Solutions Design. All rights reserved.
// http://www.llblgen.com
// 
// The ORM Support classes library sourcecode is released to LLBLGen Pro licensees under the 
// following license:
// --------------------------------------------------------------------------------------------
// 
// Redistribution and use of the sourcecode in compiled, binary forms, with or without modification, 
// are permitted provided that the following conditions are met: 
//
// 1) Redistributions must reproduce the above copyright notice, this list of 
//  conditions and the following disclaimer in the documentation and/or other materials 
//  provided with the distribution. 
// 2) Redistribution of the sourcecode in textual, non-binary, non-compiled form is prohibited.
// 
// THIS SOFTWARE IS PROVIDED BY SOLUTIONS DESIGN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOLUTIONS DESIGN OR CONTRIBUTORS BE LIABLE FOR 
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
//
// The views and conclusions contained in the software and documentation are those of the authors 
// and should not be interpreted as representing official policies, either expressed or implied, 
// of Solutions Design. 
//////////////////////////////////////////////////////////////////////
// Contributers to the code:
//      - Frans Bouma [FB]
//////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Collections;
using System.Data;

namespace SD.LLBLGen.Pro.ORMSupportClasses
{
    /// <summary>
    /// Projector engine which projects raw projection result data onto new entities which are added to a single entitycollection.
    /// </summary>
    /// <remarks>Adapter specific</remarks>
    public class DataProjectorToIEntityCollection2Custom : IEntityDataProjector, IGeneralDataProjector
    {
        #region Class Member Declarations
        private readonly IEntityCollectionCore _destination;
        private readonly Dictionary<string, PropertyDescriptor> propertyDescriptors;
        #endregion

        public DataProjectorToIEntityCollection2Custom(IEntityCollection2 destination)
        {
            if(destination == null)
            {
                throw new ArgumentNullException("destination", "destination can't be null");
            }
            if(destination.EntityFactoryToUse == null)
            {
                throw new ArgumentException("destination doesn't have an EntityFactory set.", "destination");
            }

            _destination = destination;
            IEntityCore newInstance = _destination.EntityFactoryToUse.Create();
            PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(newInstance);

            propertyDescriptors = new Dictionary<string, PropertyDescriptor>(properties.Count);
            foreach(PropertyDescriptor descriptor in properties)
            {
                propertyDescriptors.Add(descriptor.Name, descriptor);
            }
        }


        /// <summary>
        /// Adds a new projection result to the container contained into this instance. The container has to be set in the constructor.
        /// </summary>
        /// <param name="propertyProjectors">List of property projectors used to create the projection result</param>
        /// <param name="rawProjectionResult">The raw projection result.</param>
        void IEntityDataProjector.AddProjectionResultToContainer(List<IEntityPropertyProjector> propertyProjectors, object[] rawProjectionResult)
        {
            this.AddProjectionResultToContainer(propertyProjectors, rawProjectionResult);
        }


        /// <summary>
        /// Adds a new projection result to the container contained into this instance. The container has to be set in the constructor.
        /// </summary>
        /// <param name="valueProjectors">List of value projectors used to create the projection result</param>
        /// <param name="rawProjectionResult">The raw projection result.</param>
        void IGeneralDataProjector.AddProjectionResultToContainer(List<IDataValueProjector> valueProjectors, object[] rawProjectionResult)
        {
            this.AddProjectionResultToContainer(valueProjectors, rawProjectionResult);
        }


        /// <summary>
        /// Initializes the projector, using the projectors if necessary.
        /// </summary>
        /// <param name="valueProjectors">The value projectors.</param>
        /// <param name="dataSource">The data source. Open datareader to project from.</param>
        public virtual void Initialize(List<IDataValueProjector> valueProjectors, IDataReader dataSource)
        {
            // nop
        }


        /// <summary>
        /// Performs the actual projection 
        /// </summary>
        /// <param name="projectors">List of projectors used to create the projection result</param>
        /// <param name="rawProjectionResult">The raw projection result.</param>
        private void AddProjectionResultToContainer(IList projectors, object[] rawProjectionResult)
        {
            IEntityCore newInstance = _destination.EntityFactoryToUse.Create();
            bool fieldSet = false;
            for(int i = 0; i < projectors.Count; i++)
            {
                IProjector projector = (IProjector)projectors[i];
                var fieldIndex = newInstance.Fields.GetFieldIndex(projector.ProjectedResultName);
                object value = rawProjectionResult[i];
                if((value != null) && value.Equals(DBNull.Value))
                {
                    value = null;
                }
                if(fieldIndex < 0)
                {
                    // set via property descriptor
                    PropertyDescriptor property;
                    if(propertyDescriptors.TryGetValue(projector.ProjectedResultName, out property))
                    {
                        property.SetValue(newInstance, value);
                        fieldSet = true;
                    }
                }
                else
                {
                    newInstance.Fields.ForcedValueWrite(fieldIndex, value);
                    fieldSet = true;
                }
            }

            if(!fieldSet && (rawProjectionResult.Length == 1) && rawProjectionResult[0].GetType().IsAssignableFrom(newInstance.GetType()))
            {
                // special case: m:1/1:1 related entity is added. This ends up in this projection routine because the end result is
                // a collection of entities. Typical example: from o in md.Orders select o.Customer;
                _destination.Add((IEntityCore)rawProjectionResult[0]);
            }
            else
            {
                _destination.Add(newInstance);
            }
            newInstance.IsNew = false;
        }
    }
}

I hope it compiles (as I changed the code outside vs.net).

In general if a proc resultset is used, it's often more useful to map a typedview on the resultset. You can then fetch that typedview with queryspec, but in the case of writable / mutateable data that's of course not an option.

It's not a new problem, see: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=15392 (that code is similar to the one I posted above but won't work with v4 as the architecture slightly changed) And we know it's 6 years old, and the issue has been in our JIRA for quite some time but never got a high priority. I've marked it to be fixed for v5 as we can make breaking changes going into that version.

Hope this helps.

Frans Bouma | Lead developer LLBLGen Pro
omborddata
User
Posts: 42
Joined: 18-Apr-2012
# Posted on: 24-Jun-2015 10:12:49   

Sounds great Frans, will try out the class. Is v5 targeted for release next year?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39888
Joined: 17-Aug-2003
# Posted on: 24-Jun-2015 15:26:03   

omborddata wrote:

Sounds great Frans, will try out the class. Is v5 targeted for release next year?

No, this year. simple_smile (fingers crossed of course, it's software after all wink )

Frans Bouma | Lead developer LLBLGen Pro
omborddata
User
Posts: 42
Joined: 18-Apr-2012
# Posted on: 24-Jun-2015 15:29:23   

Great! Yeah, know all about that too well. ;-)