Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: metadata cache factory #893

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 0 additions & 91 deletions src/Simple.OData.Client.Core/EdmMetadataCache.cs

This file was deleted.

5 changes: 5 additions & 0 deletions src/Simple.OData.Client.Core/ISession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,9 @@ public interface ISession : IDisposable
/// </summary>
/// <returns>An <see cref="HttpClient"/> instance.</returns>
HttpConnection GetHttpConnection();

/// <summary>
/// Clears base url metadata cache
/// </summary>
void ClearMetadataCache();
}
33 changes: 33 additions & 0 deletions src/Simple.OData.Client.Core/Metadata/EdmMetadataCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;

namespace Simple.OData.Client.Metadata;

internal class EdmMetadataCache: IEdmMetadataCache
{
private readonly ITypeCache typeCache;

public EdmMetadataCache(string key, string metadataDocument, ITypeCache typeCache)
{
if (string.IsNullOrWhiteSpace(key))
{
throw new ArgumentNullException(nameof(key));
}

if (string.IsNullOrWhiteSpace(metadataDocument))
{
throw new ArgumentNullException(nameof(metadataDocument));
}
this.typeCache = typeCache;

Key = key;
MetadataDocument = metadataDocument;
}
public string Key { get; }

public string MetadataDocument { get; }

public IODataAdapter GetODataAdapter(ISession session)
{
return session.Settings.AdapterFactory.CreateAdapterLoader(MetadataDocument, typeCache)(session);
}
}
61 changes: 61 additions & 0 deletions src/Simple.OData.Client.Core/Metadata/EdmMetadataCacheFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Simple.OData.Client.Extensions;

namespace Simple.OData.Client.Metadata;

internal class EdmMetadataCacheFactory : IEdmMetadataCacheFactory
{
private static readonly ConcurrentDictionary<string, IEdmMetadataCache> _instances = new();
private static readonly SemaphoreSlim semaphore = new(1);

public static void Clear()
{
_instances.Clear();
// NOTE: Is this necessary, if so should we wipe the ITypeCache constructors?
DictionaryExtensions.ClearCache();
}

public void Clear(string key)
{
_instances.TryRemove(key, out _);
}

public IEdmMetadataCache GetOrAdd(string key, Func<string, IEdmMetadataCache> valueFactory)
{
return _instances.GetOrAdd(key, valueFactory);
}

public async Task<IEdmMetadataCache> GetOrAddAsync(string key, Func<string, Task<IEdmMetadataCache>> valueFactory)
{
// Cheaper to check first before we do the remote call
if (_instances.TryGetValue(key, out var found))
{
return found;
}

// Just allow one schema request at a time, unlikely to be much contention but avoids multiple requests for same endpoint.
await semaphore
.WaitAsync()
.ConfigureAwait(false);

try
{
if (_instances.TryGetValue(key, out found))
{
return found;
}

found = await valueFactory(key)
.ConfigureAwait(false);

return _instances.GetOrAdd(key, found);
}
finally
{
semaphore.Release();
}
}
}
8 changes: 8 additions & 0 deletions src/Simple.OData.Client.Core/Metadata/IEdmMetadataCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Simple.OData.Client.Metadata;

public interface IEdmMetadataCache
{
string Key { get; }
string MetadataDocument { get; }
IODataAdapter GetODataAdapter(ISession session);
}
11 changes: 11 additions & 0 deletions src/Simple.OData.Client.Core/Metadata/IEdmMetadataCacheFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using System.Threading.Tasks;

namespace Simple.OData.Client.Metadata;

public interface IEdmMetadataCacheFactory
{
void Clear(string key);
IEdmMetadataCache GetOrAdd(string key, Func<string, IEdmMetadataCache> valueFactory);
Task<IEdmMetadataCache> GetOrAddAsync(string key, Func<string, Task<IEdmMetadataCache>> valueFactory);
}
11 changes: 10 additions & 1 deletion src/Simple.OData.Client.Core/ODataClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Simple.OData.Client.Metadata;

namespace Simple.OData.Client;

Expand Down Expand Up @@ -104,7 +105,15 @@ public static T ParseMetadataString<T>(string metadataString)
/// </summary>
public static void ClearMetadataCache()
{
EdmMetadataCache.Clear();
EdmMetadataCacheFactory.Clear();
}

/// <summary>
/// Clears base url metadata cache
/// </summary>
public void ClearBaseUrlMetadataCache()
{
Session.ClearMetadataCache();
}

