NHibernate QueryByExample, включающий только определенные свойства

#c# #nhibernate #query-by-example

#c# #nhibernate #запрос по примеру

Вопрос:

Я создал пользовательский селектор свойств, чтобы принимать массив в конструкторе, чтобы указать, какие свойства должны быть включены в поиск. Подход работает хорошо, пока нет типов компонентов, но как мне с ними справиться? Вот пример:

 public class Customer
{
    public virtual int Id { get; private set; }
    public virtual Name Name { get; set; }
    public virtual bool isPreferred { get; set; }


    //...etc
}

public class Name
{
        public string Title { get; set; }
        public string Firstname { get; set; }
        public string Lastname { get; set; }
        public string Fullname { get; }
}


public class CustomerPropertySelector : Example.IPropertySelector
    {
        private string[] _propertiesToInclude = { };

        public CustomerPropertySelector(string[] propertiesToInclude)
        {
            this._propertiesToInclude = propertiesToInclude;
        }

        public bool Include(object propertyValue, String propertyName, NHibernate.Type.IType type)
        {
            //...Checking for null and zeros etc, excluded for brevity

            if (!_propertiesToInclude.Contains(propertyName))
                return false;

            return true;
        }
   }
  

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

ПРИМЕР:

 Customer exampleCust = new Customer(FirstName: "Owen");
IList<Customer> matchedCustomers = _custRepo.GetByExample(exampleCust, new string[] { "Name.FirstName" });
  

Учитывая, что в базе данных есть 2 клиента, только один с именем «Owen», но у обоих есть isPreferred = false , я бы хотел, чтобы мой запрос возвращал только первый. Стандартный QBE вернет оба на основе isPreferred свойства.

РЕШЕНИЕ:

Спасибо за ответы, решение в основном основано на ответе therealmitchconnors, однако я также не смог бы сделать это без ответа Марка Перри.

Хитрость заключалась в том, чтобы понять, что вместо того, чтобы включать Name.FirstName свойство, я на самом деле хочу исключить Name.LastName , поскольку QBE позволяет нам исключать только свойства. Я использовал метод, адаптированный из ответа therealmitchconnors, чтобы помочь мне определить полные имена свойств. Вот рабочий код:

 public IList<T> GetByExample(T exampleInstance, params string[] propertiesToInclude)
{
    ICriteria criteria = _session.CreateCriteria(typeof(T));
    Example example = Example.Create(exampleInstance);

    var props = typeof(T).GetProperties();
    foreach (var prop in props)
    {
        var childProperties = GetChildProperties(prop);
        foreach (var c in childProperties)
        {
            if (!propertiesToInclude.Contains(c))
                example.ExcludeProperty(c);
        }
    }
    criteria.Add(example);

    return criteria.List<T>();
}

private IEnumerable<string> GetChildProperties(System.Reflection.PropertyInfo property)
{
    var builtInTypes = new List<Type> { typeof(bool), typeof(byte), typeof(sbyte), typeof(char), 
        typeof(decimal), typeof(double), typeof(float), typeof(int), typeof(uint), typeof(long), 
        typeof(ulong), typeof(object), typeof(short), typeof(ushort), typeof(string), typeof(DateTime) };

    List<string> propertyNames = new List<string>();
    if (!builtInTypes.Contains(property.PropertyType) amp;amp; !property.PropertyType.IsGenericType)
    {
        foreach (var subprop in property.PropertyType.GetProperties())
        {
            var childNames = GetChildProperties(subprop);
            propertyNames = propertyNames.Union(childNames.Select(r => property.Name   "."   r)).ToList();
        }
    }
    else
        propertyNames.Add(property.Name);

    return propertyNames;
}
  

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

Ответ №1:

Следующий код заменит логику, которую вы используете для заполнения propertiesToInclude. Я изменил его с массива на список, чтобы я мог использовать метод Add, потому что я ленив, но я думаю, вы поняли картину. Это работает только для одного подуровня свойств. Для n уровней вам потребуется выполнить рекурсию.

         List<string> _propertiesToInclude = new List<string>();

        Type t;
        var props = t.GetProperties();
        foreach (var prop in props)
        {
            if (prop.PropertyType.IsClass)
                foreach (var subprop in prop.PropertyType.GetProperties())
                    _propertiesToInclude.Add(string.Format("{0}.{1}", prop.Name, subprop.Name));
            else
                _propertiesToInclude.Add(prop.Name);
        }
  

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

