New Posts New Posts RSS Feed: Contribution to Cocktail.Contrib
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

Contribution to Cocktail.Contrib

 Post Reply Post Reply
Author
Walid View Drop Down
Senior Member
Senior Member
Avatar

Joined: 14-Nov-2010
Posts: 161
Post Options Post Options   Quote Walid Quote  Post ReplyReply Direct Link To This Post Topic: Contribution to Cocktail.Contrib
    Posted: 27-Apr-2012 at 1:25pm
Hi,


I have some suggestion about some contribution to add to the Cocktail.Contrib project

In the repository I find the use of the actual FindAsync method "dangerous" because of the use of IPredicateDescription. Having the entity property's name writen as a string can brings errors. If the Entity property's name change in the code but not in the IPredicateDescription the code will compile.
I prefer the use of the expression so if something wrong I know it during the compile time and not during the execution of the application. 


when you use a sandbox for all the entity editing, you find yourself to always import then import back the entities and his graph. Devforce has a still not corrected bug in the ImportEntities method (see the post http://www.ideablade.com/forum/forum_posts.asp?TID=3008&PID=11813&title=importentities-and-nn-relations#11813).
The ImportEntity method below allow to import an entity and his navigation properties (given the 2nd parameters of the method) and does the workaround for the many to many relationship.
I wanted to write another method when the 2nd parameters would be an IList<Entityspan> to be able to have deep graph but I don't have enough knowledge of how to work with the metadata so I gave up (for now).

I hope you will find those methods usefull ...



Repository :
        /// <summary>
        ///   Retrieves one or more entities matching the provided predicate.
        /// </summary>
        /// <param name="predicate"> The predicate to filter the returned list of entities </param>
        /// <param name="sortSelector"> The sort criteria for the returned list of entities. </param>
        /// <param name="includeProperties"> Related entities to eager fetch together with the returned list of entities. </param>
        /// <param name="onSuccess"> Callback to be called when the entity retrieval was successful. </param>
        /// <param name="onFail"> Callback to be called when the entity retrieval failed. </param>
        /// <returns> Asynchronous operation result. </returns>
        public OperationResult<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate,
                                                         ISortSelector sortSelector = null,
                                                         string includeProperties = null,
                                                         Action<IEnumerable<T>> onSuccess = null,
                                                         Action<Exception> onFail = null)
        {
            var query = GetFindQuery(predicate, sortSelector, includeProperties);
            return query.ExecuteAsync().OnComplete(onSuccess, onFail).AsOperationResult();
        }
 
        /// <summary>
        ///   Returns the query to retrieve a list of entities
        /// </summary>
        /// <param name="predicate"> The predicate used to qualify the list of entities. </param>
        /// <param name="sortSelector"> The sort selector used to sort the list of entities. </param>
        /// <param name="includeProperties"> An optional list of included properties. </param>
        /// <remarks>
        ///   Override to modify the query used to retrieve a list of entities
        /// </remarks>
        protected virtual IEntityQuery<T> GetFindQuery(Expression<Func<T, bool>> predicate,
                                                       ISortSelector sortSelector, string includeProperties)
        {
            IEntityQuery<T> query = EntityManager.GetQuery<T>();
            if (predicate != null)
                query = query.Where(predicate);
            if (sortSelector != null)
                query = query.OrderBySelector(sortSelector);
            if (!string.IsNullOrWhiteSpace(includeProperties))
                query = ParseIncludeProperties(includeProperties)
                    .Aggregate(query, (q, includeProperty) => q.Include(includeProperty));
 
            return query;
        }



