namespace Framework.DataAccessLayer.DatabaseMemory
{
using Framework.Server;
using Microsoft.EntityFrameworkCore.Query.Internal;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
internal interface IListRow : IList
{
DatabaseMemoryInternal DatabaseMemory { get; }
}
///
/// Row list with reference to memory object.
///
internal class ListRow : List, IListRow
{
public ListRow(DatabaseMemoryInternal databaseMemory)
{
this.DatabaseMemory = databaseMemory;
}
public DatabaseMemoryInternal DatabaseMemory { get; }
}
///
/// Database memory stores row objects in memory.
///
internal class DatabaseMemoryInternal
{
///
/// (TypeRow, List). Memory row store.
///
private readonly ConcurrentDictionary rowList = new ConcurrentDictionary();
///
/// Returns list of stored memory rows of typeRow.
///
public IList RowListGet(Type typeRow)
{
IList result = rowList.GetOrAdd(typeRow, (Type typeRowLocal) => {
var typeRowList = typeof(ListRow<>).MakeGenericType(typeRowLocal);
var resultLocal = (IList)Activator.CreateInstance(typeRowList, this);
return (IListRow)resultLocal; // resultLocal;
});
return result;
}
///
/// Overload.
///
public List RowListGet()
{
return (List)RowListGet(typeof(T));
}
///
/// Returns singleton scope memory instance.
///
public static DatabaseMemoryInternal Instance
{
get
{
return (DatabaseMemoryInternal)UtilServer.Context.RequestServices.GetService(typeof(DatabaseMemoryInternal)); // See also method ConfigureServices();
}
}
///
/// Determines based on the query origin where to write back data (to database or memory).
///
public static DatabaseEnum DatabaseEnum(IQueryable query)
{
DatabaseEnum result = Framework.DataAccessLayer.DatabaseEnum.None;
if (query != null)
{
var expressionVisitorScope = new ExpressionVisitorScope();
expressionVisitorScope.Visit(query.Expression);
if (expressionVisitorScope.DatabaseEnumList.Count == 1)
{
return expressionVisitorScope.DatabaseEnumList.Single();
}
else
{
return DataAccessLayer.DatabaseEnum.Custom;
}
}
return result;
}
}
///
/// Parse query to find out if it was built with database or memory store or combination.
///
internal class ExpressionVisitorScope : ExpressionVisitor
{
protected override Expression VisitConstant(ConstantExpression node)
{
// Database
//
// This combination is not possible (anymore) for async queries. Also: EntityQueryable<> is an internal API
// if (node.Type.IsGenericType && node.Type.GetGenericTypeDefinition() == typeof(EntityQueryable<>)) // For example: Query.Where(item => item.Id == 3);
// {
// DatabaseEnumList.Add(DatabaseEnum.Database);
// }
// MemorySingleton
if (node.Value?.GetType().BaseType == typeof(EnumerableQuery))
{
var enumerable = node.Value.GetType().GetField("_enumerable", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(node.Value);
if (enumerable is IListRow)
{
DatabaseMemoryInternal databaseMemory = ((IListRow)enumerable).DatabaseMemory;
if (databaseMemory == DatabaseMemoryInternal.Instance)
{
DatabaseEnumList.Add(DatabaseEnum.Memory);
}
}
}
return base.VisitConstant(node);
}
///
/// Gets DatabaseEnumList. Detected scope origins by ExpressionVisitor.
///
public HashSet DatabaseEnumList = new HashSet();
}
}