1.Я думаю, вы немного неправильно поняли вопрос — проблема не в том, чтобы решить, как заполнить _propertiesToInclude , это учитывая, что массив, как сообщить QBE, что я хочу исключить Name.LastName из сравнения, но не Name.FirstName . Я думаю, что это может быть невозможно, потому что, насколько это касается QBE Name , это целое, я могу либо включить его, либо нет, но я не могу включить его частично.

2. Итак, вы хотите CustomerPropertySelector.Include(null, "Name.FirstName", IType.Something); возвращать true и CustomerPropertySelector.Include(null, "Name.LastName", IType.Something); возвращать false ? На основании каких критериев? Не могли бы вы просто жестко закодировать метод для исключения Name. Фамилия? Я думаю, что, должно быть, я упускаю суть.

3. Я тоже так думаю, возможно, я неправильно сформулировал вопрос. Я хочу создать пример customer new Customer(FirstName: "Owen") , затем передать это моему методу с помощью propertiesToInclude: new string[] { "Name.FirstName" } , который затем должен сравнивать объекты только на основе этого одного поля.

4. typeof(string).IsClass возвращает true, так что это не сработало бы на классах со строковыми свойствами. Я не могу придумать лучшего способа, чем перебирать и проверять все примитивные типы

Ответ №2:

Я думал, что у меня что-то есть, но, перечитывая ваш вопрос, вы хотите знать, почему код QBE NHibernate не работает со свойствами компонента.

Я думаю, вам нужно создать запрос с подкритериями для части имени.

Возможно, что-то вроде этого:

 public IList<Customer> GetByExample(Customer customer, string[] propertiesToExclude){
    Example customerQuery = Example.Create(customer);
    Criteria nameCriteria = customerQuery.CreateCriteria<Name>();
    nameCriteria.Add(Example.create(customer.Name));
    propertiesToExclude.ForEach(x=> customerQuery.ExcludeProperty(x));
    propertiesToExclude.ForEach(x=> nameCriteria.ExcludeProperty(x));
    return customerQuery.list();
}
  

Это пример из тестового проекта NHibernate, он показывает, как исключить свойства компонента.

 [Test]
public void TestExcludingQBE()
{
        using (ISession s = OpenSession())
        using (ITransaction t = s.BeginTransaction())
        {
            Componentizable master = GetMaster("hibernate", null, "ope%");
            ICriteria crit = s.CreateCriteria(typeof(Componentizable));
            Example ex = Example.Create(master).EnableLike()
                .ExcludeProperty("Component.SubComponent");
            crit.Add(ex);
            IList result = crit.List();
            Assert.IsNotNull(result);
            Assert.AreEqual(3, result.Count);

            master = GetMaster("hibernate", "ORM tool", "fake stuff");
            crit = s.CreateCriteria(typeof(Componentizable));
            ex = Example.Create(master).EnableLike()
                .ExcludeProperty("Component.SubComponent.SubName1");
            crit.Add(ex);
            result = crit.List();
            Assert.IsNotNull(result);
            Assert.AreEqual(1, result.Count);
            t.Commit();
        }
    }
  

Ссылка на исходный код

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

1. Я думаю, вы попали в точку, сказав, что «Код QBE NHibernate не работает со свойствами компонента». Есть ли ссылка, которая могла бы подтвердить это?

2. Проблема в том, что QBE не знает, включать / исключать составную часть объекта, если вы специально не добавите критерии компонента в запрос. Я полностью отказался от использования QBE в своем приложении в пользу использования следующих API (в порядке предпочтения). Linq, повторный запрос, критерии, HQL.

3. В дополнение к моему последнему комментарию я нашел некоторый код в тестовом проекте NHibernate, который, по-видимому, показывает, как исключить свойства компонента из запросов QBE.

4. Ага! Большое спасибо за продолжение, это помогло мне понять ответ. QBE поддерживает свойства компонента, я просто запутался в том, как это сделать. К сожалению, ответ therealmitchconnors ближе к истине, хотя без примера NHTest я бы никогда этого не понял.