UnitOfwork :
        private static void EnsureEntitiesAreUnmodified(IEnumerable entities)
        {
            if (entities.Cast<IEntity>()
                .Any(e => e.EntityAspect.EntityState != EntityState.Unchanged))
            {
                throw new InvalidOperationException(
                    "Cannot import entities with pending changes");
            }
        }
 
 
        public object ImportEntity(object entitySource, string[] entityRelationLinks)
        {
            var entity = EntityAspect.Wrap(entitySource);
 
            if (entity == null || entity.IsNullOrPendingEntity)
                throw new ArgumentException();
 
            if (entity.EntityManager == EntityManager)
                return entitySource;
 
            IEnumerable entitiesToImport = null;
 
            if (entityRelationLinks != null)
            {
                // Workaround for the many to many import bug
                var entitydest = EntityManager.FindEntity(entity.EntityKey);
                var destinationEntity = EntityAspect.Wrap(entitydest);
                if (destinationEntity != null)
                {
                    destinationEntity.EntityMetadata.ListNavigationProperties.ForEach(listNavigationnProperty =>
                    {
                        if (!listNavigationnProperty.IsCollectionReadOnly && listNavigationnProperty.IsManyToMany && entityRelationLinks.Any(link => link.Equals(listNavigationnProperty.Name)))
                        {
                            var children = listNavigationnProperty.GetValue(entitydest) as IEnumerable<IEntity>;
                            children.ToList().ForEach(ent => listNavigationnProperty.GetEntityReference(entitydest).RemoveEntity(ent, true));
                        }
                    });
                }
 
                if (entityRelationLinks.Any())
                {
                    var spans = new List<EntitySpan>();
                    entityRelationLinks.ForEach(
                        relationLink =>
                        spans.Add(new EntitySpan(entitySource.GetType(),
                                                 EntityManager.MetadataStore.GetEntityRelation(entitySource.GetType(),
                                                                                               relationLink))));
 
                    entitiesToImport = entity.EntityManager.FindEntityGraph(new object[] {entitySource}, spans,
                                                                            EntityState.AllButDetached);
                }
 
            }
            else
                entitiesToImport = new[] { entitySource };
 
            EnsureEntitiesAreUnmodified(entitiesToImport);
            EntityManager.ImportEntities(entitiesToImportMergeStrategy.OverwriteChanges);
            var importedEntity = EntityManager.FindEntity(entity.EntityKey);
            return importedEntity;         }     }
Back to Top
mgood View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
Post Options Post Options   Quote mgood Quote  Post ReplyReply Direct Link To This Post Posted: 27-Apr-2012 at 2:03pm
Thanks Walid,
Yes, I was already considering adding an overload for FindAsync exactly like you have it. I went with the IPredicateDescription initially, because I'm finding that for many developers Expressions are quite mystical.
 
If you want to actually contribute this to CocktailContrib please do so by following the process on github. The reason CocktailContrib is hosted on github is exactly for this purpose.
 
 
 
Back to Top
Walid View Drop Down
Senior Member
Senior Member
Avatar

Joined: 14-Nov-2010
Posts: 161
Post Options Post Options   Quote Walid Quote  Post ReplyReply Direct Link To This Post Posted: 03-May-2012 at 6:56am
Hi marcel,

I added the overload function to github 4 days ago then I pulled a request on your fork. Is it the good way to do so ?
Back to Top
mgood View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
Post Options Post Options   Quote mgood Quote  Post ReplyReply Direct Link To This Post Posted: 03-May-2012 at 9:07am
I received a notification from github, but haven't had time to look at it yet. If anything looks out of the ordinary I let you know. I also want to find out the status of the merge bug fix first. Thanks for contributing.
Back to Top
mgood View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
Post Options Post Options   Quote mgood Quote  Post ReplyReply Direct Link To This Post Posted: 05-May-2012 at 1:13pm
Walid,
Looks like it worked. I was able to pull your contribution. Just to confirm, you only added the FindAsync overload correct?
Back to Top
Walid View Drop Down
Senior Member
Senior Member
Avatar

Joined: 14-Nov-2010
Posts: 161
Post Options Post Options   Quote Walid Quote  Post ReplyReply Direct Link To This Post Posted: 05-May-2012 at 1:31pm
Nice.

yes, i wanted to see if the 6.1.7 had the importentities many to many bug fixed before to put the other method.
sincère it's still not fixed i will put it later this week-end. 
Back to Top
mgood View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
Post Options Post Options   Quote mgood Quote  Post ReplyReply Direct Link To This Post Posted: 05-May-2012 at 3:06pm
Sounds good. I merged in the FindAsync overload, but I haven't pushed it yet. I may release a minor updated to Cocktail 0.6 with a fix to the DialogManager I'm working on. I have modified your overload slightly. It looks like this now. Yours was still using the DevForce SortSelector.
        OperationResult<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> filter = null,
                                                  Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
                                                  string includeProperties = null,
                                                  Action<IEnumerable<T>> onSuccess = null,
                                                  Action<Exception> onFail = null);
