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(); } }