WIP Fix for EF with accessing disposed context

This commit is contained in:
Steven Hildreth 2018-11-10 12:03:54 -06:00
parent 41c0cb9007
commit 97483dee11
15 changed files with 104 additions and 98 deletions

View file

@ -1,18 +1,13 @@
using Mapster;
using Microsoft.AspNet.OData;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Roadie.Api.Services;
using Roadie.Library.Caching;
using Roadie.Library.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using models = Roadie.Library.Models;
namespace Roadie.Api.Controllers
{
@ -22,19 +17,13 @@ namespace Roadie.Api.Controllers
[Authorize]
public class ArtistController : EntityControllerBase
{
private readonly IArtistService _artistService;
private IArtistService ArtistService
{
get
{
return this._artistService;
}
}
private IArtistService ArtistService { get; }
public ArtistController(IArtistService artistService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration)
: base(cacheManager, configuration)
{
this._logger = logger.CreateLogger("RoadieApi.Controllers.ArtistController");
this._artistService = artistService;
this.ArtistService = artistService;
}
//[EnableQuery]
@ -46,18 +35,17 @@ namespace Roadie.Api.Controllers
[HttpGet("{id}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<IActionResult> Get(Guid id, string inc = null)
public async Task<ActionResult<Artist>> Get(Guid id, string inc = null)
{
var key = id.ToString();
var result = await this._cacheManager.GetAsync<Artist>(key, async () =>
{
var op = await this.ArtistService.ArtistById(null, id, (inc ?? "stats,imaes,associatedartists,collections,playlists,contributions,labels").ToLower().Split(","));
return op.Data;
}, key);
var result = await this.ArtistService.ArtistById(null, id, (inc ?? Artist.DefaultIncludes).ToLower().Split(","));
if (result == null)
{
return NotFound();
}
if (!result.IsSuccess)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
}
return Ok(result);
}
}

View file

@ -8,7 +8,7 @@ using Roadie.Library.Data;
namespace Roadie.Api.Controllers
{
public abstract class EntityControllerBase : ODataController
public abstract class EntityControllerBase : ODataController
{
protected readonly ICacheManager _cacheManager;
protected readonly IConfiguration _configuration;
@ -16,13 +16,7 @@ namespace Roadie.Api.Controllers
protected ILogger _logger;
protected IRoadieSettings RoadieSettings
{
get
{
return this._roadieSettings;
}
}
protected IRoadieSettings RoadieSettings => this._roadieSettings;
public EntityControllerBase(ICacheManager cacheManager, IConfiguration configuration)
{

View file

@ -1,6 +1,7 @@
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Library;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
@ -47,21 +48,29 @@ namespace Roadie.Api.Services
throw new NotImplementedException();
}
public async Task<OperationResult<Artist>> ArtistById(User roadieUser, Guid id, IEnumerable<string> includes)
{
var sw = Stopwatch.StartNew();
sw.Start();
var cacheKey = string.Format("urn:artist_by_id_operation:{0}:{1}", id, includes == null ? "0" : string.Join("|", includes));
var result = await this.CacheManager.GetAsync<OperationResult<Artist>>(cacheKey, async () => {
return await this.ArtistByIdAction(roadieUser, id, includes);
}, data.Artist.CacheRegionKey(id));
sw.Stop();
return new OperationResult<Artist>(result.Messages)
{
Data = result.Data,
Errors = result.Errors,
IsSuccess = result != null,
OperationTime = sw.ElapsedMilliseconds
};
}
private async Task<OperationResult<Artist>> ArtistByIdAction(User roadieUser, Guid id, IEnumerable<string> includes)
{
roadieUser = roadieUser ?? new User();
var sw = Stopwatch.StartNew();
sw.Start();
var cacheRegion = (new data.Artist { RoadieId = id }).CacheRegion;
var cacheKey = string.Format("urn:artist_result_model:{0}:{1}", id, includes == null ? "0" : string.Join("|", includes));
var resultInCache = this._cacheManager.Get<OperationResult<Artist>>(cacheKey, cacheRegion);
if (resultInCache != null)
{
sw.Stop();
resultInCache.OperationTime = sw.ElapsedMilliseconds;
return resultInCache;
}
var artist = this.DbContext.Artists
.Include(x => x.Genres)
@ -284,14 +293,12 @@ namespace Roadie.Api.Services
}
}
sw.Stop();
resultInCache = new OperationResult<Artist>
return new OperationResult<Artist>
{
Data = result,
IsSuccess = result != null,
OperationTime = sw.ElapsedMilliseconds
};
this._cacheManager.Add(cacheKey, resultInCache, cacheRegion);
return resultInCache;
}
public async Task<OperationResult<Artist>> ArtistByName(string name, IEnumerable<string> includes)

View file

@ -129,14 +129,16 @@ namespace Roadie.Api
// return settings;
//});
var cacheManager = new MemoryCacheManager(this._loggerFactory.CreateLogger<MemoryCacheManager>(), new CachePolicy(TimeSpan.FromHours(1)));
var cacheManager = new MemoryCacheManager(this._loggerFactory.CreateLogger<MemoryCacheManager>(), new CachePolicy(TimeSpan.FromHours(4)));
services.AddSingleton<ICacheManager>(cacheManager);
services.AddEntityFrameworkMySql().AddDbContext<ApplicationUserDbContext>(options =>
options.UseMySql(this._configuration.GetConnectionString("RoadieDatabaseConnection")));
services.AddDbContextPool<ApplicationUserDbContext>(
options => options.UseMySql(this._configuration.GetConnectionString("RoadieDatabaseConnection")
));
services.AddEntityFrameworkMySql().AddDbContext<IRoadieDbContext, RoadieDbContext>(options =>
options.UseMySql(this._configuration.GetConnectionString("RoadieDatabaseConnection")));
services.AddDbContextPool<IRoadieDbContext, RoadieDbContext>(
options => options.UseMySql(this._configuration.GetConnectionString("RoadieDatabaseConnection")
));
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationUserDbContext>()
@ -164,7 +166,7 @@ namespace Roadie.Api
services.AddScoped<IPlaylistService, PlaylistService>();
services.AddScoped<IArtistService, ArtistService>();
var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(this._configuration["Tokens:PrivateKey"]));
var securityKey = new SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(this._configuration["Tokens:PrivateKey"]));
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
@ -204,7 +206,7 @@ namespace Roadie.Api
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddHttpContextAccessor();
services.AddScoped<IHttpContext>(factory =>
{
var actionContext = factory.GetService<IActionContextAccessor>()

View file

@ -34,8 +34,6 @@ namespace Roadie.Library.Caching
public abstract void ClearRegion(string region);
public abstract void Dispose();
public abstract bool Exists<TOut>(string key);
public abstract bool Exists<TOut>(string key, string region);

View file

@ -3,7 +3,7 @@ using System.Threading.Tasks;
namespace Roadie.Library.Caching
{
public interface ICacheManager : IDisposable
public interface ICacheManager
{
bool Add<TCacheValue>(string key, TCacheValue value);

View file

@ -1,6 +1,7 @@
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Roadie.Library.Caching
@ -17,32 +18,23 @@ namespace Roadie.Library.Caching
public override bool Add<TCacheValue>(string key, TCacheValue value)
{
using (var entry = _cache.CreateEntry(key))
{
_cache.Set(key, value);
return true;
}
_cache.Set(key, value);
return true;
}
public override bool Add<TCacheValue>(string key, TCacheValue value, string region)
{
using (var entry = _cache.CreateEntry(key))
{
_cache.Set(key, value, DateTimeOffset.MaxValue);
return true;
}
_cache.Set(key, value, this._defaultPolicy.ExpiresAfter);
return true;
}
public override bool Add<TCacheValue>(string key, TCacheValue value, CachePolicy policy)
{
using (var entry = _cache.CreateEntry(key))
_cache.Set(key, value, new MemoryCacheEntryOptions
{
_cache.Set(key, value, new MemoryCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.UtcNow.Add(policy.ExpiresAfter) // new DateTimeOffset(DateTime.UtcNow, policy.ExpiresAfter)
});
return true;
}
AbsoluteExpiration = DateTimeOffset.UtcNow.Add(policy.ExpiresAfter) // new DateTimeOffset(DateTime.UtcNow, policy.ExpiresAfter)
});
return true;
}
public override bool Add<TCacheValue>(string key, TCacheValue value, string region, CachePolicy policy)
@ -60,11 +52,6 @@ namespace Roadie.Library.Caching
this.Clear();
}
public override void Dispose()
{
// throw new NotImplementedException();
}
public override bool Exists<TOut>(string key)
{
return this.Get<TOut>(key) != null;
@ -119,6 +106,11 @@ namespace Roadie.Library.Caching
{
r = await getItem();
this.Add(key, r, region);
Trace.WriteLine($"-+> Cache Miss for Key [{ key }], Region [{ region }]");
}
else
{
Trace.WriteLine($"-!> Cache Hit for Key [{ key }], Region [{ region }]");
}
return r;
}

View file

@ -83,13 +83,6 @@ namespace Roadie.Library.Caching
this.Clear();
}
// Dispose() calls Dispose(true)
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public override bool Exists<TOut>(string key)
{
return this.Exists<TOut>(key, null);

View file

@ -16,6 +16,7 @@ namespace Roadie.Library.Data
[Column("artistType", TypeName = "enum")]
public string ArtistType { get; set; }
[NotMapped]
public virtual ICollection<ArtistAssociation> AssociatedArtists { get; set; }
[Column("bandStatus", TypeName = "enum")]

View file

@ -7,11 +7,16 @@ namespace Roadie.Library.Data
{
public partial class Artist
{
public static string CacheRegionKey(Guid Id)
{
return string.Format("urn:artist:{0}", Id);
}
public string CacheRegion
{
get
{
return string.Format("urn:artist:{0}", this.RoadieId);
return Artist.CacheRegionKey(this.RoadieId);
}
}

View file

@ -5,11 +5,16 @@ namespace Roadie.Library.Data
{
public partial class Label
{
public static string CacheRegionKey(Guid Id)
{
return string.Format("urn:label:{0}", Id);
}
public string CacheRegion
{
get
{
return string.Format("urn:label:{0}", this.RoadieId);
return Label.CacheRegionKey(this.RoadieId);
}
}

View file

@ -8,11 +8,16 @@ namespace Roadie.Library.Data
{
public partial class Release
{
public static string CacheRegionKey(Guid Id)
{
return string.Format("urn:release:{0}", Id);
}
public string CacheRegion
{
get
{
return string.Format("urn:release:{0}", this.RoadieId);
return Release.CacheRegionKey(this.RoadieId);
}
}

View file

@ -10,11 +10,16 @@ namespace Roadie.Library.Data
{
public partial class Track
{
public static string CacheRegionKey(Guid Id)
{
return string.Format("urn:track:{0}", Id);
}
public string CacheRegion
{
get
{
return string.Format("urn:track:{0}", this.RoadieId);
return Track.CacheRegionKey(this.RoadieId);
}
}

View file

@ -14,6 +14,8 @@ namespace Roadie.Library.Models
[Serializable]
public class Artist : EntityModelBase
{
public const string DefaultIncludes = "stats,imaes,associatedartists,collections,playlists,contributions,labels";
public IEnumerable<ReleaseList> ArtistContributionReleases;
public IEnumerable<LabelList> ArtistLabels;

View file

@ -12,13 +12,13 @@ namespace Roadie.Library
public const string NotModified = "NotModified";
public const string OkMessage = "OK";
private List<string> _messages = new List<string>();
private List<Exception> _errors = new List<Exception>();
private List<string> _messages = new List<string>();
public Dictionary<string, object> AdditionalData { get; set; }
public T Data { get; set; }
public IEnumerable<Exception> Errors { get; set; }
public bool IsSuccess { get; set; }
public IEnumerable<string> Messages
{
get
@ -26,12 +26,22 @@ namespace Roadie.Library
return this._messages;
}
}
public long OperationTime { get; set; }
public OperationResult()
{
}
public OperationResult(IEnumerable<string> messages = null)
{
if (messages != null && messages.Any())
{
this.AdditionalData = new Dictionary<string, object>();
messages.ToList().ForEach(x => this.AddMessage(x));
}
}
public OperationResult(string message = null)
{
this.AdditionalData = new Dictionary<string, object>();
@ -49,21 +59,20 @@ namespace Roadie.Library
this.AddError(error);
}
public void AddMessage(string message)
{
if(!string.IsNullOrEmpty(message))
{
this._messages.Add(message);
}
}
public void AddError(Exception exception)
{
if(exception != null)
if (exception != null)
{
this._errors.Add(exception);
}
}
public void AddMessage(string message)
{
if (!string.IsNullOrEmpty(message))
{
this._messages.Add(message);
}
}
}
}