Как передать условия для SQL предложений в функции? - PullRequest
0 голосов
/ 28 апреля 2020

Я хочу организовать интерфейс, который требует реализации функций, связанных с запросами LINQ для БД.

Как передать условия, которые можно задать в запросе LINQ? Например, у меня есть функция:

public IEnumerable<String> function(string whereClause, string orderByClause, int maxRowCount)

Как вы можете видеть, я обязан явно указать, какие пункты я буду устанавливать в моем выполняемом запросе, и я собираюсь избегать этого. Я хотел бы решение, которое позволяет мне знать об условиях в функции. Как я могу это сделать?

Ответы [ 2 ]

2 голосов
/ 28 апреля 2020

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

Есть два основных подхода к вашей проблеме.

Явная

Идея здесь определяет интерфейс, имеющий методы, разработанные для определенной c цели. Вот пример:

public interface IOrderRepository 
{
  IEnumerable<Order> GetOrdersForCustomer(Guid customerId);
}

Здесь у вас есть метод GetOrdersForCustomer, который позволяет вам получить все заказы, сделанные одним указанным c клиентом. Все ясно.

Обратите внимание, что в этом случае способ использования элемента интерфейса очевиден , и это действительно ценно.

Гибкость

Если вы хотите быть более гибкими и предоставить потребителям интерфейса больше свободы, вы можете определить интерфейс следующим образом:

public interface IOrderRepository 
{
  IEnumerable<Order> GetOrders(Func<Order, bool> filterCondition);
}

Здесь вы предоставили потребителям способ внедрения универсальных c критериев фильтра. по которому они хотят фильтровать заказы. Это гораздо более гибко, но менее явно.

Пример использования следующий:

Guid clientId = // some client id that you have in scope
var clientOrders = _repository.GetOrders(order => order.ClientId == clientId);

Что делать?

Это зависит от ваших конкретных c сценарий, нет правильного и неправильного ответа.

Я предпочитаю быть явным, потому что мне нравится строго придерживаться принципа единой ответственности , когда я пишу код, и я думаю, что явность - это дополнительная ценность (ваш код легче понять и проверить).

Последнее замечание: что мне делать, если мне действительно нужен динамический c запрос?

Существуют несколько расширенных вариантов использования, когда требуется полностью динамический c запрос. Когда я говорю динамический запрос c, я имею в виду запрос, который полностью определяется во время выполнения. Типичным примером является сценарий, в котором пользователю разрешено предоставлять какие-либо критерии (критерии сортировки, критерии упорядочения и т. Д.) Во время выполнения, например, в виде простого текста. Эти случаи на самом деле редки, и, по моему мнению, по возможности, лучше их избегать.

Если вам действительно нужен такой уровень гибкости, то go использует библиотеку, управляемую деревом выражений, например эту , которая позволяет вам выполнять текстовые динамические c запросы.

В этом случае я бы определил интерфейс, предназначенный для выполнения предоставленного пользователем динамического c запроса, с именем, которое четко определяет его назначение. Возможен следующий дизайн:

public class DynamicQuery 
{
  public string FilterCriteria { get; set; } // user provided filter criteria in the form of a string containing code
  public string OrderCriteria { get; set; } // user provided order criteria in the form of a string containing code
}

public interface IProductDynamicQueryExecutor 
{
  IEnumerable<Product> GetProducts(DynamicQuery query);
}

Способ использования этого интерфейса довольно понятен для клиентского кода.

Опять же, я бы избегал любого вида интерфейса общего назначения, предназначенного для выполнения динамических c запросов на любом типе объекта (как вы можете видеть, этот интерфейс строго разработан для случая использования продуктов).

Вообще говоря, если не требуется абсолютно , я предпочитаю избегать любой необязательной абстракции в моем коде (в этом случае может возникнуть соблазн определить общий интерфейс c, где тип сущности выражается как универсальный параметр типа c, что-то вроде IDynamicQueryExecutor<TEntity>).

Единственной идеей общего назначения в приведенном выше коде является класс с именем DynamicQuery, потому что мне кажется разумным, что любой динамический c запрос состоит из какого-то критерия фильтрации и некоторого рода критериев сортировки , Как правило, помните, что немного дублирования кода лучше, чем слишком много абстракции в вашем коде . Опять же, я думаю, что явный и простой в написании код неоценим . Один из моих любимых преподавателей однажды сказал, что преждевременная абстракция убивает проекты , и я полностью с ним согласен.

Точный способ, которым потребительский код может express критерий, зависит от выбранной библиотеки для конкретной реализации, но, вообще говоря, это код, выраженный в каком-то мета-языке, таком как этот:

string filterCriteria = "Name = \"Contoso LTD\" AND City = \"Gotham City\"";
2 голосов
/ 28 апреля 2020

Expression предоставляет базовый класс, из которого получены классы, представляющие узлы дерева выражений. Вы можете сделать статически типизированный для ваших сущностей, чтобы он проверялся на корректность во время компиляции.

Generi c делегаты помогут вам сделать более обобщенный код для ваших сущностей. Простейший пример приведен в следующем коде:

public IEnumerable<TEntity> function<TEntity>(Expression<Func<TEntity, bool>> whereClause, Expression<Func<TEntity, TKey>> columnSelector, int maxRowCount) 
{
    return context
        .Set<TEntity>()   
        .Where(whereClause)
        .OrderBy(columnSelector)
        .Take(maxRowCount)
        .ToList();
}

// using
var result = function<User>(u => u.email.Contains("gmail.com") && y.Age < 50, u => u.Country, 10);
var result2 = function<Post>(p => p.Length > 1000, p => p.AuthorId, 100);

Существуют более сложные и более мощные решения с generi c хранилищами из UoW + шаблон хранилища :

public class GenericRepository<TEntity> where TEntity : class
{
    internal DbContext context;
    internal DbSet<TEntity> dbSet;

    public GenericRepository(DbContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }

    public virtual IEnumerable<TEntity> Get(
        Expression<Func<TEntity, bool>> filter = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        string includeProperties = "")
    {
        IQueryable<TEntity> query = dbSet;

        if (filter != null)
        {
            query = query.Where(filter);
        }

        foreach (var includeProperty in includeProperties.Split
            (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }

        if (orderBy != null)
        {
            return orderBy(query).ToList();
        }
        else
        {
            return query.ToList();
        }
    }

    public virtual TEntity GetByID(object id)
    {
        return dbSet.Find(id);
    }

    public virtual void Insert(TEntity entity)
    {
        dbSet.Add(entity);
    }

    public virtual void Delete(object id)
    {
        TEntity entityToDelete = dbSet.Find(id);
        Delete(entityToDelete);
    }

    public virtual void Delete(TEntity entityToDelete)
    {
        if (context.Entry(entityToDelete).State == EntityState.Detached)
        {
            dbSet.Attach(entityToDelete);
        }
        dbSet.Remove(entityToDelete);
    }

    public virtual void Update(TEntity entityToUpdate)
    {
        dbSet.Attach(entityToUpdate);
        context.Entry(entityToUpdate).State = EntityState.Modified;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...