/ * Copyright ( C ) 2008 - 2011 Jordan Marr
This library is free software ; you can redistribute it and / or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation ; either
version 3 of the License , or ( at your option ) any later version .
This library is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
Lesser General Public License for more details .
You should have received a copy of the GNU Lesser General Public
License along with this library . If not , see < http : //www.gnu.org/licenses/>. */
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.Text ;
using System.Data ;
using System.Data.Common ;
using System.Linq ;
using Marr.Data.Mapping ;
using System.Reflection ;
namespace Marr.Data
{
/// <summary>
/// Holds metadata about an object graph that is being queried and eagerly loaded.
/// Contains all metadata needed to instantiate the object and fill it with data from a DataReader.
/// Does not iterate through lazy loaded child relationships.
/// </summary>
internal class EntityGraph : IEnumerable < EntityGraph >
{
private MapRepository _repos ;
private EntityGraph _parent ;
private Type _entityType ;
private Relationship _relationship ;
private ColumnMapCollection _columns ;
private RelationshipCollection _relationships ;
private List < EntityGraph > _children ;
private object _entity ;
private GroupingKeyCollection _groupingKeyColumns ;
private Dictionary < string , EntityReference > _entityReferences ;
internal IList RootList { get ; private set ; }
internal bool IsParentReference { get ; private set ; }
/// <summary>
/// Recursively builds an entity graph of the given parent type.
/// </summary>
/// <param name="entityType"></param>
public EntityGraph ( Type entityType , IList rootList )
: this ( entityType , null , null ) // Recursively constructs hierarchy
{
RootList = rootList ;
}
/// <summary>
/// Recursively builds entity graph hierarchy.
/// </summary>
/// <param name="entityType"></param>
/// <param name="parent"></param>
/// <param name="relationship"></param>
private EntityGraph ( Type entityType , EntityGraph parent , Relationship relationship )
{
_repos = MapRepository . Instance ;
_entityType = entityType ;
_parent = parent ;
_relationship = relationship ;
IsParentReference = ! IsRoot & & AnyParentsAreOfType ( entityType ) ;
if ( ! IsParentReference )
{
_columns = _repos . GetColumns ( entityType ) ;
}
_relationships = _repos . GetRelationships ( entityType ) ;
_children = new List < EntityGraph > ( ) ;
Member = relationship ! = null ? relationship . Member : null ;
_entityReferences = new Dictionary < string , EntityReference > ( ) ;
if ( IsParentReference )
{
return ;
}
// Create a new EntityGraph for each child relationship that is not lazy loaded
foreach ( Relationship childRelationship in this . Relationships )
{
if ( ! childRelationship . IsLazyLoaded )
{
_children . Add ( new EntityGraph ( childRelationship . RelationshipInfo . EntityType , this , childRelationship ) ) ;
}
}
}
public MemberInfo Member { get ; private set ; }
/// <summary>
/// Gets the parent of this EntityGraph.
/// </summary>
public EntityGraph Parent
{
get
{
return _parent ;
}
}
/// <summary>
/// Gets the Type of this EntityGraph.
/// </summary>
public Type EntityType
{
get { return _entityType ; }
}
/// <summary>
/// Gets a boolean than indicates whether this entity is the root node in the graph.
/// </summary>
public bool IsRoot
{
get
{
return _parent = = null ;
}
}
/// <summary>
/// Gets a boolean that indicates whether this entity is a child.
/// </summary>
public bool IsChild
{
get
{
return _parent ! = null ;
}
}
/// <summary>
/// Gets the columns mapped to this entity.
/// </summary>
public ColumnMapCollection Columns
{
get { return _columns ; }
}
/// <summary>
/// Gets the relationships mapped to this entity.
/// </summary>
public RelationshipCollection Relationships
{
get { return _relationships ; }
}
/// <summary>
/// A list of EntityGraph objects that hold metadata about the child entities that will be loaded.
/// </summary>
public List < EntityGraph > Children
{
get { return _children ; }
}
/// <summary>
/// Adds an entity to the appropriate place in the object graph.
/// </summary>
/// <param name="entityInstance"></param>
public void AddEntity ( object entityInstance )
{
_entity = entityInstance ;
// Add newly created entityInstance to list (Many) or set it to field (One)
if ( this . IsRoot )
{
RootList . Add ( entityInstance ) ;
}
else if ( _relationship . RelationshipInfo . RelationType = = RelationshipTypes . Many )
{
var list = _parent . _entityReferences [ _parent . GroupingKeyColumns . GroupingKey ]
. ChildLists [ _relationship . Member . Name ] ;
list . Add ( entityInstance ) ;
}
else // RelationTypes.One
{
_repos . ReflectionStrategy . SetFieldValue ( _parent . _entity , _relationship . Member . Name , entityInstance ) ;
}
EntityReference entityRef = new EntityReference ( entityInstance ) ;
_entityReferences . Add ( GroupingKeyColumns . GroupingKey , entityRef ) ;
InitOneToManyChildLists ( entityRef ) ;
}
/// <summary>
/// Searches for a previously loaded parent entity and then sets that reference to the mapped Relationship property.
/// </summary>
public void AddParentReference ( )
{
var parentReference = FindParentReference ( ) ;
_repos . ReflectionStrategy . SetFieldValue ( _parent . _entity , _relationship . Member . Name , parentReference ) ;
}
/// <summary>
/// Concatenates the values of the GroupingKeys property and compares them
/// against the LastKeyGroup property. Returns true if the values are different,
/// or false if the values are the same.
/// The currently concatenated keys are saved in the LastKeyGroup property.
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
public bool IsNewGroup ( DbDataReader reader )
{
bool isNewGroup = false ;
// Get primary keys from parent entity and any one-to-one child entites
GroupingKeyCollection groupingKeyColumns = this . GroupingKeyColumns ;
// Concatenate column values
KeyGroupInfo keyGroupInfo = groupingKeyColumns . CreateGroupingKey ( reader ) ;
if ( ! keyGroupInfo . HasNullKey & & ! _entityReferences . ContainsKey ( keyGroupInfo . GroupingKey ) )
{
isNewGroup = true ;
}
return isNewGroup ;
}
/// <summary>
/// Gets the GroupingKeys for this entity.
/// GroupingKeys determine when to create and add a new entity to the graph.
/// </summary>
/// <remarks>
/// A simple entity with no relationships will return only its PrimaryKey columns.
/// A parent entity with one-to-one child relationships will include its own PrimaryKeys,
/// and it will recursively traverse all Children with one-to-one relationships and add their PrimaryKeys.
/// A child entity that has a one-to-one relationship with its parent will use the same
/// GroupingKeys already defined by its parent.
/// </remarks>
public GroupingKeyCollection GroupingKeyColumns
{
get
{
if ( _groupingKeyColumns = = null )
_groupingKeyColumns = GetGroupingKeyColumns ( ) ;
return _groupingKeyColumns ;
}
}
private bool AnyParentsAreOfType ( Type type )
{
EntityGraph parent = _parent ;
while ( parent ! = null )
{
if ( parent . _entityType = = type )
{
return true ;
}
parent = parent . _parent ;
}
return false ;
}
private object FindParentReference ( )
{
var parent = this . Parent . Parent ;
while ( parent ! = null )
{
if ( parent . _entityType = = _relationship . MemberType )
{
return parent . _entity ;
}
parent = parent . Parent ;
}
return null ;
}
/// <summary>
/// Initializes the owning lists on many-to-many Children.
/// </summary>
/// <param name="entityInstance"></param>
private void InitOneToManyChildLists ( EntityReference entityRef )
{
// Get a reference to the parent's the childrens' OwningLists to the parent entity
for ( int i = 0 ; i < Relationships . Count ; i + + )
{
Relationship relationship = Relationships [ i ] ;
if ( relationship . RelationshipInfo . RelationType = = RelationshipTypes . Many )
{
try
{
IList list = ( IList ) _repos . ReflectionStrategy . CreateInstance ( relationship . MemberType ) ;
_repos . ReflectionStrategy . SetFieldValue ( entityRef . Entity , relationship . Member . Name , list ) ;
// Save a reference to each 1-M list
entityRef . AddChildList ( relationship . Member . Name , list ) ;
}
catch ( Exception ex )
{
throw new DataMappingException (
string . Format ( "{0}.{1} is a \"Many\" relationship type so it must derive from IList." ,
entityRef . Entity . GetType ( ) . Name , relationship . Member . Name ) ,
ex ) ;
}
}
}
}
/// <summary>
/// Gets a list of keys to group by.
/// </summary>
/// <remarks>
/// When converting an unnormalized set of data from a database view,
/// a new entity is only created when the grouping keys have changed.
/// NOTE: This behavior works on the assumption that the view result set
/// has been sorted by the root entity primary key(s), followed by the
/// child entity primary keys.
/// </remarks>
/// <returns></returns>
private GroupingKeyCollection GetGroupingKeyColumns ( )
{
// Get primary keys for this parent entity
GroupingKeyCollection groupingKeyColumns = new GroupingKeyCollection ( ) ;
groupingKeyColumns . PrimaryKeys . AddRange ( Columns . PrimaryKeys ) ;
// The following conditions should fail with an exception:
// 1) Any parent entity (entity with children) must have at least one PK specified or an exception will be thrown
// 2) All 1-M relationship entities must have at least one PK specified
// * Only 1-1 entities with no children are allowed to have 0 PKs specified.
if ( ( groupingKeyColumns . PrimaryKeys . Count = = 0 & & _children . Count > 0 ) | |
( groupingKeyColumns . PrimaryKeys . Count = = 0 & & ! IsRoot & & _relationship . RelationshipInfo . RelationType = = RelationshipTypes . Many ) )
throw new MissingPrimaryKeyException ( string . Format ( "There are no primary key mappings defined for the following entity: '{0}'." , this . EntityType . Name ) ) ;
// Add parent's keys
if ( IsChild )
groupingKeyColumns . ParentPrimaryKeys . AddRange ( Parent . GroupingKeyColumns ) ;
return groupingKeyColumns ;
}
#region IEnumerable<EntityGraph> Members
public IEnumerator < EntityGraph > GetEnumerator ( )
{
return TraverseGraph ( this ) ;
}
/// <summary>
/// Recursively traverses through every entity in the EntityGraph.
/// </summary>
/// <param name="entityGraph"></param>
/// <returns></returns>
private static IEnumerator < EntityGraph > TraverseGraph ( EntityGraph entityGraph )
{
Stack < EntityGraph > stack = new Stack < EntityGraph > ( ) ;
stack . Push ( entityGraph ) ;
while ( stack . Count > 0 )
{
EntityGraph node = stack . Pop ( ) ;
yield return node ;
foreach ( EntityGraph childGraph in node . Children )
{
stack . Push ( childGraph ) ;
}
}
}
# endregion
#region IEnumerable Members
IEnumerator IEnumerable . GetEnumerator ( )
{
return this . GetEnumerator ( ) ;
}
# endregion
}
}
public struct KeyGroupInfo
{
private string _groupingKey ;
private bool _hasNullKey ;
public KeyGroupInfo ( string groupingKey , bool hasNullKey )
{
_groupingKey = groupingKey ;
_hasNullKey = hasNullKey ;
}
public string GroupingKey
{
get { return _groupingKey ; }
}
public bool HasNullKey
{
get { return _hasNullKey ; }
}
}