/// <summary>
Expand Down
7 changes: 7 additions & 0 deletions src/Simple.OData.Client.Core/ODataClientSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Simple.OData.Client.Metadata;

namespace Simple.OData.Client;

Expand Down Expand Up @@ -126,6 +127,12 @@ public Uri? BaseUri
/// </value>
public string? MetadataDocument { get; set; }


/// <summary>
/// Gets or sets factory for custom metadata management that is used by the session.
/// </summary>
public IEdmMetadataCacheFactory? MetadataCacheFactory { get; set; }

/// <summary>
/// Gets the <see cref="ITypeCache"/> associated with the uri, used to register converters and dynamic types.
/// </summary>
Expand Down
18 changes: 11 additions & 7 deletions src/Simple.OData.Client.Core/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Simple.OData.Client.Metadata;

namespace Simple.OData.Client;

Expand All @@ -26,6 +27,7 @@ private Session(ODataClientSettings settings)
}

Settings = settings;
_metadataCacheFactory = settings.MetadataCacheFactory ?? new EdmMetadataCacheFactory();

if (!string.IsNullOrEmpty(Settings.MetadataDocument))
{
Expand Down Expand Up @@ -55,7 +57,7 @@ public IODataAdapter Adapter

public IMetadata Metadata => Adapter.GetMetadata();

public EdmMetadataCache? MetadataCache { get; private set; }
public IEdmMetadataCache? MetadataCache { get; private set; }

public ODataClientSettings Settings { get; }

Expand All @@ -74,6 +76,7 @@ public void Dispose()
}

private readonly SemaphoreSlim _initializeSemaphore = new(1);
private readonly IEdmMetadataCacheFactory _metadataCacheFactory;
public async Task Initialize(CancellationToken cancellationToken)
{
// Just allow one schema request at a time, unlikely to be much contention but avoids multiple requests for same endpoint.
Expand Down Expand Up @@ -108,8 +111,9 @@ public void ClearMetadataCache()
var metadataCache = MetadataCache;
if (metadataCache != null)
{
EdmMetadataCache.Clear(metadataCache.Key);
_metadataCacheFactory.Clear(metadataCache.Key);
MetadataCache = null;
_adapter = null;
}
}

Expand Down Expand Up @@ -157,16 +161,16 @@ private async Task<HttpResponseMessage> SendMetadataRequestAsync(CancellationTok
return await new RequestRunner(this).ExecuteRequestAsync(request, cancellationToken).ConfigureAwait(false);
}

private EdmMetadataCache InitializeStaticMetadata(string metadata)
private IEdmMetadataCache InitializeStaticMetadata(string metadata)
{
return EdmMetadataCache.GetOrAdd(
return _metadataCacheFactory.GetOrAdd(
Settings.BaseUri.AbsoluteUri,
uri => CreateMdc(uri, metadata));
}

private async Task<EdmMetadataCache> InitializeMetadataCache(CancellationToken cancellationToken)
private async Task<IEdmMetadataCache> InitializeMetadataCache(CancellationToken cancellationToken)
{
return await EdmMetadataCache.GetOrAddAsync(
return await _metadataCacheFactory.GetOrAddAsync(
Settings.BaseUri.AbsoluteUri,
async uri =>
{
Expand All @@ -188,7 +192,7 @@ private async Task<string> ResolveMetadataAsync(CancellationToken cancellationTo
return metadataDocument;
}

private EdmMetadataCache CreateMdc(string key, string metadata)
private IEdmMetadataCache CreateMdc(string key, string metadata)
{
return new EdmMetadataCache(key, metadata, TypeCaches.TypeCache(key, Settings.NameMatchResolver));
}
Expand Down
19 changes: 19 additions & 0 deletions src/Simple.OData.Client.IntegrationTests/MetadataODataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,23 @@ await Task.WhenAll(

Assert.Equal(1, metadataCallsCount);
}

[Fact]
public async Task MetadataClear()
{
ODataClient.ClearMetadataCache();
var metadataCallsCount = 0;
var settings = new ODataClientSettings
{
BaseUri = _serviceUri,
BeforeRequest = _ => metadataCallsCount++
};
var client = new ODataClient(settings);

await client.GetMetadataAsStringAsync();
client.ClearBaseUrlMetadataCache();
await client.GetMetadataAsStringAsync();

Assert.Equal(2, metadataCallsCount);
}
}
Loading