private IReadOnlyList <object> Load(Type type, string sql, params object[] values) { List <object> entities = new List <object>(); using (var sqlConnection = GetConnection()) { using (var sqlCommand = new DbSqlCommand(sql)) { sqlCommand.Connection = sqlConnection.SqlConnection; QueryHelpers.AddSqlParameters(sqlCommand, values); sqlConnection.Open(); using (SqlDataReader reader = sqlCommand.ExecuteReader(CommandBehavior.SequentialAccess)) { var fillVisitor = new FillVisitor( reader: reader, db: this, objectFillerFactory: new ObjectFillerFactory()); while (fillVisitor.Read()) { object entity = QueryHelpers.Fill(type, entity: null, fillVisitor: fillVisitor); entities.Add(entity); } } } } var entitiesToReturn = new List <object>(entities.Count); var configuration = GlobalDbConfiguration.GetConfigurationOrEmpty(type); var entityFilter = configuration.EntityFilter; var queryLogger = GlobalDbConfiguration.QueryLogger; foreach (object entity in entities) { queryLogger.IncrementLoadedElementCount(increment: 1); if (entityFilter.DoReturnEntity(Settings, entity)) { entitiesToReturn.Add(entity); } } OnEntitiesLoaded(entitiesToReturn .Where(e => e is DbEntity) .Select(e => e as DbEntity) .ToList()); return(entitiesToReturn); }
private void EnsureElementsAreLoaded() { if (cachedElements != null) { return; } lock (cachedElementsLock) { if (cachedElements != null) { return; } var cachedTypedElements = new List <TElement>(); List <(TElement, List <object>)> loadedData = SqlCommands.ExecuteSqlCommand(() => { using (var sqlConnection = Db.GetConnection()) { using (var sqlCommand = GetSqlCommand()) { QueryHelpers.AddSqlParameters(sqlCommand, parameters); // AppContext.StartRequestTiming(sql, "OpenConnection"); sqlConnection.Open(); // AppContext.StopRequestTiming(sql, "OpenConnection"); sqlCommand.Connection = sqlConnection.SqlConnection; // sqlCommand.Transaction = sqlConnection.SqlConnection.BeginTransaction(Db.Settings().IsolationLevel); if (sqlCommand.CommandType == CommandType.StoredProcedure) { SqlParameter returnValue = SqlParameterUtils.Create("@ReturnValue", TypeToDbType <TElement>()); returnValue.Direction = ParameterDirection.ReturnValue; sqlCommand.Parameters.Add(returnValue); sqlCommand.ExecuteNonQuery(); TElement entity = (TElement)sqlCommand.Parameters["@ReturnValue"].Value; cachedTypedElements.Add(entity); sqlCommand.Parameters.Clear(); return(new List <(TElement, List <object>)>()); } else { // AppContext.StartRequestTiming(sql, "ExecuteReader"); var loadedDataFromReader = new List <(TElement, List <object>)>(); using (SqlDataReader reader = sqlCommand.ExecuteReader(CommandBehavior.SequentialAccess)) { // AppContext.StopRequestTiming(sql, "ExecuteReader"); // AppContext.StartRequestTiming(sql, "FillVisitor"); var fillVisitor = new FillVisitor( reader: reader, db: Db, objectFillerFactory: new ObjectFillerFactory()); // fillVisitor.SQL = sql; // AppContext.StopRequestTiming(sql, "FillVisitor"); // AppContext.StartRequestTiming(sql, "Read"); int k = 0; while (fillVisitor.Read()) { // AppContext.StopRequestTiming(sql, "Read"); // AppContext.StartRequestTiming(sql, "Fill", k > 0); TElement entity = QueryHelpers.Fill(default(TElement), fillVisitor); var dbEntity = entity as DbEntity; if (dbEntity != null) { ((IDbEntityInternal)dbEntity).SetAllowSettingColumns(false); } if (entity is IId) { entity = (TElement)Db.LoadedEntityCache.GetOrAdd(typeof(TElement), "Id", ((IId)entity).Id, entity); } // AppContext.StopRequestTiming(sql, "Fill"); // First fetch all entities from the db and then return them // Otherwise we have problems, if someone calls AnyDb() first (AnyDb() just requests the first entity) // Anyway we improve performance if someone calls the query two times: The second time, we don't query the // database but return the cached elements List <object> subEntities = new List <object>(); if (fillVisitor.HasNext && !fillVisitor.IsDBNull()) { subEntities.OfType <IDbEntityInternal>().ForEach(e => e.SetAllowSettingColumns(true)); // AppContext.StopRequestTiming(sql, "subEntities"); } k++; loadedDataFromReader.Add((entity, subEntities)); // CachedElements.Add(entity); } // AppContext.StopRequestTiming(sql, "Read"); } sqlCommand.Parameters.Clear(); return(loadedDataFromReader); } } } }); // Connect the items to one another (i.e. if Person.Customer was called, set Person.Customer to the recently loaded entity) // First emit EmitValueLoadedBeforeRightsCheck(dbEntity); for all entities before checking the rights, because if only do it within the while loop below just before Db.ReturnEntity for each dbEntity, // in GetOwnerMandatorId() of i.e. User (which is called from Db.ReturnEntity()), if we call Person.Customer.MandatorId, Customer is loaded from the DB, because Person.Customer was not set yet loadedData.Select(e => e.Item1).OfType <DbEntity>().ForEach(e => EmitValueLoadedBeforeRightsCheck(e)); // Do this after the sql connection above has been closed // Otherwise we get this exception: // System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Transactions.TransactionAbortedException: The transaction has aborted. ---> System.Transactions.TransactionPromotionException: Failure while attempting to promote transaction. ---> System.Data.SqlClient.SqlException: There is already an open DataReader associated with this Command which must var entityFilter = GlobalDbConfiguration.GetConfigurationOrEmpty(typeof(TElement)).EntityFilter; for (int i = 0; i < loadedData.Count; i++) { var entity = loadedData[i].Item1; var joinedEntities = loadedData[i].Item2; GlobalDbConfiguration.QueryLogger.IncrementLoadedElementCount(increment: 1); // Count the joined entities too GlobalDbConfiguration.QueryLogger.IncrementLoadedElementCount(increment: joinedEntities?.Count ?? 0); var dbEntity = entity as DbEntity; if (dbEntity != null) { ((IDbEntityInternal)dbEntity).SetAllowSettingColumns(allowSettingColumns: true); } if (entityFilter.DoReturnEntity(Db.Settings, entity, joinedEntities)) { EmitValueLoaded(dbEntity); cachedTypedElements.Add(entity); } else { EmitValueRemoved(dbEntity); } } cachedElements = cachedTypedElements.AsReadOnly(); } }