|
//---------------------------------------------------------------------
// <copyright file="MetadataCache.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
namespace System.Data.Metadata.Edm
{
using System;
using System.Collections.Generic;
using System.Data.Common.Utils;
using System.Data.Entity;
using System.Data.Mapping;
using System.Diagnostics;
using System.Runtime.Versioning;
using System.Security.Permissions;
using System.Threading;
using System.Xml;
/// <summary>
/// Runtime Metadata Cache - this class contains the metadata cache entry for edm and store collections.
/// </summary>
internal static class MetadataCache
{
#region Fields
private const string s_dataDirectory = "|datadirectory|";
private const string s_metadataPathSeparator = "|";
// This is the period in the periodic cleanup measured in milliseconds
private const int cleanupPeriod = 5 * 60 * 1000;
// This dictionary contains the cache entry for the edm item collection. The reason why we need to keep a seperate dictionary
// for CSpace item collection is that the same model can be used for different providers. We don't want to load the model
// again and again
private static readonly Dictionary<string, EdmMetadataEntry> _edmLevelCache = new Dictionary<string, EdmMetadataEntry>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// This dictionary contains the store cache entry - this entry will only keep track of StorageMappingItemCollection, since internally
/// storage mapping item collection keeps strong references to both edm item collection and store item collection.
/// </summary>
private static readonly Dictionary<string, StoreMetadataEntry> _storeLevelCache = new Dictionary<string, StoreMetadataEntry>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// The list maintains the store metadata entries that are still in use, maybe because someone is still holding a strong reference
/// to it. We need to scan this list everytime the clean up thread wakes up and make sure if the item collection is no longer in use,
/// call clear on query cache
/// </summary>
private static readonly List<StoreMetadataEntry> _metadataEntriesRemovedFromCache = new List<StoreMetadataEntry>();
private static Memoizer<string, List<MetadataArtifactLoader>> _artifactLoaderCache = new Memoizer<string, List<MetadataArtifactLoader>>(MetadataCache.SplitPaths, null);
/// <summary>
/// Read/Write lock for edm cache
/// </summary>
private static readonly object _edmLevelLock = new object();
/// <summary>
/// Read/Write lock for the store cache
/// </summary>
private static readonly object _storeLevelLock = new object();
// Periodic thread which runs every n mins (look up the cleanupPeriod variable to see the exact time), walks through
// every item in other store and edm cache and tries to do some cleanup
private static Timer timer = new Timer(PeriodicCleanupCallback, null, cleanupPeriod, cleanupPeriod);
#endregion
#region Methods
/// <summary>
/// The purpose of the thread is to do cleanup. It marks the object in various stages before it actually cleans up the object
/// Here's what this does for each entry in the cache:
/// 1> First checks if the entry is marked for cleanup.
/// 2> If the entry is marked for cleanup, that means its in one of the following 3 states
/// a) If the strong reference to item collection is not null, it means that this item was marked for cleanup in
/// the last cleanup cycle and we must make the strong reference set to null so that it can be garbage collected.
/// b) Otherwise, we are waiting for GC to collect the item collection so that we can remove this entry from the cache
/// If the weak reference to item collection is still alive, we don't do anything
/// c) If the weak reference to item collection is not alive, we need to remove this entry from the cache
/// 3> If the entry is not marked for cleanup, then check whether the weak reference to entry token is alive
/// a) if it is alive, then this entry is in use and we must do nothing
/// b) Otherwise, we can mark this entry for cleanup
/// </summary>
/// <param name="state"></param>
private static void PeriodicCleanupCallback(object state)
{
// Perform clean up on edm cache
DoCacheClean<EdmMetadataEntry>(_edmLevelCache, _edmLevelLock);
// Perform clean up on store cache
DoCacheClean<StoreMetadataEntry>(_storeLevelCache, _storeLevelLock);
}
/// <summary>
/// A helper function for splitting up a string that is a concatenation of strings delimited by the metadata
/// path separator into a string list. The resulting list is NOT sorted.
/// </summary>
/// <param name="paths">The paths to split</param>
/// <returns>An array of strings</returns>
[ResourceExposure(ResourceScope.Machine)] //Exposes the file name which is a Machine resource
[ResourceConsumption(ResourceScope.Machine)] //For MetadataArtifactLoader.Create method call. But the path is not created in this method.
internal static List<MetadataArtifactLoader> SplitPaths(string paths)
{
Debug.Assert(!string.IsNullOrEmpty(paths), "paths cannot be empty or null");
string[] results;
// This is the registry of all URIs in the global collection.
HashSet<string> uriRegistry = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
List<MetadataArtifactLoader> loaders = new List<MetadataArtifactLoader>();
// If the argument contains one or more occurrences of the macro '|DataDirectory|', we
// pull those paths out so that we don't lose them in the string-splitting logic below.
// Note that the macro '|DataDirectory|' cannot have any whitespace between the pipe
// symbols and the macro name. Also note that the macro must appear at the beginning of
// a path (else we will eventually fail with an invalid path exception, because in that
// case the macro is not expanded). If a real/physical folder named 'DataDirectory' needs
// to be included in the metadata path, whitespace should be used on either or both sides
// of the name.
//
List<string> dataDirPaths = new List<string>();
int indexStart = paths.IndexOf(MetadataCache.s_dataDirectory, StringComparison.OrdinalIgnoreCase);
while (indexStart != -1)
{
int prevSeparatorIndex = indexStart == 0 ? -1 : paths.LastIndexOf(
MetadataCache.s_metadataPathSeparator,
indexStart - 1, // start looking here
StringComparison.Ordinal
);
int macroPathBeginIndex = prevSeparatorIndex + 1;
// The '|DataDirectory|' macro is composable, so identify the complete path, like
// '|DataDirectory|\item1\item2'. If the macro appears anywhere other than at the
// beginning, splice out the entire path, e.g. 'C:\item1\|DataDirectory|\item2'. In this
// latter case the macro will not be expanded, and downstream code will throw an exception.
//
int indexEnd = paths.IndexOf(MetadataCache.s_metadataPathSeparator,
indexStart + MetadataCache.s_dataDirectory.Length,
StringComparison.Ordinal);
if (indexEnd == -1)
{
dataDirPaths.Add(paths.Substring(macroPathBeginIndex));
paths = paths.Remove(macroPathBeginIndex); // update the concatenated list of paths
break;
}
dataDirPaths.Add(paths.Substring(macroPathBeginIndex, indexEnd - macroPathBeginIndex));
// Update the concatenated list of paths by removing the one containing the macro.
//
paths = paths.Remove(macroPathBeginIndex, indexEnd - macroPathBeginIndex);
indexStart = paths.IndexOf(MetadataCache.s_dataDirectory, StringComparison.OrdinalIgnoreCase);
}
// Split the string on the separator and remove all spaces around each parameter value
results = paths.Split(new string[] { MetadataCache.s_metadataPathSeparator }, StringSplitOptions.RemoveEmptyEntries);
// Now that the non-macro paths have been identified, merge the paths containing the macro
// into the complete list.
//
if (dataDirPaths.Count > 0)
{
dataDirPaths.AddRange(results);
results = dataDirPaths.ToArray();
}
for (int i = 0; i < results.Length; i++)
{
// Trim out all the spaces for this parameter and add it only if it's not blank
results[i] = results[i].Trim();
if (results[i].Length > 0)
{
loaders.Add(MetadataArtifactLoader.Create(
results[i],
MetadataArtifactLoader.ExtensionCheck.All, // validate the extension against all acceptable values
null,
uriRegistry
));
}
}
return loaders;
}
/// <summary>
/// Walks through the given cache and calls cleanup on each entry in the cache
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="cache"></param>
/// <param name="objectToLock"></param>
private static void DoCacheClean<T>(Dictionary<string, T> cache, object objectToLock) where T: MetadataEntry
{
// Sometime, for some reason, timer can be initialized and the cache is still not initialized.
if (cache != null)
{
List<KeyValuePair<string, T>> keysForRemoval = null;
lock (objectToLock)
{
// we should check for type of the lock object first, since otherwise we might be reading the count of the list
// while some other thread might be modifying it. For e.g. when this function is called for edmcache,
// we will be acquiring edmlock and trying to get the count for the list, while some other thread
// might be calling ClearCache and we might be adding entries to the list
if (objectToLock == _storeLevelLock && _metadataEntriesRemovedFromCache.Count != 0)
{
// First check the list of entries and remove things which are no longer in use
for (int i = _metadataEntriesRemovedFromCache.Count - 1; 0 <= i; i--)
{
if (!_metadataEntriesRemovedFromCache[i].IsEntryStillValid())
{
// Clear the query cache
_metadataEntriesRemovedFromCache[i].CleanupQueryCache();
// Remove the entry at the current index. This is the reason why we
// go backwards.
_metadataEntriesRemovedFromCache.RemoveAt(i);
}
}
}
// We have to use a list to keep track of the keys to remove because we can't remove while enumerating
foreach (KeyValuePair<string, T> pair in cache)
{
if (pair.Value.PeriodicCleanUpThread())
{
if (keysForRemoval == null)
{
keysForRemoval = new List<KeyValuePair<string, T>>();
}
keysForRemoval.Add(pair);
}
}
// Remove all the entries from the cache
if (keysForRemoval != null)
{
for (int i = 0; i < keysForRemoval.Count; i++)
{
keysForRemoval[i].Value.Clear();
cache.Remove(keysForRemoval[i].Key);
}
}
}
}
}
/// <summary>
/// Retrieves an cache entry holding to edm metadata for a given cache key
/// </summary>
/// <param name="cacheKey">string containing all the files from which edm metadata is to be retrieved</param>
/// <param name="composite">An instance of the composite MetadataArtifactLoader</param>
/// <param name="entryToken">The metadata entry token for the returned entry</param>
/// <returns>Returns the entry containing the edm metadata</returns>
internal static EdmItemCollection GetOrCreateEdmItemCollection(string cacheKey,
MetadataArtifactLoader loader,
out object entryToken)
{
EdmMetadataEntry entry = GetCacheEntry<EdmMetadataEntry>(_edmLevelCache, cacheKey, _edmLevelLock,
new EdmMetadataEntryConstructor(), out entryToken);
// Load the edm item collection or if the collection is already loaded, check for security permission
LoadItemCollection(new EdmItemCollectionLoader(loader), entry);
return entry.EdmItemCollection;
}
/// <summary>
/// Retrieves an entry holding store metadata for a given cache key
/// </summary>
/// <param name="cacheKey">The connection string whose store metadata is to be retrieved</param>
/// <param name="composite">An instance of the composite MetadataArtifactLoader</param>
/// <param name="entryToken">The metadata entry token for the returned entry</param>
/// <returns>the entry containing the information on how to load store metadata</returns>
internal static StorageMappingItemCollection GetOrCreateStoreAndMappingItemCollections(
string cacheKey,
MetadataArtifactLoader loader,
EdmItemCollection edmItemCollection,
out object entryToken)
{
StoreMetadataEntry entry = GetCacheEntry<StoreMetadataEntry>(_storeLevelCache, cacheKey, _storeLevelLock,
new StoreMetadataEntryConstructor(), out entryToken);
// Load the store item collection or if the collection is already loaded, check for security permission
LoadItemCollection(new StoreItemCollectionLoader(edmItemCollection, loader), entry);
return entry.StorageMappingItemCollection;
}
internal static List<MetadataArtifactLoader> GetOrCreateMetdataArtifactLoader(string paths)
{
return _artifactLoaderCache.Evaluate(paths);
}
/// <summary>
/// Get the entry from the cache given the cache key. If the entry is not present, it creates a new entry and
/// adds it to the cache
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="cache"></param>
/// <param name="cacheKey"></param>
/// <param name="entryToken"></param>
/// <param name="metadataEntry"></param>
/// <param name="objectToLock"></param>
/// <returns></returns>
private static T GetCacheEntry<T>(Dictionary<string, T> cache, string cacheKey, object objectToLock,
IMetadataEntryConstructor<T> metadataEntry, out object entryToken) where T: MetadataEntry
{
T entry;
// In the critical section, we need to do the minimal thing to ensure correctness
// Within the lock, we will see if an entry is present. If it is not, we will create a new entry and
// add it to the cache. In either case, we need to ensure the token to make sure so that any other
// thread that comes looking for the same entry does nothing in this critical section
// Also the cleanup thread doesn't do anything since the token is alive
lock (objectToLock)
{
if (cache.TryGetValue(cacheKey, out entry))
{
entryToken = entry.EnsureToken();
}
else
{
entry = metadataEntry.GetMetadataEntry();
entryToken = entry.EnsureToken();
cache.Add(cacheKey, entry);
}
}
return entry;
}
/// <summary>
/// Loads the item collection for the entry
/// </summary>
/// <param name="itemCollectionLoader">struct which loads an item collection</param>
/// <param name="entry">entry whose item collection needs to be loaded</param>
private static void LoadItemCollection<T>(IItemCollectionLoader<T> itemCollectionLoader, T entry) where T : MetadataEntry
{
// At this point, you have made sure that there is an entry with an alive token in the cache so that
// other threads can find it if they come querying for it, and cleanup thread won't clean the entry
// If two or more threads come one after the other, we don't won't both of them to load the metadata.
// So if one of them is loading the metadata, the other should wait and then use the same metadata.
// For that reason, we have this lock on the entry itself to make sure that this happens. Its okay to
// update the item collection outside the lock, since assignment are guarantees to be atomic and no two
// thread are updating this at the same time
bool isItemCollectionAlreadyLoaded = true;
if (!entry.IsLoaded)
{
lock (entry)
{
if (!entry.IsLoaded)
{
itemCollectionLoader.LoadItemCollection(entry);
isItemCollectionAlreadyLoaded = false;
}
}
}
Debug.Assert(entry.IsLoaded, "The entry must be loaded at this point");
// Making sure that the thread which loaded the item collection is not checking for file permisssions
// again
if (isItemCollectionAlreadyLoaded)
{
entry.CheckFilePermission();
}
}
/// <summary>
/// Remove all the entries from the cache
/// </summary>
internal static void Clear()
{
lock (_edmLevelLock)
{
_edmLevelCache.Clear();
}
lock (_storeLevelLock)
{
// Call clear on each of the metadata entries. This is to make sure we clear all the performance
// counters associated with the query cache
foreach (StoreMetadataEntry entry in _storeLevelCache.Values)
{
// Check if the weak reference to item collection is still alive
if (entry.IsEntryStillValid())
{
_metadataEntriesRemovedFromCache.Add(entry);
}
else
{
entry.Clear();
}
}
_storeLevelCache.Clear();
}
Memoizer<string, List<MetadataArtifactLoader>> artifactLoaderCacheTemp =
new Memoizer<string, List<MetadataArtifactLoader>>(MetadataCache.SplitPaths, null);
Interlocked.CompareExchange(ref _artifactLoaderCache, artifactLoaderCacheTemp, _artifactLoaderCache);
}
#endregion
#region InlineClasses
/// <summary>
/// The base class having common implementation for all metadata entry classes
/// </summary>
private abstract class MetadataEntry
{
private WeakReference _entryTokenReference;
private ItemCollection _itemCollection;
private WeakReference _weakReferenceItemCollection;
private bool _markEntryForCleanup;
private FileIOPermission _filePermissions;
/// <summary>
/// The constructor for constructing this MetadataEntry
/// </summary>
internal MetadataEntry()
{
// Create this once per life time of the object. Creating extra weak references causing unnecessary GC pressure
_entryTokenReference = new WeakReference(null);
_weakReferenceItemCollection = new WeakReference(null);
}
/// <summary>
/// returns the item collection inside this metadata entry
/// </summary>
protected ItemCollection ItemCollection { get { return _itemCollection; } }
/// <summary>
/// Update the entry with the given item collection
/// </summary>
/// <param name="itemCollection"></param>
protected void UpdateMetadataEntry(ItemCollection itemCollection, FileIOPermission filePermissions)
{
Debug.Assert(_entryTokenReference.IsAlive, "You must call Ensure token before you call this method");
Debug.Assert(_markEntryForCleanup == false, "The entry must not be marked for cleanup");
Debug.Assert(_itemCollection == null, "Item collection must be null");
Debug.Assert(_filePermissions == null, "filePermissions must be null");
// Update strong and weak reference for item collection
_weakReferenceItemCollection.Target = itemCollection;
_filePermissions = filePermissions;
// do this last, because it signals that we are loaded
_itemCollection = itemCollection;
}
internal bool IsLoaded { get { return _itemCollection != null; } }
/// <summary>
/// This method is called periodically by the cleanup thread to make the unused entries
/// go through various stages, before it is ready for cleanup. If it is ready, this method
/// returns true and then the entry is completely removed from the cache
/// </summary>
/// <returns></returns>
internal bool PeriodicCleanUpThread()
{
// Here's what this does for each entry in the cache:
// 1> First checks if the entry is marked for cleanup.
// 2> If the entry is marked for cleanup, that means its in one of the following 3 states
// a) If the strong reference to item collection is not null, it means that this item was marked for cleanup in
// the last cleanup cycle and we must make the strong reference set to null so that it can be garbage collected. (GEN 2)
// b) Otherwise, we are waiting for GC to collect the item collection so that we can remove this entry from the cache
// If the weak reference to item collection is still alive, we don't do anything
// c) If the weak reference to item collection is not alive, we need to remove this entry from the cache (GEN 3)
// 3> If the entry is not marked for cleanup, then check whether the weak reference to entry token is alive
// a) if it is alive, then this entry is in use and we must do nothing
// b) Otherwise, we can mark this entry for cleanup (GEN 1)
if (_markEntryForCleanup)
{
Debug.Assert(_entryTokenReference.IsAlive == false, "Entry Token must never be alive if the entry is marked for cleanup");
if (_itemCollection != null)
{
// GEN 2
_itemCollection = null;
}
else if (!_weakReferenceItemCollection.IsAlive)
{
// GEN 3
_filePermissions = null;
// this entry must be removed from the cache
return true;
}
}
else if (!_entryTokenReference.IsAlive)
{
// GEN 1
// If someone creates a entity connection, and calls GetMetadataWorkspace. This creates an cache entry,
// but the item collection is not initialized yet (since store item collection are initialized only
// when one calls connection.Open()). Suppose now the connection is no longer used - in other words,
// open was never called and it goes out of scope. After some time when the connection gets GC'ed,
// entry token won't be alive any longer, but item collection inside it will be null, since it was never initialized.
// So we can't assert that item collection must be always initialized here
_markEntryForCleanup = true;
}
return false;
}
/// <summary>
/// Make sure that the entry has a alive token and returns that token - it can be new token or an existing
/// one, depending on the state of the entry
/// </summary>
/// <returns></returns>
internal object EnsureToken()
{
object entryToken = _entryTokenReference.Target;
ItemCollection itemCollection = (ItemCollection)_weakReferenceItemCollection.Target;
// When ensure token is called, the entry can be in different stages
// 1> Its a newly created entry - no token, no item collection, etc. Just create a new token and
// return back
// 2> An entry already in use - the weak reference to token must be alive. We just need to grab the token
// and return it
// 3> No one is using this entry and hence the token is no longer alive. If we have strong reference to item
// collection, then create a new token and return it
// 4> No one has used this token for one cleanup cycle and hence strong reference is null. But the weak reference
// is still alive. We need to make the initialize the strong reference again, create a new token and return it
// 5> This entry has not been used for long enough that even the weak reference is no longer alive. This entry is
// now exactly like a new entry, except that it is still marked for cleanup. Create a new token, set mark for
// cleanup to false and return the token
if (_entryTokenReference.IsAlive)
{
Debug.Assert(_markEntryForCleanup == false, "An entry with alive token cannot be marked for cleanup");
// ItemCollection strong pointer can be null or not null. If the entry has been created, and loadItemCollection
// hasn't been called yet, the token will be alive, but item collection will be null. If someone called
// load item collection, then item collection will not be non-null
return entryToken;
}
// If the entry token is not alive, then it can be either a new created entry with everything set
// to null or it must be one of the entries which is no longer in use
else if (_itemCollection != null)
{
Debug.Assert(_weakReferenceItemCollection.IsAlive, "Since the strong reference is still there, weak reference must also be alive");
// This means that no one is using the item collection, and its waiting to be cleanuped
}
else
{
if (_weakReferenceItemCollection.IsAlive)
{
Debug.Assert(_markEntryForCleanup, "Since the strong reference is null, this entry must be marked for cleanup");
// Initialize the strong reference to item collection
_itemCollection = itemCollection;
}
else
{
// no more references to the collection
// are available, so get rid of the permissions
// object. We will get a new one when we get a new collection
_filePermissions = null;
}
}
// Even if the _weakReferenceItemCollection is no longer alive, we will reuse this entry. Assign a new entry token and set mark for cleanup to false
// so that this entry is not cleared by the cleanup thread
entryToken = new object();
_entryTokenReference.Target = entryToken;
_markEntryForCleanup = false;
return entryToken;
}
/// <summary>
/// Check if the thread has appropriate permissions to use the already loaded metadata
/// </summary>
internal void CheckFilePermission()
{
Debug.Assert(_itemCollection != null, "Item collection must be present since we want to reuse the metadata");
Debug.Assert(_entryTokenReference.IsAlive, "This entry must be in use");
Debug.Assert(_markEntryForCleanup == false, "The entry must not marked for cleanup");
Debug.Assert(_weakReferenceItemCollection.IsAlive, "Weak reference to item collection must be alive");
// we will have an empty ItemCollection (no files were used to load it)
if (_filePermissions != null)
{
_filePermissions.Demand();
}
}
/// <summary>
/// Dispose the composite loader that encapsulates all artifacts
/// </summary>
internal virtual void Clear()
{
}
/// <summary>
/// This returns true if the entry is still in use - the entry can be use if the entry token is
/// still alive.If the entry token is still not alive, it means that no one is using this entry
/// and its okay to remove it. Today there is no
/// </summary>
/// <returns></returns>
internal bool IsEntryStillValid()
{
return _entryTokenReference.IsAlive;
}
}
/// <summary>
/// A metadata entry holding EdmItemCollection object for the cache
/// </summary>
private class EdmMetadataEntry : MetadataEntry
{
/// <summary>
/// Gets the EdmItemCollection for this entry
/// </summary>
internal EdmItemCollection EdmItemCollection
{
get
{
return (EdmItemCollection)this.ItemCollection;
}
}
/// <summary>
/// Just loads the edm item collection
/// </summary>
/// <returns></returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")]
internal void LoadEdmItemCollection(MetadataArtifactLoader loader)
{
Debug.Assert(loader != null, "loader is null");
List<XmlReader> readers = loader.CreateReaders(DataSpace.CSpace);
try
{
EdmItemCollection itemCollection = new EdmItemCollection(
readers,
loader.GetPaths(DataSpace.CSpace)
);
List<string> permissionPaths = new List<string>();
loader.CollectFilePermissionPaths(permissionPaths, DataSpace.CSpace);
FileIOPermission filePermissions = null;
if (permissionPaths.Count > 0)
{
filePermissions = new FileIOPermission(FileIOPermissionAccess.Read, permissionPaths.ToArray());
}
UpdateMetadataEntry(itemCollection, filePermissions);
}
finally
{
Helper.DisposeXmlReaders(readers);
}
}
}
/// <summary>
/// A metadata entry holding a StoreItemCollection and a StorageMappingItemCollection objects for the cache
/// </summary>
private class StoreMetadataEntry : MetadataEntry
{
private System.Data.Common.QueryCache.QueryCacheManager _queryCacheManager;
/// <summary>
/// The constructor for constructing this entry with an StoreItemCollection and a StorageMappingItemCollection
/// </summary>
/// <param name="compositeLoader">An instance of the composite MetadataArtifactLoader</param>
internal StoreMetadataEntry()
{
}
/// <summary>
/// Gets the StorageMappingItemCollection for this entry
/// </summary>
internal StorageMappingItemCollection StorageMappingItemCollection
{
get
{
return (StorageMappingItemCollection)this.ItemCollection;
}
}
/// <summary>
/// Load store specific metadata into the StoreItemCollection for this entry
/// </summary>
/// <param name="factory">The store-specific provider factory</param>
/// <param name="edmItemCollection">edmItemCollection</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")]
internal void LoadStoreCollection(EdmItemCollection edmItemCollection, MetadataArtifactLoader loader)
{
StoreItemCollection storeItemCollection = null;
IEnumerable<XmlReader> sSpaceXmlReaders = loader.CreateReaders(DataSpace.SSpace);
try
{
// Load the store side, however, only do so if we don't already have one
storeItemCollection = new StoreItemCollection(
sSpaceXmlReaders,
loader.GetPaths(DataSpace.SSpace));
}
finally
{
Helper.DisposeXmlReaders(sSpaceXmlReaders);
}
// If this entry is getting re-used, make sure that the previous query cache manager gets
// cleared up
if (_queryCacheManager != null)
{
_queryCacheManager.Clear();
}
// Update the query cache manager reference
_queryCacheManager = storeItemCollection.QueryCacheManager;
// With the store metadata in place, we can then load the mappings, however, only use it
// if we don't already have one
//
StorageMappingItemCollection storageMappingItemCollection = null;
IEnumerable<XmlReader> csSpaceXmlReaders = loader.CreateReaders(DataSpace.CSSpace);
try
{
storageMappingItemCollection = new StorageMappingItemCollection(
edmItemCollection,
storeItemCollection,
csSpaceXmlReaders,
loader.GetPaths(DataSpace.CSSpace));
}
finally
{
Helper.DisposeXmlReaders(csSpaceXmlReaders);
}
List<string> permissionPaths = new List<string>();
loader.CollectFilePermissionPaths(permissionPaths, DataSpace.SSpace);
loader.CollectFilePermissionPaths(permissionPaths, DataSpace.CSSpace);
FileIOPermission filePermissions = null;
if (permissionPaths.Count > 0)
{
filePermissions = new FileIOPermission(FileIOPermissionAccess.Read, permissionPaths.ToArray());
}
this.UpdateMetadataEntry(storageMappingItemCollection, filePermissions);
}
/// <summary>
/// Calls clear on query cache manager to make sure all the performance counters associated with the query
/// cache are gone
/// </summary>
internal override void Clear()
{
// there can be entries in cache for which the store item collection was never created. For e.g.
// if you create a new entity connection, but never call open on it
CleanupQueryCache();
base.Clear();
}
/// <summary>
/// Cleans and Dispose query cache manager
/// </summary>
internal void CleanupQueryCache()
{
if (null != _queryCacheManager)
{
_queryCacheManager.Dispose();
_queryCacheManager = null;
}
}
}
/// <summary>
/// Interface to construct the metadata entry so that code can be reused
/// </summary>
/// <typeparam name="T"></typeparam>
interface IMetadataEntryConstructor<T>
{
T GetMetadataEntry();
}
/// <summary>
/// Struct for creating EdmMetadataEntry
/// </summary>
private struct EdmMetadataEntryConstructor : IMetadataEntryConstructor<EdmMetadataEntry>
{
public EdmMetadataEntry GetMetadataEntry()
{
return new EdmMetadataEntry();
}
}
/// <summary>
/// Struct for creating StoreMetadataEntry
/// </summary>
private struct StoreMetadataEntryConstructor : IMetadataEntryConstructor<StoreMetadataEntry>
{
public StoreMetadataEntry GetMetadataEntry()
{
return new StoreMetadataEntry();
}
}
/// <summary>
/// Interface which constructs a new Item collection
/// </summary>
/// <typeparam name="T"></typeparam>
interface IItemCollectionLoader<T> where T : MetadataEntry
{
void LoadItemCollection(T entry);
}
private struct EdmItemCollectionLoader : IItemCollectionLoader<EdmMetadataEntry>
{
private MetadataArtifactLoader _loader;
public EdmItemCollectionLoader(MetadataArtifactLoader loader)
{
Debug.Assert(loader != null, "loader must never be null");
_loader = loader;
}
/// <summary>
/// Creates a new item collection and updates the entry with the item collection
/// </summary>
/// <param name="entry"></param>
/// <returns></returns>
public void LoadItemCollection(EdmMetadataEntry entry)
{
entry.LoadEdmItemCollection(_loader);
}
}
private struct StoreItemCollectionLoader : IItemCollectionLoader<StoreMetadataEntry>
{
private EdmItemCollection _edmItemCollection;
private MetadataArtifactLoader _loader;
/// <summary>
/// Constructs a struct from which you can load edm item collection
/// </summary>
/// <param name="factory"></param>
/// <param name="edmItemCollection"></param>
internal StoreItemCollectionLoader(EdmItemCollection edmItemCollection, MetadataArtifactLoader loader)
{
Debug.Assert(edmItemCollection != null, "EdmItemCollection must never be null");
Debug.Assert(loader != null, "loader must never be null");
//StoreItemCollection requires atleast one SSDL path.
if ((loader.GetPaths(DataSpace.SSpace) == null) || (loader.GetPaths(DataSpace.SSpace).Count == 0))
{
throw EntityUtil.Metadata(Strings.AtleastOneSSDLNeeded);
}
_edmItemCollection = edmItemCollection;
_loader = loader;
}
public void LoadItemCollection(StoreMetadataEntry entry)
{
entry.LoadStoreCollection(_edmItemCollection, _loader);
}
}
#endregion
}
}
|