Этот баннер — требование Роскомнадзора для исполнения 152 ФЗ.
«На сайте осуществляется обработка файлов cookie, необходимых для работы сайта, а также для анализа использования сайта и улучшения предоставляемых сервисов с использованием метрической программы Яндекс.Метрика. Продолжая использовать сайт, вы даёте согласие с использованием данных технологий».
Политика конфиденциальности
|
|
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
Я вот тут недавно спрашивал: http://www.gotdotnet.ru/Forums/Web/395783.aspx И навеяло: Есть единственный generic класс, который способен обработать все запросы к базе данных, и вернуть любой результат. Вот как он устроен: Принимает 3 типа: тип A -- параметры, и B – результат, и C. С Data Adapter -- дает возможность работать с разными базами данных. Любой A наследуется от ABase. Все поля в любом классе A помечены атрибутами, которые представляют параметры процедур в базе, и сам класс помечен атрибутами, которые представляют имя процедур (для создания, чтения, обновления...). В классе ABase есть 2 метода, которые наследуется всеми A. Первый из них возвращает имя процедуры, а второй передает значения: имя параметра + значение параметра для процедуры... Аналогично B и BBase… B имеет поля, которые заполняются методом, наследованным из BBase. Сам B помечен атрибутами, которые дают представление о том, каким образом следует его заполнять значениями. Что это дает? Для того, чтобы получить из базы новый объект, или List объектов, достаточно вот что сделать: 1. Объявить класс входных параметров, пометив его и поля атрибутами, соотв. параметрам процедур. 2. Объявить класс результата. Все! Помоему это самое лучшее решение, которое полностью избавит от мучений со строками соединений, вызовом процедур, и вообще поможет о них частично забыть. + останется возможность расширения.. Может кто лучше придумает, или уже у кого есть идеи.. Если нет, то буду использовать это решение. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.11.2006, 19:56 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
Idea horoshoia no prover chto ne izobretaesh velosiped mne kajetsa ja uje videl pohojee reshenie v knige esli naidu prishlu ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.11.2006, 20:16 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
Класс! Вытянул я, значится, из базы "ЧТО-ТО", амоя программка сделала с этим ЧЕМ-ТО ЧЕГО-ТО. Ну и результат получился КАКОЙ-ТО... А может к врачу сходить? А то уровень абстрагирования от абстракций что-то зашкалило... How can men die better than facing fearful odds, For the ashes of their fathers and the temples of their gods? | Мой Brainbench | BookReader 1.1 | Wallpaper Cycler | ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.11.2006, 20:28 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
Вот есть собственна наработка в этом плане- на вход передаем имя ХП или сам запрос,передаем параметры,передаем ссылку на делегат который будет что-то делать (например читать IDataReader) поскольку используются интерфейсы то нет привязки к конкретному классу (Главное чтобы он реализовал интерфейс IDbConnection). Код: public delegate T DataRetriever<T>(IDataReader reader); public class DbParameters { CommandType commandType; String commandText; String connectionString; IDictionary<String, Object> commandParameters; public DbParameters(CommandType commandType, String commandText, String connectionString) { this.commandType = commandType; this.commandText = commandText; this.connectionString = connectionString; } public DbParameters(CommandType commandType, String commandText, String connectionString, IDictionary<String, Object> commandParameters) : this(commandType, commandText, connectionString) { this.commandParameters = commandParameters; } public void AddParameterWithValue(String parameterName, Object value) { if (String.IsNullOrEmpty(parameterName)) throw new ArgumentNullException("parameterName"); if (commandParameters == null) commandParameters = new Dictionary<String, Object>(); commandParameters.Add(parameterName, value); } public IDictionary<String, Object> CommandParameters { get { return commandParameters; } } public CommandType CommandType { get { return commandType; } } public String CommandText { get { return commandText; } } public String ConnectionString { get { return connectionString; } } } public class DataMapper<T> { DataRetriever<T> method; DbParameters dbParameters; public DataMapper(DbParameters dbParameters) { this.dbParameters = dbParameters; } public DataMapper(DbParameters dbParameters, DataRetriever<T> method) : this(dbParameters) { this.method = method; } public DataRetriever<T> RetrieveMethod { get { return method; } set { method = value; } } public DbParameters DbParameters { get { return dbParameters; } } } public class DAL<T, U> where T : IDbConnection, new() { public static U ExecuteReader(DataMapper<U> dataMapper, bool useTransaction) { if (dataMapper == null) throw new ArgumentNullException("dataMapper"); if (String.IsNullOrEmpty(dataMapper.DbParameters.ConnectionString)) throw new ArgumentNullException("dataMapper.ConnectionString"); using (T connection = new T()) { connection.ConnectionString = dataMapper.DbParameters.ConnectionString; using (IDbCommand command = connection.CreateCommand()) { command.CommandType = dataMapper.DbParameters.CommandType; command.CommandText = dataMapper.DbParameters.CommandText; if (dataMapper.DbParameters.CommandParameters != null) AddParameters(command, dataMapper.DbParameters.CommandParameters); IDataReader reader = null; U data; connection.Open(); if (!useTransaction) { try { reader = command.ExecuteReader(CommandBehavior.CloseConnection); data = dataMapper.RetrieveMethod.Invoke(reader); } catch (Exception) { throw; } finally { if (reader != null) reader.Close(); } } else { using (IDbTransaction transaction = connection.BeginTransaction()) { command.Transaction = transaction; try { reader = command.ExecuteReader(CommandBehavior.Default); data = dataMapper.RetrieveMethod.Invoke(reader); } catch (Exception) { transaction.Rollback(); throw; } finally { if (reader != null) reader.Close(); } transaction.Commit(); } } return data; } } } private static void AddParameters(IDbCommand command, IDictionary<String, Object> commandParameters) { foreach (String name in commandParameters.Keys) { IDataParameter parameter = command.CreateParameter(); parameter.ParameterName = name; parameter.Value = commandParameters[name]; command.Parameters.Add(parameter); } } } Как исползовать: DbParameters dbp = new DbParameters(CommandType.StoredProcedure, "[dbo].[GetCustomers]", connectionString); dbp.AddParameterWithValue("@data", "09.09.2006"); DataMapper<IList<Customer>> dm = new DataMapper<IList<Customer>>(dbp); dm.RetrieveMethod = delegate(IDataReader reader) { reader.Read(); IList<Customer> customers= new List<Customer>(reader.GetInt32(0)); //max size reader.NextResult(); while (reader.Read()) customers.Add(new Customer(reader.GetString(0),reader.GetString(1),reader.GetString(2)); return customers; }; IList<Customer> customers= DAL<SqlConnection, IList<Customer>>.ExecuteReader(dm, false); ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.11.2006, 20:34 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
jenia: Idea horoshoia no prover chto ne izobretaesh velosiped mne kajetsa ja uje videl pohojee reshenie v knige esli naidu prishlu Если не трудно, то дайте, пожалуйста название книги, или ссылку. Может действительно велосипед изобретаю.. Спасибо. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.11.2006, 20:34 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
someone1: Вот есть собственна наработка в этом плане- на вход передаем имя ХП или сам запрос,передаем параметры,передаем ссылку на делегат который будет что-то делать (например читать IDataReader) поскольку используются интерфейсы то нет привязки к конкретному классу (Главное чтобы он реализовал интерфейс IDbConnection). Код: public delegate T DataRetriever<T>(IDataReader reader); public class DbParameters { CommandType commandType; String commandText; String connectionString; IDictionary<String, Object> commandParameters; public DbParameters(CommandType commandType, String commandText, String connectionString) { this.commandType = commandType; this.commandText = commandText; this.connectionString = connectionString; } public DbParameters(CommandType commandType, String commandText, String connectionString, IDictionary<String, Object> commandParameters) : this(commandType, commandText, connectionString) { this.commandParameters = commandParameters; } public void AddParameterWithValue(String parameterName, Object value) { if (String.IsNullOrEmpty(parameterName)) throw new ArgumentNullException("parameterName"); if (commandParameters == null) commandParameters = new Dictionary<String, Object>(); commandParameters.Add(parameterName, value); } public IDictionary<String, Object> CommandParameters { get { return commandParameters; } } public CommandType CommandType { get { return commandType; } } public String CommandText { get { return commandText; } } public String ConnectionString { get { return connectionString; } } } public class DataMapper<T> { DataRetriever<T> method; DbParameters dbParameters; public DataMapper(DbParameters dbParameters) { this.dbParameters = dbParameters; } public DataMapper(DbParameters dbParameters, DataRetriever<T> method) : this(dbParameters) { this.method = method; } public DataRetriever<T> RetrieveMethod { get { return method; } set { method = value; } } public DbParameters DbParameters { get { return dbParameters; } } } public class DAL<T, U> where T : IDbConnection, new() { public static U ExecuteReader(DataMapper<U> dataMapper, bool useTransaction) { if (dataMapper == null) throw new ArgumentNullException("dataMapper"); if (String.IsNullOrEmpty(dataMapper.DbParameters.ConnectionString)) throw new ArgumentNullException("dataMapper.ConnectionString"); using (T connection = new T()) { connection.ConnectionString = dataMapper.DbParameters.ConnectionString; using (IDbCommand command = connection.CreateCommand()) { command.CommandType = dataMapper.DbParameters.CommandType; command.CommandText = dataMapper.DbParameters.CommandText; if (dataMapper.DbParameters.CommandParameters != null) AddParameters(command, dataMapper.DbParameters.CommandParameters); IDataReader reader = null; U data; connection.Open(); if (!useTransaction) { try { reader = command.ExecuteReader(CommandBehavior.CloseConnection); data = dataMapper.RetrieveMethod.Invoke(reader); } catch (Exception) { throw; } finally { if (reader != null) reader.Close(); } } else { using (IDbTransaction transaction = connection.BeginTransaction()) { command.Transaction = transaction; try { reader = command.ExecuteReader(CommandBehavior.Default); data = dataMapper.RetrieveMethod.Invoke(reader); } catch (Exception) { transaction.Rollback(); throw; } finally { if (reader != null) reader.Close(); } transaction.Commit(); } } return data; } } } private static void AddParameters(IDbCommand command, IDictionary<String, Object> commandParameters) { foreach (String name in commandParameters.Keys) { IDataParameter parameter = command.CreateParameter(); parameter.ParameterName = name; parameter.Value = commandParameters[name]; command.Parameters.Add(parameter); } } } Как исползовать: DbParameters dbp = new DbParameters(CommandType.StoredProcedure, "[dbo].[GetCustomers]", connectionString); dbp.AddParameterWithValue("@data", "09.09.2006"); DataMapper<IList<Customer>> dm = new DataMapper<IList<Customer>>(dbp); dm.RetrieveMethod = delegate(IDataReader reader) { reader.Read(); IList<Customer> customers= new List<Customer>(reader.GetInt32(0)); //max size reader.NextResult(); while (reader.Read()) customers.Add(new Customer(reader.GetString(0),reader.GetString(1),reader.GetString(2)); return customers; }; IList<Customer> customers= DAL<SqlConnection, IList<Customer>>.ExecuteReader(dm, false); Мне понравилась ваша идея, однако почему больше не абстрагировать... Зачем каждый раз указывать названия параметров и имя процедуры + писать безымянный метод каждый раз. dm.RetrieveMethod = delegate(IDataReader reader) { reader.Read(); IList<Customer> customers= new List<Customer>(reader.GetInt32(0)); //max size reader.NextResult(); while (reader.Read()) customers.Add(new Customer(reader.GetString(0),reader.GetString(1),reader.GetString(2)); return customers; }; Как вам на счет той идеи, что я предложил. Постараюсь сегодня выложить код. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.11.2006, 20:41 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
Шеф, видимо я не такой опытный программист как Вы... (сори) Я всегда думал, что меня абстрагирует от различных БД правильно спроектированный DAL(DataAccessLayer) и что то подсказывает, что объект с DataAdapter (дописываю сам ибо, Вы видимо поленились) с разными объектами Connection не самое гибкое решение... С. Вот. Хотя бы потому, что я уже подсел на адаптеры(абстрагирующие меня от БД), а следовательно диктующие мне логику дальнейшей работы с таблицами... Читайте Фаулера, уверен именно там лежит ответ на Ваш порыв.. С ув. Сергей. История - это огромная система раннего предупреждения. Norman Cousins ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.11.2006, 20:45 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
можно коненчо было и дальше абстргироваться до чего-то типа фабрик объектов в которых уже зашиты параметры, строки тип соединения, а необходимые параметры передаются в сами фабрики но просто не хотелось это делать т.к пришлось бы писать под кадждый объект свою реализацию фабрики. Очень интересно было бы взглянуть на вашу реализацию. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.11.2006, 21:10 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
Слушайте, а объясните мне великий смысл написания DAL на делегатах с дженериками? Cheers Pete ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 14.11.2006, 16:42 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
Вобщем есть пока только оччень сырой код. НО идею можно уловить.. 1. Класс, от которого порождаются все классы, которые инкапсулируею информацию вызова процедуры: public class SourceBase : ISource { private event AddParametersDelegate parameterChangeEvent; public event AddParametersDelegate ParameterChangeEvent { add { if (null == parameterChangeEvent) { parameterChangeEvent += value; } } remove { if (null != parameterChangeEvent) { parameterChangeEvent -= value; } } } public virtual ICommand GetCommand(OperationTypeInfo operationType) { Type thisType = this.GetType(); SourceAttribute sourceAttribute = thisType.GetCustomAttributes(typeof(SourceAttribute), false)[0] as SourceAttribute; string commandText = sourceAttribute.CommandText; CommandType commandType = sourceAttribute.CommandType; ICommand command = new Command(commandText, commandType); return command; } public virtual void AddParameters(OperationTypeInfo operationType) { Type thisType = this.GetType(); PropertyInfo[] propertyInfoArray = thisType.GetProperties(); foreach (PropertyInfo propertyInfo in propertyInfoArray) { ParameterAttribute paramAttribute = (propertyInfo.GetCustomAttributes(typeof(ParameterAttribute), false)[0]) as ParameterAttribute; if (paramAttribute.OperationType == operationType) { object propertyValue = propertyInfo.GetValue(this, null); string propertyName = propertyInfo.Name; if (string.Empty != paramAttribute.ParamName) propertyName = paramAttribute.ParamName; parameterChangeEvent(propertyName, propertyValue); } } } } 2. Аттрибуты, которыми нужно помечать поля тех классов. По ним узнаем названия процедур и их параметров. [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] public class ParameterAttribute : Attribute { private string paramName; private ParamUsageInfo paramUsage; private OperationTypeInfo operationType; public string ParamName { get { return paramName; } protected set { paramName = value; } } public ParamUsageInfo ParamUsage { get { return paramUsage; } protected set { paramUsage = value; } } public OperationTypeInfo OperationType { get { return operationType; } protected set { operationType = value; } } public ParameterAttribute(OperationTypeInfo operationType) : this(string.Empty, ParamUsageInfo.Input, operationType) { } public ParameterAttribute(string paramName, OperationTypeInfo operationType) : this(paramName, ParamUsageInfo.Input, operationType) { } public ParameterAttribute(string paramName, ParamUsageInfo paramUsage, OperationTypeInfo operationType) { ParamName = paramName; ParamUsage = paramUsage; OperationType = operationType; } } [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public class SourceAttribute : Attribute { private string commandText; private CommandType commandType; public string CommandText { get { return commandText; } protected set { commandText = value; } } public CommandType CommandType { get { return commandType; } protected set { commandType = value; } } public SourceAttribute(string commandText, CommandType commandType) { CommandText = commandText; CommandType = commandType; } } 3. То же самое для результата.. public class TargetBase : ATarget { public TargetBase() :base() { } public override void Init(SetFieldsDelegate setFieldsDelegate) { Type thisType = this.GetType(); PropertyInfo[] propertyInfoArray = thisType.GetProperties(); foreach (PropertyInfo propertyInfo in propertyInfoArray) { FieldAttribute fieldAttribute = (propertyInfo.GetCustomAttributes(typeof(FieldAttribute), false)[0]) as FieldAttribute; string fieldName = fieldAttribute.FieldName; object fieldValue = null; setFieldsDelegate(fieldName, out fieldValue); propertyInfo.SetValue(this, fieldValue, null); } } } [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] public class FieldAttribute : Attribute { private string fieldName; public string FieldName { get { return fieldName; } protected set { fieldName = value; } } public FieldAttribute(string fieldName) { FieldName = fieldName; } } 4. Класс, который делает обьекты Target, из обьектов Source:)) public class ObjectBuilder<TSource, TTarget> : IObjectBuilder<TSource, TTarget> where TSource: ISource where TTarget: new() { IDataServer dataServer; //private const int _FIRST_INDEX = 0; //public int FIRST_INDEX //{ // get // { // return _FIRST_INDEX; // } //} protected IDataServer DataServer { set { dataServer = value; } } public ObjectBuilder(IDataServer dataServer) { DataServer = dataServer; } public List<TTarget> Read(TSource source) { List<TTarget> list = new List<TTarget>(); TTarget target = default(TTarget); ICommand command = source.GetCommand(OperationTypeInfo.Read); if (command.ComType == CommandType.StoredProcedure) { source.ParameterChangeEvent += new AddParametersDelegate(dataServer.AddParameter); source.AddParameters(OperationTypeInfo.Read); IDataReader reader = dataServer.ExecuteReader(command.ComText); target = new TTarget(); if (target is ATarget) { while (reader.Read()) { target = new TTarget(); (target as ATarget).Init(delegate(string fieldName, out object fieldValue) { fieldValue = reader[fieldName]; }); list.Add(target); } } else { throw new System.Exception("ObjectBuilder.Read: the method or operation is not implemented."); } } if (null == list) throw new TargetException("Class ObjectBuilder.Read."); return list; } public bool Create(TSource sourceList) { throw new System.Exception("The method or operation is not implemented."); } public List<TTarget> Update(TSource sourceList) { throw new System.Exception("The method or operation is not implemented."); } public bool Delete(TSource sourceList) { throw new System.Exception("The method or operation is not implemented."); } public void Dispose() { dataServer.Dispose(); } } КАК ИСПОЛЬЗОВАТЬ: Достаточно обьявить два класса наследника SourceBase и TargetBase, пометив поля аттрибутами, соотв имени процедуры, параметрам и полям (см пример.) [SourceAttribute("WEB_GET_TOV_IMG", CommandType.StoredProcedure)] public class ImageSource : SourceBase { private long pictId; [ParameterAttribute("@PICT_ID", OperationTypeInfo.Read)] public long PictId { get { return pictId; } protected set { pictId = value; } } public ImageSource(long pictId) { PictId = pictId; } } public class ImageTarget : TargetBase { private byte[] imageData; private string imageName; [FieldAttribute("ImageData")] public byte[] ImageData { get { return imageData; } protected set { imageData = value; } } [FieldAttribute("ImageName")] public string ImageName { get { return imageName; } protected set { imageName = value; } } } И вызвать так: ImageSource source = new ImageSource(imageId); List<ImageTarget> imageTargetList = ObjectBuilder.Read(source); ImageTarget imageTarget = imageTargetList[FIRST_INDEX]; return imageTarget.ImageData; Теперь критика... ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 16.11.2006, 16:13 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
Выглядит очень интересно. А что по поводу скорости работы - ведь у вас почти везде используюется reflection ? ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 16.11.2006, 16:34 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
А вот это меня тоже беспокоит.. А что делать? посмотрите здесь статейка: http://www.gotdotnet.ru/LearnDotNet/NETFramework/592.aspx Примечание: Если вы предпочитаете использовать при работе с данными более объектно-ориентированный подход, то можно выбрать еще один вариант: определить уровень хранения объектов, основанный на функциональности отражения (reflection), предоставляемой общеязыковой исполняющей средой (CLR). Можно создать инфраструктуру, применяющую отражение для чтения свойств объектов и использующую файл сопоставления (mapping file) для описания связей объектов и таблиц. Однако эффективная реализация такой инфраструктуры потребует массы усилий на написание кода. Эти издержки приемлемы для независимых поставщиков программного обеспечения (independent software vendors, ISV) или поставщиков решений (solution providers), но большинству организаций они не по силам. Поэтому мы не будем рассматривать такой подход. То есть у меня получилось вполне стандартное решение. Пока скорость я не замерял, но не думаю, что она намного медленнее. ЗАТО удобно то как. Ничего писать не нужно. Можно забыть про вызов процедур, строки соединения и прочую муть... ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 16.11.2006, 17:37 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
C# Карли Ватсон автор издательства Wrox Press, Глава атрибут Практикум: создание таблиц баз данных с использованием атрибутов ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 16.11.2006, 18:51 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
someone1: Выглядит очень интересно. А что по поводу скорости работы - ведь у вас почти везде используюется reflection ? Есть выход: нужно кешировать то, что мы получаем с помощью рефлекшена. Скоро скину код. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 17.11.2006, 12:26 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
До свидания... ЗЫ: даёшь много вИлАсипедов! разных и прикольных! How can men die better than facing fearful odds, For the ashes of their fathers and the temples of their gods? | Мой Brainbench | BookReader 1.1 | Wallpaper Cycler | ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 17.11.2006, 12:58 |
|
||
|
Возникла оригинальнейшая идея! Нужна критика
|
|||
|---|---|---|---|
|
#18+
Блин, отписаться забыл... Бай. How can men die better than facing fearful odds, For the ashes of their fathers and the temples of their gods? | Мой Brainbench | BookReader 1.1 | Wallpaper Cycler | ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 17.11.2006, 12:59 |
|
||
|
|

start [/forum/topic.php?fid=18&msg=34136111&tid=1387479]: |
0ms |
get settings: |
7ms |
get forum list: |
15ms |
check forum access: |
3ms |
check topic access: |
3ms |
track hit: |
35ms |
get topic data: |
8ms |
get forum data: |
2ms |
get page messages: |
55ms |
get tp. blocked users: |
1ms |
| others: | 208ms |
| total: | 337ms |

| 0 / 0 |
