Отделение логики базы данных (попытка №2)

в 14:25, , рубрики: generic object, mongodb, reflection, Программирование, Совершенный код, метки: , , ,

Первая попытка получилась немного сумбурна, поэтому я решил написать более последовательно.

Повторим основную идею: работу с базой нужно организовать через специально заточенный класс, а не разбрасывать код вызовов по всему проекту.

Но этому может препятствовать то, какой интерфейс предлагают производители баз данных. Мы потренируемся на базе MongoDB.

Здесь я предложу реализацию следующей логики работы:

Должен быть базовый класс, от которого наследуются все классы, которые будем сохранять в базе, пусть это будет DBData. Тогда у него будут просто 3 метода: Load, Save, Delete. А обращение к базе уже будет делом DBData.

Польза от такой концепции должна быть очевидна — пользователь объектов может не думая о реализации и нюансах баз данных просто сохранять и восстанавливать свои объекты. Но на этом пути есть ряд сложностей. Рассмотрим их.

Начнем с нашего управляющего класса в нашем ПО. Он в том или иной виде всегда должен быть. Его ответственность достаточно обширна — он занимается склеиванием всех системных классов в нашем ПО, в нашем случае управляет также базами данных.

public class TaskManager
{
  /// Ссылка менеджер задач
  private static TaskManager thisInstance;
  /// Ссылка на наш адаптер к MongoDB
  private Database currentDatabase;
  public TaskManager()
  {
    thisInstance = this;
    currentDatabase = new Database();
    currentDatabase.RunServer();
  }
  /// Получить указатель на менеджер задач
  public static TaskManager GetInstance()
  {
    return thisInstance;
  }
  /// Получить указатель на базу данных
  public  Database GetDatabase()
  {
    return currentDatabase;
  }
  public void CloseDBServer()
  {
    if (currentDatabase != null)
    { currentDatabase.CloseServer(); }
  }
}
 

Теперь собственно адаптер к MongoDB. Как мы видим тут может понадобится использование отображения для обобщенных методов, причем в самых разных вариациях.

public class Database
{
  /// Процесс сервера MongoDB
  private Process MongoDBProcess;
  /// Коннект с сервером MongoDB
  Mongo mongo = new Mongo();
  /// Текущая база данных
  IMongoDatabase db;
 
  /// Запустить сервер
  public void RunServer()
  { ... }
  /// Отключить сервер
  public void CloseServer()
  { ... }
 
  /// Загрузить объект по ID
  public DBData Load(DBData argObject)
  {
    MethodInfo locMethodGetCollection = GetCollection(argObject);
    var locCollection = locMethodGetCollection.Invoke(db, null);
    MethodInfo locMethodLoad = GetMethod(locCollection, "FindOne"1"Object");
    object[] locArgs = { new { ID = argObject.ID } };
    return (DBData)locMethodLoad.Invoke(locCollection, locArgs);
  }
  /// Сохранить объект
  public void Save(DBData argObject)
  {
    MethodInfo locMethodGetCollection = GetCollection(argObject);
    var locCollection = locMethodGetCollection.Invoke(db, null);
    MethodInfo locMethodSave = GetMethod(locCollection, "Save"1"Object");
    object[] locArgs = { argObject };
    locMethodSave.Invoke(locCollection, locArgs);
  }
  /// Удалить объект
  public void Delete(DBData argObject)
  {
    MethodInfo locMethodGetCollection = GetCollection(argObject);
    var locCollection = locMethodGetCollection.Invoke(db, null);
    MethodInfo locMethodDelete = GetMethod(locCollection, "Delete"1"Object");
    object[] locArgs = { new { ID = argObject.ID } };
    locMethodDelete.Invoke(locCollection, locArgs);
  }
 
  /// Получить число объектов
  public long Count(DBData argObject)
  {
    MethodInfo locMethodGetCollection = GetCollection(argObject);
    var locCollection = locMethodGetCollection.Invoke(db, null);
    MethodInfo locMethodCount = GetMethod(locCollection, "Count");
    return (long)locMethodCount.Invoke(locCollection, null);
  }
 
  /// Загрузить идентификацию всех объектов
  public ArrayList LoadAllID(DBData argObject)
  {
    MethodInfo locMethodGeneric = InstantiationLoadAllID(argObject);
    object[] locArgs = { argObject };
    return (ArrayList)locMethodGeneric.Invoke(this, locArgs);
  }
 