Back to Top
Walid View Drop Down
Senior Member
Senior Member
Avatar

Joined: 14-Nov-2010
Posts: 161
Post Options Post Options   Quote Walid Quote  Post ReplyReply Direct Link To This Post Posted: 07-May-2012 at 1:43am
Hi marcel,

I added the UOW functions. 
Since you didn't push it yet, the new pull request automatically included my previous commit. I didn't find how to exclude it, hope it won't cause any trouble.
Back to Top
mgood View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
Post Options Post Options   Quote mgood Quote  Post ReplyReply Direct Link To This Post Posted: 07-May-2012 at 12:38pm
Thanks. I'll take a look.
Back to Top
mgood View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
Post Options Post Options   Quote mgood Quote  Post ReplyReply Direct Link To This Post Posted: 08-May-2012 at 3:53pm
Walid,
I integrated your latest contribution and pushed it all to github, but I made major changes to the import functionality. I decided to reimplement it from scratch and not bring over your bug fix. I let my engineers fix the issue in DevForce itself. Your bug fix seemed to fix it for your situation, but didn't look like a general fix that would work for everybody. For example it looks like you had to restrict import to unchanged entities for your fix to work. I removed that restriction and added an optional MergeStrategy parameter. The caller should decide how they want to merge the entities into the current UoW and it shouldn't prevent them from importing entities with pending changes if they choose to do so.
 
On the topic of many-to-many relationships, we actually advise against using them. Many-to-many relationships are problematic on many levels, because you don't have access to the association object. We recommend to break a many-to-many relationship into two one-to-many and explicity model the association object. You may eventually find it difficult to build a UI without having access to the association object.
 
I'm also kinda wondering why you find yourself doing a lot of imports. It was actually kind of on purpose to not have import methods. If you look at TempHire, we generally recommend using projections for the master screen for better performance and then have the detail sandbox load a single entity graph, modify and save it. Using the Cocktail change sync feature you can ensure that other sandboxes holding copies of the affected entities get updated with saved changes. I'm rarely doing explicit imports even in apps much larger and more complex than TempHire. Anyway, for now the methods are there, I may decide to remove them once I move it all over to Cocktail itself.
 
I've pushed a new NuGet package v1.0.3, which has a dependency to Cocktail v0.6.1. Cocktail v0.6.1 is a minor update to v0.6.
Back to Top
Walid View Drop Down
Senior Member
Senior Member
Avatar

Joined: 14-Nov-2010
Posts: 161
Post Options Post Options   Quote Walid Quote  Post ReplyReply Direct Link To This Post Posted: 09-May-2012 at 3:03am

Hi marcel,

Thanks for adding the contribution.

I agree that many to many can be sometimes problematic. As exemple, I didn't find the good way to use AttachEntities with a many to many relationship.
Denis says that what I call a bug looks like a feature for him, maybe ... I will think about the pro/cons of your proposal to break up the model.

About the sandbox part, I don't always use projections. When the entities are small, I don't need to use a projection. In that case I want to import them in a new entitymanager for my editing and to avoid a redundant query to the base. Yes, I can have a configuration for the Sandbox and use a special SyncInterceptor which won't allow import but will allow all export.
That would remove the final Import (sandbox to original EM), but I don't see how to avoid the initial Import (the SyncInterceptor is no use there).
Maybe I am missing something ...

Back to Top
mgood View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
Post Options Post Options   Quote mgood Quote  Post ReplyReply Direct Link To This Post Posted: 09-May-2012 at 10:50am
I generally always load a fresh graph into a sandbox editor, because going the import route risks that you import stale data. Of course the risk of importing stale data depends on how frequent changes happen in your application and how likely it is that multiple users work on the same data. Loading a fresh graph is the safe route. One thing I learned over the years is that users use an application in unexpected ways, so the safe route is generally the better route to avoid expensive fixes and refactoring down the line. I hope you have concurrency checks turned on so a user can never save stale data, but you still let them edit something just to be stopped in thier tracks by a concurrency exception.
 
Food for thought.
Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down