c # — Могу ли я преобразовать функции, которые имеют похожие входные данные, но вызывают разные службы, в одну универсальную функцию?

#c# #generics #design-patterns #asp.net-web-api2 #refactoring

#c# #универсальные #шаблоны проектирования #asp.net-web-api2 #рефакторинг

Вопрос:

Я ищу способ объединить x количество очень похожих CRUD-функций в одну без необходимости использовать x количество операторов if else для проверки типа универсальной.

У меня есть контроллеры Web API, с которых я хочу выполнять вызовы следующим образом:

 Service.Get<FooModel>(number, type, part, version); 
  

Это делается для того, чтобы избежать необходимости иметь чрезвычайно похожую функцию для более чем 40 конечных точек API. Проблема в том, что когда я получаю это в своем сервисе, я должен проверить тип заданного универсального и сравнить с этими 40 типами объектов в одной функции. Все модели в настоящее время наследуются от базовой унаследованной модели.

Текущая универсальная функция

(Функции создания, обновления, удаления похожи):

 public T Get<T>(string documentNr, string type, string part, string version) where T : InheritedModel, new()
{
    try
    {
        T model = new T();

        if (typeof(T) == typeof(InheritedModel))
        {
           using (var repo = new InheritedModelConsumer(ref _helper))
           {
                model = (T)repo.Get(documentNr, type, part, version);
           }
        }
        else if (typeof(T) == typeof(FooModel))
        {
            using (var repo = new FooModelConsumer(ref _helper))
            {
                model = (T)(object)repo.Get(documentNr, type, part, version);
            }
        }
        else if (typeof(T) == typeof(ComponentModel))
        {
            using (var repo = new ComponentModelConsumer(ref _helper))
            {
                model = (T)(object)repo.Get(documentNr, type, part, version);
            }
        }
        else if (typeof(T) == typeof(BarModel))
        {
            using (var repo = new BarModelConsumer(ref _helper))
            {
               model = (T)(object)repo.Get(documentNr, type, part, version);
            }
        }
        ... and so on
        ... and so on
        ...
        else
            throw new Exception("Type T structure not defined");

        return model;
    }
    catch (Exception)
    {

        throw;
    }
    finally
    {
        _helper.Dispose();
    }
}
  

Это действительно работает, но если это возможно, я ищу что-нибудь, где я мог бы сказать во время выполнения: «О, у меня есть этот объект типа T, и, поскольку я знаю, что все функции имеют одинаковые входные данные, я собираюсь создать экземпляр этого потребителя типа TConsumer, вызовите consumer.Получить (входные данные), а затем вернуть объект T любому контроллеру API, который вызвал меня.»

Редактировать

Пример используемого простого потребительского класса

 
    internal sealed class FooConsumer : RepositoryConsumer<Foo, FooRepository, FooFilter>
        {
            public FooConsumer(ref SqlHelper helper) : base(ref helper) { }

            public List<Foo> GetAll(string token)
            {
                return _repo.Get().Where(x => Extensions.StringContainsToken(x.AccountName, token)).ToList();
            }
        }

  

Потребитель репозитория, от которого наследуют все потребители.
T — это модель, K — репозиторий (пользовательский класс ORM), а O — фильтр для предложения WHERE, выполняемого ORM.

 

     public abstract class RepositoryConsumer<T, K, O> : IDisposable, IRepositoryConsumer<T> where T : class, new() where K : Repository<T, O>, new() where O : QueryFilter, new()
        {
            /// <summary>
            /// Repository instance
            /// </summary>
            protected K _repo;

            /// <summary>
            /// Only constructor avaialble. MUst pass SqlHelper instance for transaction support
            /// </summary>
            /// <param name="sql"></param>
            public RepositoryConsumer(ref SqlHelper sql)
            {
                _repo = Activator.CreateInstance(typeof(K), new object[] { sql }) as K;
            }

            /// <summary>
            /// Allow consumer initializations in using statements
            /// </summary>
            public void Dispose()
            {

            }

            /// <summary>
            /// Create instance of T
            /// </summary>
            /// <param name="data"></param>
            /// <returns></returns>
            public virtual int Create(T data)
            {
                return _repo.Create(data);
            }

            /// <summary>
            /// Bulk create instances of T
            /// </summary>
            /// <param name="contract"></param>
            /// <returns></returns>
            public virtual int Create(BaseBulkable<T> contract)
            {
                return _repo.BulkCreate(contract);
            }

            /// <summary>
            /// Get an instance of T based on a single PK field id
            /// </summary>
            /// <param name="id"></param>
            /// <returns></returns>
            public virtual T Get(long id)
            {
                return _repo.Get(id);
            }

            /// <summary>
            /// Gets all instances of T
            /// </summary>
            /// <returns></returns>
            public virtual List<T> GetAll()
            {
                return _repo.Get();
            }

            /// <summary>
            /// Updates an instance of T
            /// </summary>
            /// <param name="data"></param>
            /// <returns></returns>
            public virtual int Update(T data)
            {
                return _repo.Update(data);
            }

            /// <summary>
            /// Updates an instance of T based on a single PK field id
            /// </summary>
            /// <param name="id"></param>
            /// <param name="data"></param>
            /// <returns></returns>
            public virtual int Update(long id, T data)
            {
                return _repo.Update(id, data);
            }

            /// <summary>
            /// Deletes an instance of T
            /// </summary>
            /// <param name="data"></param>
            /// <returns></returns>
            public virtual int Delete(T data)
            {
                return _repo.Delete(data);
            }

            /// <summary>
            /// Deletes an instance of T based on a single PK field id
            /// </summary>
            /// <param name="id"></param>
            /// <returns></returns>
            public virtual int Delete(long id)
            {
                return _repo.Delete(id);
            }
        }

  

Комментарии:

1. Вы могли бы зарегистрировать своих потребителей в словаре по типу сущности. var repo = dict[typeof(T)]; .

2. Что уникального в каждом потребителе, что отделяет его от RepositoryConsumer<T> ? Если вы можете использовать универсальный тип T при вызове вашей базы данных или API, вы могли бы просто ввести RepositoryConsumer<T> или IRepositoryConsumer<T> и удалить if-else

3. Ваш RepositoryConsumer код не может быть вашим реальным кодом, он даже не компилируется, виртуальной функции нужно тело?

4. @JSteward обновил вопрос кодом RepositoryConsumer. RepositoryConsumer фактически принимает 3 универсальных аргумента, каждый из которых имеет отношение к T , и именно здесь, я думаю, я сталкиваюсь с большинством своих проблем, пытаясь уменьшить вызов службы, извините. База репозиториев взята из внутреннего пакета Nuget, моя ошибка. Итак, у них есть тела функций, это просто в сборке.

5. @JSteward обновлен правильным кодом для RepositoryConsumer