CachingPolicy to include entities of certain types

Nov 25, 2014 at 5:19 PM
Edited Nov 25, 2014 at 5:20 PM
Hi,

I'm trying out the second level cache and I'm currently using the InMemoryCache that it comes with. I'm trying to configure it to not include all entities so I was hoping to make the cacheable entities implement an interface, like:
public class EntityName : ICacheable
{
    ...
}
But is there anyway to test/check this from the EntitySetBase that gets passed in to the CanBeReached method of the CachingPolicies?
public class MyCachingPolicy : CachingPolicy
    {
        protected override bool CanBeCached(System.Collections.ObjectModel.ReadOnlyCollection<System.Data.Entity.Core.Metadata.Edm.EntitySetBase> affectedEntitySets, string sql, IEnumerable<KeyValuePair<string, object>> parameters)
        {
            // only cache if only contains references to cacheable entities
            bool onlyCacheableItems = true;
            
            foreach (var set in affectedEntitySets)
            {
// is it possible to do this?
                if (!(set.ElementType.BaseType is ICacheable))
                {
                    onlyCacheableItems = false;
                    break;
                }
            }

            return onlyCacheableItems;
        }
    }
I can't see an obvious way to do this. Is it even possible or is the way that I'm doing it not the recommended way?

Kind regards
Sidharth
Nov 26, 2014 at 6:41 AM
Hi Sidharth,

There is no easy way to tell which tables store which entity types. You would have to go through the mapping API (which was opened in EF6.1 AFAIR) which is quite convoluted. Here is an example showing how to find which table an entity is being saved to - so the opposite you want to do. You may try kind of reversing the steps. I am not sure if this handles more complicated cases where multiple entity types are saved to the same table (i.e. the TPH hierarchy mapping) or if an entity is being saved to multiple tables (Entity Splitting or the TPT hierarchy mapping).

Hope this helps,
Pawel
Nov 27, 2014 at 8:46 AM
Hi Pawel,

Thanks for that information - much appreciated. I'll try that out and report how I get along.

Kind regards
Sidharth
Dec 10, 2014 at 12:55 PM
Hi,

I've been able to use that article to find the entity types from the tables. I've not tested it with the complex scenarios that you mentioned but it seems for simple cases it works.
       /// <summary>
        /// Gets the entity type that corresponds to this entity set base, table.
        /// </summary>
        /// <param name="setBase">The entity set base</param>
        /// <param name="metadata">The database metadata in which to check</param>
        /// <returns>The entity type that corresponds to this entity set base, or null if not found.</returns>
        public static Type GetEntityType(EntitySetBase setBase, MetadataWorkspace metadata)
        {
            // if it's an entity framework internal table then return null
            if (IsEntityFrameworkInternalTable(setBase))
            {
                return null;
            }

            // Get the part of the model that contains info about the actual CLR types
            var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));

            // Get conceptual model
            var primitiveTypeCollection = metadata.GetItems<EntityContainer>(DataSpace.CSpace).Single();

            // Get the mapping model
            var entityPrimitiveMappingCollection =
                metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace).Single();

            // Get the entity type from the model and find which entities this set base refers to
            var oSpace = metadata.GetItems<EntityType>(DataSpace.OSpace);
            foreach (var entityType in oSpace)
            {
                // Get the entity set that uses this entity type
                EntitySet entitySet = primitiveTypeCollection.EntitySets
                                                             .SingleOrDefault(s => s.ElementType.Name == entityType.Name);

                if (entitySet == null)
                {
                    continue;
                }

                // Find the mapping between conceptual and storage model for this entity set
                var mapping = entityPrimitiveMappingCollection.EntitySetMappings
                                                              .Single(s => s.EntitySet == entitySet);

                // Find the storage entity set (table) that the entity is mapped to. 
                // This could be mapped to multiple entities
                IEnumerable<MappingFragment> fragments = mapping.EntityTypeMappings.SelectMany(x => x.Fragments);
                var tableEntitySets = fragments.Select(x => x.StoreEntitySet);
                var tableProperties = tableEntitySets.Select(x => x.MetadataProperties);

                // is this the table we are looking for?
                var result = tableProperties.Where(y => y["Table"].Value == setBase.ElementType.Name);
                if (result != null && result.Count() > 0)
                {
                    Type clrType = objectItemCollection.GetClrType(entityType);
                    return clrType;
                }
            }

            // not found!
            return null;

        }

        /// <summary>
        /// Finds if the table is an entity framework specific table.
        /// </summary>
        /// <param name="setBase">The set base</param>
        /// <returns>True if it's an EF internal table</returns>
        public static bool IsEntityFrameworkInternalTable(EntitySetBase setBase)
        {
            if (setBase.Table.StartsWith("__") || setBase.Table.StartsWith("Edm"))
            {
                return true;
            }

            return false;
        }
Kind regards
Sidharth
Dec 11, 2014 at 6:26 AM
Hi Sidharth,

You pretty much nailed it - this is more or less what I had in mind. Good job! (...and thanks for posting - I believe this can help many people who need/want to do this but are afraid of MetadataWorkspace).

Thanks,
Pawel
Jul 10, 2016 at 1:27 AM
Edited Jul 10, 2016 at 1:53 AM
Sorry to be slightly late in the game on this one, but how do we get a MetadataWorkspace, or more specifically, ObjectContext instance from within CacheablePolicy?
Jul 17, 2016 at 6:33 AM
Edited Jul 17, 2016 at 6:33 AM
Not sure why you need an instance of ObjectContext - it actually does not seem like a good idea to keep the context around. I also looked at the code a bit and I don't see a good way to get a MetadataWorkspace instance. Seems, it would be very easy to pass it to the CanBeCached method as parameter but unfortunately it is not being passed currently. The workaround would be to keep a reference to the custom CachingPolicy and set it once you get it. This is pretty gross though. You could also try a much simpler solution of identifying tables for which you want to cache results and just hardcode them. If you are worried about the table names in the list getting out of synch with actual table names you could introduce a method that validates that all the tables in the list exist in the model. Another solution could be to use a custom attribute instead of the interface. The attribute would take a parameter which would be the table name. Again you could validate the values to make sure they are in sync with the database.

Hope this helps,
Pawel
Jul 19, 2016 at 1:41 AM
From memory I think if I have an object context, then i'd have a MetadataWorkspace.
I thought of all those alternate work arounds you mentioned, but decided in the end just to default the whole thing to not cache anything, and then use your .Cache() extension methods where I wanted.
I see it's really a per-linq/sql type cache, rather than per entity/table. I guess that makes a kind of sense, and I've gone with that. I guess it just would be cool if you could give that caching power to the Entity itself, although some wouldn't want to assign this responsibility.
Anyway, I'm happy :)