#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