/// <summary> /// Order entities according to principal self navigation count ascendingly /// for defining state appropriately. /// </summary> /// <example> /// firstCategory which has null value on ParentCategory property has 0 principal self /// navigation count. If ParentCategory of secondCategory is firstCategory, then secondCategory /// has 1 principal self navigation count, if ParentCategory of thirdCateogry is secondCategory, /// then it has 2 principal self navigaiton count. In this case, state of enitites must be defined /// in ascending order. In our example, the order is like below: /// 1. firstCategory, 2. secondCategory, 3. thirdCateogry. /// </example> /// <exception cref="ArgumentNullException"> /// When entityCollection is null. /// </exception> /// <exception cref="InvalidOperationException"> /// When entities in collection do not have same type. /// </exception> /// <param name="entityCollection">EntityCollection to define state define order.</param> /// <returns>Orderer entity collection suiting for defining state.</returns> private IEnumerable <object> DefineStateDefineOrder(IEnumerable <object> entityCollection) { if (entityCollection == null) { throw new ArgumentNullException(nameof(entityCollection)); } if (!entityCollection.Any()) { return(entityCollection); } // Get type of entity string entityTypeName = entityCollection.First().GetType().Name; // All entities must have same type in collection. if (entityCollection .Any(m => m != null && m.GetType().Name != entityTypeName)) { throw new InvalidOperationException(string.Format( "All entities must have same type in collection to define state define order." + " Entity type: {0}.", entityTypeName)); } // Check if entity has navigation propert to itself. If not, then // there is no need to order them, otherwise order. IGraphEntityTypeManager entityTypeManager = GetEntityTypeManager(entityTypeName); NavigationDetail navigationDetail = entityTypeManager.GetNavigationDetail(); bool hasPrinciplaSelfNavigationProperty = navigationDetail .Relations .Any(m => m.Direction == NavigationDirection.From && m.PropertyTypeName == entityTypeName); if (!hasPrinciplaSelfNavigationProperty) { return(entityCollection); } // Initialize store for ordered entities. Dictionary <object, int> principalCountStore = new Dictionary <object, int>(); for (int i = 0; i < entityCollection.Count(); i++) { dynamic entity = entityCollection.ElementAt(i); int principalSelfCount = CalculatePrincipalSelfNavigationCount( entity, principalCountStore); } // Order according to principal slef navigation properties ascendingly. return(principalCountStore.OrderBy(m => m.Value).Select(m => m.Key)); }
/// <summary> /// Get list of unique properties. /// </summary> /// <remarks> /// Generally useful when logging to get key data to later find entity easily. /// </remarks> /// <exception cref="ArgumentNullException"> /// When context or typeName is null. /// </exception> /// <param name="context">Context to get unique keys from.</param> /// <param name="typeName">Name of type to get unique keys for.</param> /// <returns>List of unique properties</returns> public static List <PropertyInfo> GetUniqueProperties( this DbContext context, string typeName) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (string.IsNullOrEmpty(typeName)) { throw new ArgumentNullException(nameof(typeName)); } ContextHelper contextHelper = new ContextHelper(context); IGraphEntityTypeManager entityTypeManager = contextHelper .GetEntityTypeManager(typeName); return(entityTypeManager.GetUniqueProperties()); }
/// <summary> /// Calculate state define order of added entities. /// </summary> /// <returns>Sorted stete define order.</returns> private IOrderedEnumerable <KeyValuePair <string, int> > CalculateStateDefineOrder() { // Initialize store Dictionary <string, int> store = new Dictionary <string, int>(); List <string> typeNames = ObjectContext .MetadataWorkspace .GetItems <EntityType>(DataSpace.CSpace) .Select(m => m.Name) .ToList(); foreach (string typeName in typeNames) { IGraphEntityTypeManager entityTypeManager = GetEntityTypeManager(typeName); entityTypeManager.FindPrincipalCount(store); } IOrderedEnumerable <KeyValuePair <string, int> > sorted = store.OrderBy(m => m.Value); return(sorted); }
/// <summary> /// Detach dependants of entity. /// </summary> /// <exception cref="ArgumentNullException"> /// When entity is null /// </exception> /// <typeparam name="TEntity">Type of entity.</typeparam> /// <param name="entity">Entity to detach dependants.</param> /// <param name="detachItself">Also detach entity itself.</param> public void DetachWithDependants <TEntity>( TEntity entity, bool detachItself) where TEntity : class { if (entity == null) { throw new ArgumentNullException(nameof(entity)); } string typeName = entity.GetType().Name; IGraphEntityTypeManager graphEntityTypeManager = GetEntityTypeManager(typeName); List <string> dependantPropertyTypes = graphEntityTypeManager .GetForeignKeyDetails() .Where(r => r.FromDetails .ContainerClass .Equals(typeName)) .Select(r => r.ToDetails.ContainerClass) .ToList(); List <PropertyInfo> dependantProperties = entity .GetType() .GetProperties() .Where(p => dependantPropertyTypes .Contains(p.PropertyType.GetUnderlyingType().Name)) .ToList(); if (dependantProperties != null && dependantProperties.Count > 0) { foreach (PropertyInfo childEntityProperty in dependantProperties) { if (childEntityProperty.PropertyType.IsCollectionType()) { // If child entity is collection detach all entities inside this collection IEnumerable <object> enumerableChildEntity = ReflectionExtensions.GetPropertyValue(entity, childEntityProperty.Name) as IEnumerable <object>; if (enumerableChildEntity != null) { foreach (dynamic childEntity in enumerableChildEntity.ToList()) { if (childEntity != null) { DetachWithDependants(childEntity, true); } } } } else { // If child entity is not collection define state of its own dynamic childEntity = ReflectionExtensions.GetPropertyValue(entity, childEntityProperty.Name); if (childEntity != null) { DetachWithDependants(childEntity, true); } } } } if (detachItself) { Context.Entry(entity).State = EntityState.Detached; } }
/// <summary> /// Get parents of entity which contains this entity /// </summary> /// <exception cref="ArgumentNullException"> /// When entity is null /// </exception> /// <typeparam name="TEntity">Type of entity.</typeparam> /// <param name="entity">Entity to get parent.</param> /// <param name="onlyPrincipal">Get only one-to-one parent of entity</param> /// <returns>Principal parent of entity.</returns> public IEnumerable <object> GetParents <TEntity>( TEntity entity, bool onlyPrincipal) where TEntity : class { if (entity == null) { throw new ArgumentNullException(nameof(entity)); } string typeName = entity.GetType().Name; List <RelationshipMultiplicity> principalRelationshipMultiplicity = new List <RelationshipMultiplicity>() { RelationshipMultiplicity.One, RelationshipMultiplicity.ZeroOrOne }; IGraphEntityTypeManager graphEntityTypeMangeer = GetEntityTypeManager(typeName); NavigationDetail navigationDetailOfCurrent = graphEntityTypeMangeer .GetNavigationDetail(); // Get only those parent property navigation details // which has navigation property to this entity var parentNavigationDetails = navigationDetailOfCurrent .Relations .Where(r => r.Direction == NavigationDirection.From) .Select(r => { IGraphEntityTypeManager typeManager = GetEntityTypeManager(r.PropertyTypeName); return(new { SourceTypeName = r.PropertyTypeName, Relation = typeManager .GetNavigationDetail() .Relations .FirstOrDefault(pr => pr.PropertyTypeName.Equals(typeName) && pr.SourceMultiplicity == r.TargetMultiplicity && pr.TargetMultiplicity == r.SourceMultiplicity && pr.ToKeyNames.SequenceEqual(r.ToKeyNames)) }); }) .Where(r => r.Relation != null); if (onlyPrincipal) { parentNavigationDetails = parentNavigationDetails .Where(r => principalRelationshipMultiplicity .Contains(r.Relation.TargetMultiplicity)); } List <string> parentPropertyNames = navigationDetailOfCurrent .Relations .Where(r => parentNavigationDetails.Any(p => p.SourceTypeName == r.PropertyTypeName && p.Relation.SourceMultiplicity == r.TargetMultiplicity && p.Relation.TargetMultiplicity == r.SourceMultiplicity && p.Relation.ToKeyNames.SequenceEqual(r.ToKeyNames))) .Select(r => r.PropertyName) .ToList(); if (parentPropertyNames != null && parentPropertyNames.Count > 0) { foreach (string propertyName in parentPropertyNames) { object parent = entity.GetPropertyValue(propertyName); if (parent != null) { yield return(parent); } } } }
/// <summary> /// Find on how many properties this type depends on. /// </summary> /// <exception cref="ArgumentNullException"> /// When store is null. /// </exception> /// <param name="store">Calculation store.</param> /// <returns>Calculated count.</returns> public int FindPrincipalCount(Dictionary <string, int> store) { if (store == null) { throw new ArgumentNullException(nameof(store)); } // If principal count has already been // calculated for current type, get if from store. if (store.ContainsKey(EntityTypeName)) { return(store[EntityTypeName]); } // Get principal properties. var navigationDetail = GetNavigationDetail(); var principalCollection = navigationDetail .Relations .Where(m => m.Direction == NavigationDirection.From); // Get state definers IEnumerable <PropertyInfo> stateDefiners = MappingStorage.Instance.StateDefiners .Where(s => s.SourceType.Name.Equals(EntityTypeName)) .SelectMany(s => s.Properties); // Get types which this type is state definer for List <string> stateDefinerFor = MappingStorage .Instance .StateDefiners .Where(m => m.Properties .Select(pr => pr.PropertyType.Name) .Contains(EntityTypeName)) .Select(m => m.SourceType.Name) .ToList(); // If this type has no principal property // or stete definer return 0. if (principalCollection.Count() == 0 && stateDefiners.Count() == 0) { store.Add(EntityTypeName, 0); return(0); } // Calculate initial count of principal properties // and state definers. int count = principalCollection.Count() + stateDefiners.Count(); // Loop through principal properties and add count of // principal properties of them to the current type. // Because when FirstType depends on SecondType, // and SecondType depends on ThirdType this means // that FirstType depends on SecondType and ThirdType. foreach (var principal in principalCollection) { // If this principal property is itself, do not count it if (principal.PropertyTypeName == EntityTypeName) { continue; } // If this type is stete definer for principal // type, do not count it if (stateDefinerFor.Contains(principal.PropertyTypeName)) { continue; } IGraphEntityTypeManager entityTypeManager = ContextFactory .GetEntityTypeManager(principal.PropertyTypeName); count += entityTypeManager.FindPrincipalCount(store); } // Add count of principal properties of state definers also. foreach (var stateDefiner in stateDefiners) { IGraphEntityTypeManager entityTypeManager = ContextFactory .GetEntityTypeManager(stateDefiner.PropertyType.Name); count += entityTypeManager.FindPrincipalCount(store); } // Store calculation for further use. store.Add(EntityTypeName, count); return(count); }
/// <summary> /// Get the the origin class which this foreign key refers to. /// </summary> /// <exception cref="ArgumentNullException"> /// When foreignKeyName is null. /// </exception> /// <param name="foreignKeyName">Name of foreign key property.</param> /// <returns>Name of class which this foreign key refers to.</returns> public string GetOriginOfForeignKey(string foreignKeyName) { if (string.IsNullOrEmpty(foreignKeyName)) { throw new ArgumentNullException(nameof(foreignKeyName)); } // Try to get from store Tuple <string, string> key = new Tuple <string, string>(EntityTypeName, foreignKeyName); if (Store.ForeignKeyOrigin.ContainsKey(key)) { return(Store.ForeignKeyOrigin[key]); } string originOfForeignKey = string.Empty; // Principal relationship multiplicities List <RelationshipMultiplicity> principalRelationshipMultiplicity = new List <RelationshipMultiplicity>() { RelationshipMultiplicity.One, RelationshipMultiplicity.ZeroOrOne }; // Get the relationship detail which this foreign keys refers. RelationshipDetail parentType = GetForeignKeyDetails() .Where(r => r.ToDetails.ContainerClass.Equals(EntityTypeName) && r.ToDetails.Keys.Any(k => k.Equals(foreignKeyName)) && r.FromDetails != null && !string.IsNullOrEmpty(r.FromDetails.ContainerClass) && principalRelationshipMultiplicity .Contains(r.FromDetails.RelationshipMultiplicity)) .FirstOrDefault(); if (parentType != null) { var currentReferencedTypeName = parentType.FromDetails.ContainerClass; var currentForeignKeyName = GetMatchingFromForeignKeyName(parentType, foreignKeyName); while (!string.IsNullOrEmpty(currentReferencedTypeName)) { // If EntityTypeName and currentReferencedTypeName is same, // then this is self reference, so we need to break infinete iteration. if (EntityTypeName == currentReferencedTypeName) { break; } IGraphEntityTypeManager referencedTypeManager = ContextFactory .GetEntityTypeManager(currentReferencedTypeName); // Get one-to-one principal parent relationship detail, // because the origin of foreign key is uppermost principal parent. RelationshipDetail principalParent = referencedTypeManager .GetForeignKeyDetails() .Where(r => r.ToDetails.ContainerClass.Equals(currentReferencedTypeName) && r.ToDetails.Keys.Contains(currentForeignKeyName) && r.FromDetails != null && !string.IsNullOrEmpty(r.FromDetails.ContainerClass) && principalRelationshipMultiplicity .Contains(r.FromDetails.RelationshipMultiplicity)) .FirstOrDefault(); if (principalParent != null) { // If principal parent is not null set current to this. currentReferencedTypeName = principalParent.FromDetails.ContainerClass; currentForeignKeyName = GetMatchingFromForeignKeyName(principalParent, currentForeignKeyName); } else { // If principal parent is null exit the iteration. break; } } originOfForeignKey = currentReferencedTypeName; } // Add to store Store.ForeignKeyOrigin.Add(key, originOfForeignKey); return(originOfForeignKey); }
/// <summary> /// Synchronize keys of entity with matching entity. /// <remarks> /// In EntityFramework at one-to-one relationships, setting primary key /// of parent entity and setting state of entities to Unchanged or to Modified /// will also change primary key value of child entity. /// But vice-versa is not correct. This means that setting primary key value /// of child entity and setting state of entities to Unchanged or to Modified /// will not change primary key value of parent key, instead, it resets primary /// key value of child entity to its default value, (or to value of primary key /// of parent). /// This method is for synchronizing PKs of entity and PKs of matching entity and /// setting key of parent to key of child entities at one to relationships. /// </remarks> /// </summary> /// <param name="entity">Entity to set pk and parent key values.</param> /// <param name="matchingEntity">Found matching entity from underlying source.</param> public void SynchronizeKeys( TEntity entity, TEntity matchingEntity) { /* * * * Set keys of parent entity to value of child entity at one to one relations. * * */ IEnumerable <RelationshipDetail> associatedRealtionships = GetForeignKeyDetails() .Where(m => m.ToDetails.ContainerClass.Equals(entity.GetType().Name) && m.ToDetails.RelationshipMultiplicity == RelationshipMultiplicity.One); foreach (RelationshipDetail relationshipDetail in associatedRealtionships) { // Get parent property name from navigation details using information from foreign keys IGraphEntityTypeManager entityTypeManager = ContextFactory .GetEntityTypeManager(relationshipDetail.ToDetails.ContainerClass); string parentPropertyName = entityTypeManager .GetNavigationDetail() .Relations .Where(n => n.PropertyTypeName.Equals(relationshipDetail.FromDetails.ContainerClass) && n.SourceMultiplicity == relationshipDetail.ToDetails.RelationshipMultiplicity && n.TargetMultiplicity == relationshipDetail.FromDetails.RelationshipMultiplicity) .Select(n => n.PropertyName) .FirstOrDefault(); dynamic parent = entity.GetPropertyValue(parentPropertyName); if (parent != null) { for (int i = 0; i < relationshipDetail.FromDetails.Keys.Count; i++) { // Get matching from and to key names string fromKeyName = relationshipDetail.FromDetails.Keys[i]; string toKeyName = relationshipDetail.ToDetails.Keys[i]; ReflectionExtensions.SetPropertyValue(parent, fromKeyName, matchingEntity.GetPropertyValue(toKeyName)); } } } /* * Description: * PK value shuold be changed by using * context.Entry(entity).Property(pkName).CurrentValue = pkValue; * becasue setting value by entity.pkName = pkValue will not synchronize * it with dependent navigation properties automatically but prior method * will do it. * Primary key values of entity itself must be changed after * principal parent keys has been synchronized. Because changing * primary key value of entity using * context.Entry(entity).Property(pkName).CurrentValue = pkValue * set principal parent navigation property to null. */ IEnumerable <string> primaryKeyNames = GetPrimaryKeys(); DbEntityEntry current = Context.Entry(entity); foreach (string primaryKey in primaryKeyNames) { current.Property(primaryKey).CurrentValue = matchingEntity.GetPropertyValue(primaryKey); } }
/// <summary> /// Construct filter expression for entity. /// </summary> /// <param name="entity">Entity to construct filter expression for.</param> /// <param name="typeOfFilter">Type of filter expression.</param> /// <returns>Filter expression according to entity.</returns> public Expression <Func <TEntity, bool> > ConstructFilterExpression( TEntity entity, FilterType typeOfFilter) { Expression <Func <TEntity, bool> > filterExpression = null; IEnumerable <string> primaryKeyNames = GetPrimaryKeys(); if (primaryKeyNames.Count() == 0) { throw new ArgumentException(string.Format( "'{0}' has no configured primary key.", typeof(TEntity).Name)); } List <Expression> equalityExpressions = new List <Expression>(); IEnumerable <Expression> singleExpressionList; ParameterExpression parameterExp = Expression.Parameter(typeof(TEntity), "m"); Expression pkFilter = null; Expression ukFilter = null; bool primaryKeysHaveDefaultValues = entity.HasDefaultValues(primaryKeyNames); if (!primaryKeysHaveDefaultValues && typeOfFilter != FilterType.OnlyUnique) { // If primary keys do not have their default values // add this check to equlity expression list singleExpressionList = parameterExp .ConstructEqualityExpressions( entity, primaryKeyNames); pkFilter = singleExpressionList.ConstructAndChain(); } if (typeOfFilter != FilterType.OnlyId && (primaryKeysHaveDefaultValues || typeOfFilter != FilterType.IdOptionalUnique)) { IEnumerable <PropertiesWithSource> uniqueProperties = MappingStorage.Instance.UniqueProperties.Where( m => m.SourceType.Equals(entity.GetType())); if (uniqueProperties.Count() > 0) { foreach (PropertiesWithSource unique in uniqueProperties) { /* *********************************************************** * If any of current set of properties * marked as unique is foreign key, * has default value, and appropriate navigation property is not * null and also origin of this foreign * key has any store generated primary key then this * uniqueness must be ignored. * For example if PersonId (int) and DocumentType (short) has been * set as composite unique in PersonDocument and * if PersonId is foreign key to Person, which in its term has * Primary key which is store generated and if there is no navigation * property to Person from PersonDocument or PersonDocument.Person is not null * then PersonId = 0 and DocumentType = 5 should not * be treated as unique, because the real value of PersonId * will be computed when data will be inserted. *********************************************************** */ IGraphEntityTypeManager uniqueSourceTypeManager = ContextFactory .GetEntityTypeManager(unique.SourceType.Name); bool uniquenessMustBeIgnored = false; var uniquePropertyNames = unique.Properties.Select(m => m.Name).ToList(); var uniqueForeignKeys = uniqueSourceTypeManager .GetForeignKeyDetails() .Select(m => new { TargetClass = m.FromDetails.ContainerClass, Keys = m.ToDetails.Keys.Intersect(uniquePropertyNames) }) .Where(m => m.Keys != null && m.Keys.Any()); NavigationDetail navigationDetailsOfCurrent = GetNavigationDetail(); // If unuque property is foreign key if (uniqueForeignKeys != null && uniqueForeignKeys.Any()) { foreach (var uniqueFk in uniqueForeignKeys) { // If foreign key has default value if (uniqueFk.Keys.Any(u => entity.HasDefaultValue(u))) { // Get navigation relation according to foreign key NavigationRelation navigationRelation = navigationDetailsOfCurrent .Relations .FirstOrDefault(r => r.Direction == NavigationDirection.From && r.PropertyTypeName.Equals(uniqueFk.TargetClass) && r.ToKeyNames.Intersect(uniqueFk.Keys).Any()); // If corresponding navigation property is not null // or there is no such navigation property if (navigationRelation == null || entity.GetPropertyValue(navigationRelation.PropertyName) != null) { bool foreignKeyHasStoreGeneratedPrimaryKey = uniqueFk.Keys.Any(k => { // Get origin of foreign key and check // if it has store generated key. string foreignKeyOrigin = uniqueSourceTypeManager .GetOriginOfForeignKey(k); IGraphEntityTypeManager foreignKeyOriginTypeManger = ContextFactory .GetEntityTypeManager(foreignKeyOrigin); return(foreignKeyOriginTypeManger.HasStoreGeneratedKey()); }); // If origin of foreign key has store generated Primary key if (foreignKeyHasStoreGeneratedPrimaryKey) { uniquenessMustBeIgnored = true; break; } } } } } // If uniqueness must be ignored then skip this iteration if (uniquenessMustBeIgnored) { continue; } singleExpressionList = parameterExp .ConstructEqualityExpressions( entity, unique.Properties .Select(m => m.Name).ToList()); equalityExpressions.Add(singleExpressionList.ConstructAndChain()); } if (equalityExpressions.Count > 0) { ukFilter = equalityExpressions.ConstructOrChain(); } } } equalityExpressions.Clear(); if (pkFilter != null) { equalityExpressions.Add(pkFilter); } if (ukFilter != null) { equalityExpressions.Add(ukFilter); } if (equalityExpressions.Count > 0) { Expression filterBaseExpression = typeOfFilter == FilterType.IdAndUnique ? equalityExpressions.ConstructAndChain() : equalityExpressions.ConstructOrChain(); filterExpression = Expression.Lambda <Func <TEntity, bool> >( filterBaseExpression, parameterExp); } return(filterExpression); }