Projection onto EntityCollection leaves DbValue null

Posts   
 
    
tomahawk
User
Posts: 169
Joined: 02-Mar-2005
# Posted on: 27-Feb-2009 01:27:33   

v2.6.08.1013

I am using a stored proc projection to load various EntityCollections (similarly to . The problem is that although the CurrentValue of the Entity fields are set properly, the DbValue is not set (still null), which causes plenty of problems for editing down the road in UI.

Can this be corrected? Or is there a reasonable explanation that I'm missing?

Thanks, Josh

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 27-Feb-2009 09:17:09   

I'll have to ask you this although I'm not sure it would make a difference: Would you please try the latest available build, coz you are using an old one?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 27-Feb-2009 10:29:32   

tomahawk wrote:

v2.6.08.1013

I am using a stored proc projection to load various EntityCollections (similarly to . The problem is that although the CurrentValue of the Entity fields are set properly, the DbValue is not set (still null), which causes plenty of problems for editing down the road in UI.

Can this be corrected? Or is there a reasonable explanation that I'm missing?

Thanks, Josh

The projector to entitycollection is a class which works but it isn't a class which has the most optimized codepath.

There are a couple of things with this projector. Please open the DataProjectorToEntityCollection.cs file in the runtime lib and look at the AddProjectionResultToContainer method for understanding: (2 classes are in that file, one for ss and one for adapter) - it creates new entities and sets field values through the SetNewFieldValue method. this method makes the field become changed. - It's also not the fastest way to set values in an entity through projection though it's the one which always works and always touches the validators for example. It uses SetNewFieldValue, which is slower than the direct ForcedCurrentValueWrite(value, value)

So it has some disadvantages in its current state for some situations, like the dirty writes. However, in other situations, like projecting entities to new entities from a collection/view, it is exactly what you want (new entities, with dirty fields and no DbValue set)

I.o.w., there's no single routine to get all situations right. I admit it should have been an option in the projector itself, but alas, that's not the case. I'll make that change in v3.

What you could do is the following. Below I've copied the projector's sourcecode and have altered the code so it will produce a new entity which isn't dirty and which has DBValue set as well. Use an instance of THIS projector for the proc projections.


/// <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 IEntityCollection2 _destination;
    private Dictionary<string, PropertyDescriptor> propertyDescriptors;
    #endregion

    /// <summary>
    /// Initializes a new instance of the <see cref="DataProjectorToIEntityCollection2Custom"/> class.
    /// </summary>
    /// <param name="destination">The destination of the data. It's required that the destination collection has a factory set.</param>
    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;
        IEntity2 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>
    /// 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 )
    {
        IEntity2 newInstance = _destination.EntityFactoryToUse.Create();
        bool fieldSet = false;
        // set fields. This is done through property descriptors. 
        for( int i = 0; i < projectors.Count; i++ )
        {
            IProjector projector = (IProjector)projectors[i];
            IEntityField2 field = newInstance.Fields[projector.ProjectedResultName];
            object value = rawProjectionResult[i];
            if((value != null) && value.Equals(DBNull.Value))
            {
                value = null;
            }
            if(field == null)
            {
                // set via property descriptor
                PropertyDescriptor property = null;
                if( propertyDescriptors.TryGetValue( projector.ProjectedResultName, out property ) )
                {
                    property.SetValue(newInstance, value);
                    fieldSet = true;
                }
            }
            else
            {
                // set through field object.
                field.ForcedCurrentValueWrite(value, 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((IEntity2)rawProjectionResult[0]);
        }
        else
        {
            newInstance.IsNew = false;
            _destination.Add(newInstance);
        }
    }
}

Frans Bouma | Lead developer LLBLGen Pro
tomahawk
User
Posts: 169
Joined: 02-Mar-2005
# Posted on: 27-Feb-2009 20:28:26   

Thank you!