public static bool IsReferenceLoaded <TEntity, TProperty>( this HookingDbContext ctx, TEntity entity, Expression <Func <TEntity, TProperty> > navigationProperty, out ReferenceEntry <TEntity, TProperty> referenceEntry) where TEntity : BaseEntity where TProperty : BaseEntity { Guard.NotNull(entity, nameof(entity)); Guard.NotNull(navigationProperty, nameof(navigationProperty)); referenceEntry = null; if (entity.Id == 0) { return(false); } var entry = ctx.Entry(entity); referenceEntry = entry.Reference(navigationProperty); // Avoid System.InvalidOperationException: Member 'IsLoaded' cannot be called for property... if (entry.State == EfState.Detached || entry.State == EfState.Added) { return(referenceEntry.CurrentValue != null); } return(referenceEntry.IsLoaded); }
private static int DetachInternal(this HookingDbContext ctx, BaseEntity obj, ISet <BaseEntity> objSet, bool deep) { if (obj == null) { return(0); } return(ctx.DetachInternal(ctx.Entry(obj), objSet, deep)); }
public static bool IsReferenceLoaded <TEntity, TProperty>( this HookingDbContext ctx, TEntity entity, Expression <Func <TEntity, TProperty> > navigationProperty, out ReferenceEntry <TEntity, TProperty> referenceEntry) where TEntity : BaseEntity where TProperty : BaseEntity { Guard.NotNull(entity, nameof(entity)); Guard.NotNull(navigationProperty, nameof(navigationProperty)); referenceEntry = null; if (entity.Id == 0) { return(false); } var entry = ctx.Entry(entity); referenceEntry = entry.Reference(navigationProperty); var isLoaded = referenceEntry.CurrentValue != null || referenceEntry.IsLoaded; // Avoid System.InvalidOperationException: Member 'IsLoaded' cannot be called for property... if (!isLoaded && (entry.State == EfState.Detached)) { // Attaching an entity while another instance with same primary key is attached will throw. // First we gonna try to locate an already attached entity. var other = ctx.FindTracked <TEntity>(entity.Id); if (other != null) { referenceEntry = ctx.Entry(entity).Reference(navigationProperty); } else { ctx.Attach(entity); } } return(isLoaded); }
/// <summary> /// Sets the state of an entity to <see cref="EfState.Modified"/> if it is detached. /// </summary> /// <typeparam name="TEntity">Type of entity</typeparam> /// <param name="entity">The entity instance</param> /// <returns><c>true</c> if the state has been changed, <c>false</c> if entity is attached already.</returns> public static bool TryUpdate <TEntity>(this HookingDbContext ctx, TEntity entity) where TEntity : BaseEntity { var detectChanges = ctx.ChangeTracker.AutoDetectChangesEnabled; ctx.ChangeTracker.AutoDetectChangesEnabled = false; using (new ActionDisposable(() => ctx.ChangeTracker.AutoDetectChangesEnabled = detectChanges)) { // (perf) turning off autoDetectChanges prevents that ctx.Entry() performs change detection internally. var entry = ctx.Entry(entity); return(entry.TryUpdate()); } }
/// <summary> /// Change the state of an entity object /// </summary> /// <typeparam name="TEntity">Type of entity</typeparam> /// <param name="entity">The entity instance</param> /// <param name="requestedState">The requested new state</param> public static void ChangeState <TEntity>(this HookingDbContext ctx, TEntity entity, EfState requestedState) where TEntity : BaseEntity { //Console.WriteLine("ChangeState ORIGINAL"); var entry = ctx.Entry(entity); if (entry.State != requestedState) { // Only change state when requested state differs, // because EF internally sets all properties to modified // if necessary, even when requested state equals current state. entry.State = requestedState; } }
/// <summary> /// Determines whether an entity property has changed since it was attached. /// </summary> /// <param name="entity">Entity</param> /// <param name="propertyName">The property name to check</param> /// <param name="originalValue">The previous/original property value if change was detected</param> /// <returns><c>true</c> if property has changed, <c>false</c> otherwise</returns> public static bool TryGetModifiedProperty(this HookingDbContext ctx, BaseEntity entity, string propertyName, out object originalValue) { Guard.NotNull(entity, nameof(entity)); if (entity.IsTransientRecord()) { originalValue = null; return(false); } var entry = ctx.Entry((object)entity); return(entry.TryGetModifiedProperty(propertyName, out originalValue)); }
/// <summary> /// Loads an entity referenced by a navigation property from database, unless data is already loaded. /// </summary> /// <param name="entity">Entity instance to load data for.</param> /// <param name="navigationProperty">The navigation property expression.</param> /// <param name="force"><c>false:</c> do nothing if data is already loaded. <c>true:</c> Reload data event if loaded already.</param> /// <param name="queryModifier">Modifier for the query that is about to be executed against the database.</param> public static async Task LoadReferenceAsync <TEntity, TProperty>( this HookingDbContext ctx, TEntity entity, Expression <Func <TEntity, TProperty> > navigationProperty, bool force = false, Func <IQueryable <TProperty>, IQueryable <TProperty> > queryModifier = null) where TEntity : BaseEntity where TProperty : BaseEntity { Guard.NotNull(entity, nameof(entity)); Guard.NotNull(navigationProperty, nameof(navigationProperty)); var entry = ctx.Entry(entity); var reference = entry.Reference(navigationProperty); // Avoid System.InvalidOperationException: Member 'IsLoaded' cannot be called for property... if (entry.State == EfState.Detached) { try { ctx.Attach(entity); } catch { // Attach may throw! } } if (force) { reference.IsLoaded = false; } if (!reference.IsLoaded) { if (queryModifier != null) { var query = queryModifier(reference.Query()); reference.CurrentValue = await query.FirstOrDefaultAsync(); } else { await reference.LoadAsync(); } reference.IsLoaded = true; } }
/// <summary> /// Loads entities referenced by a collection navigation property from database, unless data is already loaded. /// </summary> /// <param name="entity">Entity instance to load data for.</param> /// <param name="navigationProperty">The navigation property expression.</param> /// <param name="force"><c>false:</c> do nothing if data is already loaded. <c>true:</c> Reload data event if loaded already.</param> /// <param name="queryModifier">Modifier for the query that is about to be executed against the database.</param> public static async Task LoadCollectionAsync <TEntity, TCollection>( this HookingDbContext ctx, TEntity entity, Expression <Func <TEntity, IEnumerable <TCollection> > > navigationProperty, bool force = false, Func <IQueryable <TCollection>, IQueryable <TCollection> > queryModifier = null) where TEntity : BaseEntity where TCollection : BaseEntity { Guard.NotNull(entity, nameof(entity)); Guard.NotNull(navigationProperty, nameof(navigationProperty)); var entry = ctx.Entry(entity); var collection = entry.Collection(navigationProperty); // Avoid System.InvalidOperationException: Member 'IsLoaded' cannot be called for property... if (entry.State == EfState.Detached) { try { ctx.Attach(entity); } catch { // Attach may throw! } } if (force) { collection.IsLoaded = false; } if (!collection.IsLoaded) { if (queryModifier != null) { var query = queryModifier(collection.Query()); collection.CurrentValue = await query.ToListAsync(); } else { await collection.LoadAsync(); } collection.IsLoaded = true; } }
public static bool IsCollectionLoaded <TEntity, TCollection>( this HookingDbContext ctx, TEntity entity, Expression <Func <TEntity, IEnumerable <TCollection> > > navigationProperty) where TEntity : BaseEntity where TCollection : BaseEntity { Guard.NotNull(entity, nameof(entity)); Guard.NotNull(navigationProperty, nameof(navigationProperty)); var entry = ctx.Entry(entity); var collection = entry.Collection(navigationProperty); // Avoid System.InvalidOperationException: Member 'IsLoaded' cannot be called for property... if (entry.State == EfState.Detached) { return(false); } return(collection.IsLoaded); }
/// <summary> /// Reloads the entity from the database overwriting any property values with values from the database. /// The entity will be in the Unchanged state after calling this method. /// </summary> /// <typeparam name="TEntity">Type of entity</typeparam> /// <param name="entity">The entity instance</param> public static Task ReloadEntityAsync <TEntity>(this HookingDbContext ctx, TEntity entity, CancellationToken cancelToken = default) where TEntity : BaseEntity { return(ctx.Entry((object)entity).ReloadEntityAsync(cancelToken)); }
/// <summary> /// Reloads the entity from the database overwriting any property values with values from the database. /// The entity will be in the Unchanged state after calling this method. /// </summary> /// <typeparam name="TEntity">Type of entity</typeparam> /// <param name="entity">The entity instance</param> public static void ReloadEntity <TEntity>(this HookingDbContext ctx, TEntity entity) where TEntity : BaseEntity { ctx.Entry((object)entity).ReloadEntity(); }
/// <summary> /// Gets a list of modified properties for the specified entity /// </summary> /// <param name="entity">The entity instance for which to get modified properties for</param> /// <returns> /// A dictionary, where the key is the name of the modified property /// and the value is its ORIGINAL value (which was tracked when the entity /// was attached to the context the first time) /// Returns an empty dictionary if no modification could be detected. /// </returns> public static IDictionary <string, object> GetModifiedProperties(this HookingDbContext ctx, BaseEntity entity) { return(ctx.Entry((object)entity).GetModifiedProperties()); }
/// <summary> /// Loads an entity referenced by a navigation property from database, unless data is already loaded. /// </summary> /// <param name="entity">Entity instance to load data for.</param> /// <param name="navigationProperty">The navigation property expression.</param> /// <param name="force"><c>false:</c> do nothing if data is already loaded. <c>true:</c> Reload data event if loaded already.</param> /// <param name="queryModifier">Modifier for the query that is about to be executed against the database.</param> /// <param name="cancelToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param> public static async Task <ReferenceEntry <TEntity, TProperty> > LoadReferenceAsync <TEntity, TProperty>( this HookingDbContext ctx, TEntity entity, Expression <Func <TEntity, TProperty> > navigationProperty, bool force = false, Func <IQueryable <TProperty>, IQueryable <TProperty> > queryModifier = null, CancellationToken cancelToken = default) where TEntity : BaseEntity where TProperty : BaseEntity { Guard.NotNull(entity, nameof(entity)); Guard.NotNull(navigationProperty, nameof(navigationProperty)); if (entity.Id == 0) { return(null); } var entry = ctx.Entry(entity); if (entry.State == EfState.Deleted) { return(null); } var reference = entry.Reference(navigationProperty); var isLoaded = reference.CurrentValue != null || reference.IsLoaded; if (!isLoaded && entry.State == EfState.Detached) { try { // Attaching an entity while another instance with same primary key is attached will throw. // First we gonna try to locate an already attached entity. var other = ctx.FindTracked <TEntity>(entity.Id); if (other != null) { // An entity with same key is attached already. So we gonna load the reference property of attached entity // and copy the result to this detached entity. This way we don't need to attach the source entity. var otherReference = await ctx.LoadReferenceAsync(other, navigationProperty, force, queryModifier, cancelToken : cancelToken); // Copy reference over to detached entity. reference.CurrentValue = otherReference.CurrentValue; reference.IsLoaded = true; isLoaded = true; force = false; } else { ctx.Attach(entity); } } catch { // Attach may throw! } } if (force) { reference.IsLoaded = false; isLoaded = false; } if (!isLoaded) { if (queryModifier != null) { var query = queryModifier(reference.Query()); reference.CurrentValue = await query.FirstOrDefaultAsync(cancellationToken : cancelToken); } else { await reference.LoadAsync(cancelToken); } reference.IsLoaded = true; } return(reference); }
/// <summary> /// Loads entities referenced by a collection navigation property from database, unless data is already loaded. /// </summary> /// <param name="entity">Entity instance to load data for.</param> /// <param name="navigationProperty">The navigation property expression.</param> /// <param name="force"><c>false:</c> do nothing if data is already loaded. <c>true:</c> reload data even if already loaded.</param> /// <param name="queryModifier">Modifier for the query that is about to be executed against the database.</param> /// <param name="cancelToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param> public static async Task <CollectionEntry <TEntity, TCollection> > LoadCollectionAsync <TEntity, TCollection>( this HookingDbContext ctx, TEntity entity, Expression <Func <TEntity, IEnumerable <TCollection> > > navigationProperty, bool force = false, Func <IQueryable <TCollection>, IQueryable <TCollection> > queryModifier = null, CancellationToken cancelToken = default) where TEntity : BaseEntity where TCollection : BaseEntity { Guard.NotNull(entity, nameof(entity)); Guard.NotNull(navigationProperty, nameof(navigationProperty)); if (entity.Id == 0) { return(null); } var entry = ctx.Entry(entity); if (entry.State == EfState.Deleted) { return(null); } var collection = entry.Collection(navigationProperty); // TODO: (Core) Entities with hashSets as collections always return true here, as they are never null (for example: Product.ProductVariantAttributes). var isLoaded = collection.CurrentValue != null || collection.IsLoaded; if (!isLoaded && entry.State == EfState.Detached) { try { // Attaching an entity while another instance with same primary key is attached will throw. // First we gonna try to locate an already attached entity. var other = ctx.FindTracked <TEntity>(entity.Id); if (other != null) { // An entity with same key is attached already. So we gonna load the navigation property of attached entity // and copy the result to this detached entity. This way we don't need to attach the source entity. var otherCollection = await ctx.LoadCollectionAsync(other, navigationProperty, force, queryModifier, cancelToken : cancelToken); // Copy collection over to detached entity. collection.CurrentValue = otherCollection.CurrentValue; collection.IsLoaded = true; isLoaded = true; force = false; } else { ctx.Attach(entity); } } catch { // Attach may throw! } } if (force) { collection.IsLoaded = false; isLoaded = false; } if (!isLoaded) { if (queryModifier != null) { var query = queryModifier(collection.Query()); collection.CurrentValue = await query.ToListAsync(cancellationToken : cancelToken); } else { await collection.LoadAsync(cancelToken); } collection.IsLoaded = true; } return(collection); }