none
How do I unload a navigation property that's NULL? RRS feed

  • Question

  • Let's assume I have two entities Order and OrderDetails. There is a 1 to 0..1 relation between the two and there's a navigation property called Details on the Order entity leading to OrderDetails.

    Now if the order.Details is already loaded and something (a stored procedure for example) modifies the data, I can "unload" the navigation property by context.Detach(order.Details) so that if and only if the entity is needed again it will be reloaded from the database.

    The problem is you can't detach a null. So if the navigation property was loaded and there was no matching record in the database I don't know what to do. 

    I mean I can detach the whole Order and hope, I can fiddle with attributes to get the necessary data out of the EdmRelationshipNavigationPropertyAttribute and after typecasting the entity to IEntityWithRelationships I can finaly get to the RelationManager, explan which silly relation do I mean, GetRelatedEnd, see whether the navigation property is loaded (thanks for hiding this information so well!) and reload the navigation property, but how the heck do I mark it "not loaded" so that it is loaded when needed?

    For those interested here's the code that allows you to test whether a (non collection) navigation property is loaded, reload it and (if it was not null) unload it so that it will be loaded onoy if needed.

     

    private static Dictionary<string, Func<IEntityWithRelationships, IRelatedEnd>> _RelatedEndCache = new Dictionary<string, Func<IEntityWithRelationships, IRelatedEnd>>();
    private static IRelatedEnd GetRelatedEnd<TObj, TProperty>(this TObj obj, Expression<Func<TObj, TProperty>> expr)
    	where TObj : IEntityWithRelationships
    	where TProperty : class {
    	var hashKey = expr.HashId();
    	if (!_RelatedEndCache.ContainsKey(hashKey)) {
    
    		var body = (expr as LambdaExpression).Body;
    
    		if (!(body is MemberExpression)) throw new ArgumentException("The expression passed to entityObject.IsLoaded() must be a simple member expression!");
    		var attr = (body as MemberExpression).Member.GetCustomAttributes(typeof(EdmRelationshipNavigationPropertyAttribute), true);
    		if (attr == null || !attr.Any()) {
    			throw new ArgumentException("The expression passed to entityObject.IsLoaded() must be a simple member expression referencing a navigation property!");
    		}
    		var info = (EdmRelationshipNavigationPropertyAttribute)attr.First();
    
    		string relation = info.RelationshipNamespaceName + "." + info.RelationshipName;
    		string role = info.TargetRoleName;
    
    		lock (_RelatedEndCache) {
    			_RelatedEndCache[hashKey] = (o => o.RelationshipManager.GetRelatedEnd(relation, role));
    		}
    	}
    
    	return _RelatedEndCache[hashKey](obj);
    }
    
    /// <summary>
    /// Checks whether the specified navigation property is loaded.
    /// </summary>
    /// <remarks>By default only the collection navigation properties provide a way to ask whether they are loaded, there is no builtin way to ask whether 
    /// a navigation property pointing to a single entity is loaded or not.</remarks>
    /// <typeparam name="TObj">the type of the entity</typeparam>
    /// <typeparam name="TProperty">the type of the property to check</typeparam>
    /// <param name="expr">a lambda expression specifying the property you are interested in. The expression must be a simple <example>obj => obj.Property</example>,
    /// and the property must be a navigation property, not a simple or complex property!</param>
    /// <returns></returns>
    public static bool IsLoaded<TObj, TProperty>(this TObj obj, Expression<Func<TObj, TProperty>> expr)
    	where TObj : IEntityWithRelationships
    	where TProperty : class {
    	return GetRelatedEnd(obj, expr).IsLoaded;
    }
    
    public static void Reload<TObj, TProperty>(this TObj obj, Expression<Func<TObj, TProperty>> expr, System.Data.Objects.MergeOption opt = System.Data.Objects.MergeOption.OverwriteChanges)
    	where TObj : IEntityWithRelationships
    	where TProperty : class {
    	GetRelatedEnd(obj, expr).Load(opt);
    }
    
    /// <summary>
    /// Removes the entity referenced by the specified navigation property from memory so that it is read from the database the next time it is needed.
    /// </summary>
    /// <param name="expr">a lambda expression specifying the property you are interested in. The expression must be a simple <example>obj => obj.Property</example>,
    /// and the property must be a navigation property, not a simple or complex property!</param>
    public static void UnLoad<TObj, TProperty>(this TObj obj, Expression<Func<TObj, TProperty>> expr)
    	where TObj : IEntityWithRelationships
    	where TProperty : class, IEntityWithRelationships {
    	if (obj.IsLoaded(expr)) {
    		var propty = expr.Compile().Invoke(obj);
    		if (propty == null) {
    			obj.Reload(expr);
    		} else {
    			obj.GetContext().Detach(propty);
    		}
    	}
    }
    
    



    ----------------------------------
    http://jendaperl.blogspot.com
    A Perl developer in the world of C#
    Tuesday, August 2, 2011 2:54 PM

All replies

  • if the entity's navigation is null, it will not be loaded in context. so I don't think we should detach it, right?


    I am fish.
    Wednesday, August 3, 2011 9:20 AM
  • If the entity's navigation is null and is marked "loaded" then apparently there was no matching record in the database when I first accessed the property. So yes, there is nothing to detach. On the other hand even if there now is a record in the database, I will not see it because the navigation property was loaded and will not be reloaded. 

    Using stuff available to an ordinary Joe there is no way to reload the property if it's not a collection, for an inquisitive fellow like me there is a way. But even I did not find a way to change the "is loaded" flag so that the navigation property is reloaded from the database only once needed.

     

    All using the full-blown EF objects with a database first development model.


    ----------------------------------
    http://jendaperl.blogspot.com
    A Perl developer in the world of C#
    Wednesday, August 3, 2011 5:12 PM
  • Hi JendaPerl,

    Welcome!

    I think you can try to use Entry().Collection().Load() method.

    var address = context.Addressesd.Find(2);
            var test = address.Claims;//null
            //run T-sql (insert into Claims values('hello',2))
            context.Entry<Address>(address).Collection(a=>a.Claims).Load();
            var test1 = address.Claims;//not null
    


    Based on my test, it works.

    Have a nice day.


    Alan Chen[MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Friday, August 5, 2011 7:42 AM
    Moderator
  • Yes, but that LOADs the navigation property which is not what I asked for. I wanted to UNLOAD it! So that it's again in the same state it was before I first looked and will be loaded if and only if I happen to need it later.
    ----------------------------------
    http://jendaperl.blogspot.com
    A Perl developer in the world of C#
    Tuesday, August 9, 2011 4:22 PM
  • Hi,

    It seems there's no good way to unload the navigation that's null, thanks for understanding.

    Have a nice day. 


    Alan Chen[MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, August 23, 2011 3:59 AM
    Moderator