#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
. Я думаю, что это может быть невозможно, потому что, насколько это касается QBEName
, это целое, я могу либо включить его, либо нет, но я не могу включить его частично.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 я бы никогда этого не понял.