  /// Загрузить идентификацию всех объектов
  private ArrayList LoadAllID<T>(DBData argObject) where T:DBData
  {
    ArrayList retArray = new ArrayList();
    MethodInfo locMethodGetCollection = GetCollection(argObject);
    var locCollection = locMethodGetCollection.Invoke(db, null);
 
    MethodInfo locMethodFindAll = GetMethod(locCollection, "FindAll");
    ICursor<T> locCursor = locMethodFindAll.Invoke(locCollection, null) as ICursor<T>;
 
    foreach (DBData d in locCursor.Documents)
    { retArray.Add(d.ID); }
    return retArray;
  }
 
  private MethodInfo GetCollection(object argObject)
  {
    MethodInfo locMethodGetCollectionGeneric = null;
 
    Type locType = typeof(IMongoDatabase);
    MethodInfo[] myMethod = locType.GetMethods();
    foreach (MethodInfo m in myMethod)
    {
      if (m.Name == "GetCollection")
      {
        ParameterInfo[] pi = m.GetParameters();
        if (m.ReturnType.IsGenericType && pi.Length == 0)
        {
          locMethodGetCollectionGeneric = m;
          break;
        }
      }
    }
    Type locObjectType = argObject.GetType();
    Type[] locTypeArgs = { locObjectType };
    return locMethodGetCollectionGeneric.MakeGenericMethod(locTypeArgs);
  }
 
  private MethodInfo InstantiationLoadAllID(object argObject)
  {
    MethodInfo locMethodGeneric = null;
    Type locType = typeof(Database);
    MethodInfo[] locMethod = locType.GetMethods(BindingFlags.NonPublic|BindingFlags.Instance);
    foreach (MethodInfo m in locMethod)
    {
      if (m.Name == "LoadAllID")
      {
        ParameterInfo[] pi = m.GetParameters();
        if (m.IsGenericMethod && pi.Length == 1)
        {
          locMethodGeneric = m;
          break;
        }
      }
    }
    Type locObjectType = argObject.GetType();
    Type[] locTypeArgs = { locObjectType };
    return locMethodGeneric.MakeGenericMethod(locTypeArgs);
  }
 
  private MethodInfo GetMethod(object argObject, string argMethodName)
  {
    return GetMethod(argObject, argMethodName, 0null);
  }
 
  private MethodInfo GetMethod(object argObject, string argMethodName, int argParamCount, string artTypeP1)
  {
    MethodInfo locMethod = null;
 
    Type locType = argObject.GetType();
    MethodInfo[] locMethods = locType.GetMethods();
    foreach (MethodInfo m in locMethods)
    {
      if (m.Name == argMethodName)
      {
        ParameterInfo[] pi = m.GetParameters();
        if (pi.Length == argParamCount)
        {
          if (pi.Length == 1)
          {
            if (pi[0].ParameterType.Name != artTypeP1)
            { continue; }
          }
          locMethod = m;
          break;
        }
      }
    }
    return locMethod;
  }
}
 

Но суть в том, что к адаптеру никто не обращается. У нас есть класс-родитель, наследники которого могут сохраняться и восстанавливаться из базы. Вот класс родитель:

public class DBData
{
  public int ID;
  public DBData()
  { }
 
  public DBData(int argID)
  { ID = argID; }
 
  private Database GetDB()
  {
    return TaskManager.GetInstance().GetDatabase();
  }
 
  public int Count()
  {
    return (int)GetDB().Count(this);
  }
  public void Save()
  {
    GetDB().Save(this);
  }
  public void Delete()
  {
    GetDB().Delete(this);
  }
  public object Load()
  {
    return GetDB().Load(this);
  }
  public ArrayList LoadAllID()
  {
    return GetDB().LoadAllID(this);
  }
}
 

Далее создаем наследника:

public class StrategiesData : DBData
{
  public string Name;
 
  public StrategiesData() : base()
  { }
 
  public StrategiesData(int argID) : base(argID)
  { }
 
  public StrategiesData(int argID, string argName)
  {
    ID = argID;
    Name = argName;
  }
}
 

и далее работаем с ним элементарным образом:

StrategiesData SD = new StrategiesData(1"Test1");
SD.Save();
 
 

и тому подобное. Что может быть проще и приятнее :)

Автор: tac


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js