I have built a repository which only exposes IEnumerable based mostly on the examples in "Professional ASP.NET Design Patterns" by Scott Millett.
However because he mostly uses NHibernate his example of how to implement the Query Object Pattern, or rather how to best translate the query into something useful in EF, is a bit lacking.
I am looking for a good example of an implementation of the Query Object Pattern using EF4.
Edit: The main issues with the trivial example in the book are that CreateQueryAndObjectParameters only handles 2 cases, Equal & LesserThanOrEqual - not exactly a complete query solution. And it uses a string to build the criterion - which is a very crude way to handle this when compared to NHibernate. He says he'll provide the EF code for the chapter 10 example, but its not in the download. Hence looking for a real world example.
You're probably sick of hearing from me at this point, but the natively supported IQueryable is the only Query Object Pattern implementation you need for EF.
According to the book ( "Professional ASP.NET Design Patterns" by Scott Millett) you can use this codes [I have improved some lines]:
Infrastructure layer :
Criterion class : (each Query can contain some Criterion)
public class Criterion
{
private string _propertyName;
private object _value;
private CriteriaOperator _criteriaOperator;
public Criterion(string propertyName, object value,
CriteriaOperator criteriaOperator)
{
_propertyName = propertyName;
_value = value;
_criteriaOperator = criteriaOperator;
}
public string PropertyName
{
get { return _propertyName; }
}
public object Value
{
get { return _value; }
}
public CriteriaOperator criteriaOperator
{
get { return _criteriaOperator; }
}
public static Criterion Create<T>(Expression<Func<T, object>> expression, object value, CriteriaOperator criteriaOperator)
{
string propertyName = PropertyNameHelper.ResolvePropertyName<T>(expression);
Criterion myCriterion = new Criterion(propertyName, value, criteriaOperator);
return myCriterion;
}
}
CriteriaOperator Class:
public enum CriteriaOperator
{
Equal,
LesserThanOrEqual,
NotApplicable
// TODO: Add the remainder of the criteria operators as required.
}
OrderByClause Class:
public class OrderByClause
{
public string PropertyName { get; set; }
public bool Desc { get; set; }
}
Query class:
public class Query
{
private QueryName _name;
private IList<Query> _subQueries = new List<Query>();
private IList<Criterion> _criteria = new List<Criterion>();
public Query()
: this(QueryName.Dynamic, new List<Criterion>())
{ }
public Query(QueryName name, IList<Criterion> criteria)
{
_name = name;
_criteria = criteria;
}
public QueryName Name
{
get { return _name; }
}
public bool IsNamedQuery()
{
return Name != QueryName.Dynamic;
}
public IEnumerable<Criterion> Criteria
{
get {return _criteria ;}
}
public void Add(Criterion criterion)
{
if (!IsNamedQuery())
_criteria.Add(criterion);
else
throw new ApplicationException("You cannot add additional criteria to named queries");
}
public IEnumerable<Query> SubQueries
{
get { return _subQueries; }
}
public void AddSubQuery(Query subQuery)
{
_subQueries.Add(subQuery);
}
public QueryOperator QueryOperator { get; set; }
public OrderByClause OrderByProperty { get; set; }
}
PropertyNameHelper class:
public static class PropertyNameHelper
{
public static string ResolvePropertyName<T>(
Expression<Func<T, object>> expression)
{
var expr = expression.Body as MemberExpression;
if (expr == null)
{
var u = expression.Body as UnaryExpression;
expr = u.Operand as MemberExpression;
}
return expr.ToString().Substring(expr.ToString().IndexOf(".") + 1);
}
}
You need a QueryTranslator class too, to translate query (in Repository layer):
public class TimelogQueryTranslator : QueryTranslator
{
public ObjectQuery<Timelog> Translate(Query query)
{
ObjectQuery<Timelog> timelogQuery;
if (query.IsNamedQuery())
{
timelogQuery = FindEFQueryFor(query);
}
else
{
StringBuilder queryBuilder = new StringBuilder();
IList<ObjectParameter> paraColl = new List<ObjectParameter>();
CreateQueryAndObjectParameters(query, queryBuilder, paraColl);
//[Edited By= Iman] :
if (query.OrderByProperty == null)
{
timelogQuery = DataContextFactory.GetDataContext().Timelogs
.Where(queryBuilder.ToString(), paraColl.ToArray());
}
else if (query.OrderByProperty.Desc == true)
{
timelogQuery = DataContextFactory.GetDataContext().Timelogs
.Where(queryBuilder.ToString(), paraColl.ToArray()).OrderBy(String.Format("it.{0} desc", query.OrderByProperty.PropertyName));
}
else
{
timelogQuery = DataContextFactory.GetDataContext().Timelogs
.Where(queryBuilder.ToString(), paraColl.ToArray()).OrderBy(String.Format("it.{0} asc", query.OrderByProperty.PropertyName));
}
//[Edited By= Iman] .
}
return timelogQuery;
}
//-------------------------------------------------------------
public abstract class QueryTranslator
{
public void CreateQueryAndObjectParameters(Query query, StringBuilder queryBuilder, IList<ObjectParameter> paraColl)
{
bool _isNotFirstFilterClause = false;
foreach (Criterion criterion in query.Criteria)
{
if (_isNotFirstFilterClause)
{
queryBuilder.Append(" AND "); //TODO: select depending on query.QueryOperator
}
switch (criterion.criteriaOperator)
{
case CriteriaOperator.Equal:
queryBuilder.Append(String.Format("it.{0} = #{0}", criterion.PropertyName));
break;
case CriteriaOperator.LesserThanOrEqual:
queryBuilder.Append(String.Format("it.{0} <= #{0}", criterion.PropertyName));
break;
default:
throw new ApplicationException("No operator defined");
}
paraColl.Add(new ObjectParameter(criterion.PropertyName, criterion.Value));
_isNotFirstFilterClause = true;
}
}
}
Now in your service layer:
public IEnumerable<Timelog> GetAllTimelogsFor(int iadcId, byte workShift)
{
Query query = new Query(QueryName.Dynamic,new List<Criterion>());
query.Add(Criterion.Create<Timelog>(t=>t.IadcId, iadcId, CriteriaOperator.Equal));
query.QueryOperator = QueryOperator.And;
query.Add(Criterion.Create<Timelog>(t=>t.Shift, workShift, CriteriaOperator.Equal));
query.OrderByProperty = new OrderByClause { PropertyName = "FromTime", Desc = false };
IEnumerable<Timelog> timelogs = _timelogRepository.FindBy(query);
return timelogs;
}
Related
In short I wanted to create a custom IUserType to represent IPAddress from .NET (as an inet type in postgresql) and to be able to query it with custom functions via HQL and Linq. I have trouble with implementing a custom function for Linq.
What I have thus far:
A) I am able to map it:
public class SomeEntityMapper : ClassMap<SomeEntity> {
public SomeEntityMapper() {
...
Map(x => x.IpAddressField)
.CustomSqlType("inet")
.CustomType<IPAddressUserType>()
...
}
}
IUserType implementation below:
[Serializable]
public class IPAddressUserType : IUserType
{
public new bool Equals(object x, object y)
{
if (x == null && y == null)
return true;
if (x == null || y == null)
return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
if (x == null)
return 0;
return x.GetHashCode();
}
public object NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner)
{
if (names.Length == 0)
throw new InvalidOperationException("Expected at least 1 column");
if (rs.IsDBNull(rs.GetOrdinal(names[0])))
return null;
object value = rs[names[0]];
return value;
}
public void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session)
{
NpgsqlParameter parameter = (NpgsqlParameter) cmd.Parameters[index];
parameter.NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Inet;
if (value == null)
{
parameter.Value = DBNull.Value;
}
else
{
parameter.Value = value;
}
}
public object DeepCopy(object value)
{
if (value == null)
return null;
IPAddress copy = IPAddress.Parse(value.ToString());
return copy;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
if (cached == null)
return null;
if (IPAddress.TryParse((string)cached, out var address))
{
return address;
}
return null;
}
public object Disassemble(object value)
{
if (value == null)
return null;
return value.ToString();
}
public SqlType[] SqlTypes => new SqlType[] { new NpgsqlSqlType(DbType.String, NpgsqlTypes.NpgsqlDbType.Inet), };
public Type ReturnedType => typeof(IPAddress);
public bool IsMutable => false;
}
public class NpgsqlSqlType : SqlType
{
public NpgsqlDbType NpgDbType { get; }
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType)
: base(dbType)
{
NpgDbType = npgDbType;
}
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType, int length)
: base(dbType, length)
{
NpgDbType = npgDbType;
}
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType, byte precision, byte scale)
: base(dbType, precision, scale)
{
NpgDbType = npgDbType;
}
}
}
B) I am able to query it using HQL and a custom function (inet_equals) I implemented.
using(var session = _factory.OpenSession()) {
var q = session.CreateQuery("from SomeEntity as s WHERE inet_equals(s.IpAddressField, :ip)");
q.SetParameter("ip", IPAddress.Parse("4.3.2.1"), NHibernateUtil.Custom(typeof(IPAddressUserType)));
}
Custom Postgresql Dialect extending the HQL functions implementation below:
public class CustomPostgresqlDialect : PostgreSQL83Dialect
{
public CustomPostgresqlDialect()
{
RegisterFunction("inet_equals", new SQLFunctionTemplate(NHibernateUtil.Boolean, "(?1::inet = ?2::inet)"));
}
}
C) I want to be able to query it using LINQ like so:
using(var session = _factory.OpenSession()) {
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(IPAddress.Parse("4.3.2.1")));
}
Custom LINQ Generator for NHibernate provider below:
public static class InetExtensions
{
public static bool InetEquals(this IPAddress value, IPAddress other)
{
throw new NotSupportedException();
}
}
public class InetGenerator : BaseHqlGeneratorForMethod
{
public InetGenerator()
{
SupportedMethods = new[]
{
ReflectHelper.GetMethodDefinition(() => InetExtensions.InetEquals(default(IPAddress), default(IPAddress)))
};
}
public override HqlTreeNode BuildHql(MethodInfo method, System.Linq.Expressions.Expression targetObject, ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
HqlExpression lhs = visitor.Visit(arguments[0]).AsExpression();
HqlExpression rhs = visitor.Visit(arguments[1]).AsExpression();
return treeBuilder.BooleanMethodCall(
"inet_equals",
new[]
{
lhs,
rhs
}
);
}
}
public class ExtendedLinqToHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public ExtendedLinqToHqlGeneratorsRegistry()
: base()
{
this.Merge(new InetGenerator());
}
}
Unfortunately when using LINQ I get this exception:
HibernateException: Could not determine a type for class: System.Net.IPAddress
Interestingly, this is the exact same error I get if I omit the NHibernateUtil.Custom(typeof(IPAddressUserType)) parameter inside the HQL query.
Which leads me to believe I am on the right track, but I can't figure out what I may be missing exactly. I assume I need to inform NHibernate inside the Generator that this is a custom UserType (just like I did with the HQL query via the NHibernateUtil.Custom(typeof(IPAddressUserType)) parameter.
I found the solution.
To hint NHibernate to use the proper UserType use: MappedAs extension method, like so:
using(var session = _factory.OpenSession()) {
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(
IPAddress.Parse("4.3.2.1").MappedAs(NHibernateUtil.Custom(typeof(IPAddressUserType))
);
}
The year is 2019 and I'm still stuck on version 3.3.2.GA of NHibernate and the MappedAs extension method exists only on version 4.x onwards.
My scenario was the need to pass a large string as a parameter to a supported method in an HqlGeneratorForMethod.
I created the following class to store any large string to use as a parameter of any method throughout the application:
public class StringClob
{
public StringClob()
{
}
public StringClob(string value)
{
Value = value;
}
public virtual string Value { get; protected set; }
}
In order to link the NHibernateUtil.StringClob type with the string value I had the idea to create a mapping for my class without informing table mapping just the property (I use FluentNHibernate to map class):
public class StringClobMap : ClassMap<StringClob>
{
public StringClobMap()
{
Id(x => x.Value, "VALUE").CustomType("StringClob").CustomSqlType("VARCHAR(MAX)").Length(int.MaxValue / 2);
}
}
And now hypothetically following the example of #krdx the usage would look like this:
using(var session = _factory.OpenSession())
{
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(new StringClob("<large_string_here>")));
// ...
}
Therefore by passing the StringClob class as a parameter, NHibernate gets the custom type defined in the mapping.
I hope I helped those who still use NHibernate 3.3.2.GA.
I have an abstract class like this:
public abstract class Records
{
public string Type;
public string Source;
public int Value;
protected Records(string type, string source, int value)
{
Type = type;
Source = source;
Value = value;
}
}
I would like to create many classes inheriting this class, and filling their Type field with a value coming from a static class like this:
public static class ContentTypesString
{
public static string DocumentNew { get { return "Document - New this Month"; }}
public static string HeadlinesNew { get { return "Headlines - New this Month"; }}
etc...
}
I would like to be able to create those child classes without having a test "if foo == "document" then type = ContentTypesString.DocumentNew" or an equivalent switch case (I really have a lot of cases)
Is there a design pattern that suits my needs?
EDIT : As several people pointed out, i should show how i create my instances.
private delegate SPListItemCollection Query(SPWeb web, DateTime startDate, DateTime endDate);
private readonly Query _queries;
#region Constructors
public QueryHandler(SPWeb web, DateTime startTimeSelectedDate, DateTime endTimeSelectedDate)
{
if (web == null) throw new ArgumentNullException("web");
_web = web;
_startTimeSelectedDate = startTimeSelectedDate;
_endTimeSelectedDate = endTimeSelectedDate;
RecordsList = new List<Records>();
// Query Invocation List
_queries = NumberPagePerMonthQuery.PreparedQuery;
_queries += NumberDocumentsPerMonthQuery.PreparedQuery;
_queries += NumberHeadlinesPerMonthQuery.PreparedQuery;
_queries += NumberLeaderboxPerMonthQuery.PreparedQuery;
_queries += NumberNewsPerMonthQuery.PreparedQuery;
_queries += NumberPagesModifiedPerMonthQuery.PreparedQuery;
_queries += NumberPicturesPerMonthQuery.PreparedQuery;
_queries += NumberTeasingPerMonthQuery.PreparedQuery;
}
#endregion Constructors
#region Public Methods
// what about NullReferenceException ? C#6 : item?.Foreach(item => {}); ?
/*** NO C#6 compiler in VS2012... ***/
public void Queries()
{
foreach (var del in _queries.GetInvocationList())
{
var queryresult =
(SPListItemCollection) del.DynamicInvoke(_web, _startTimeSelectedDate, _endTimeSelectedDate);
RecordsList.Add(new Records(del.Method.Name, _web.Title, queryresult.Count));
}
}
EDIT² :
The solution i chose
public List<IQuery> QueryList { get; } // no delegate anymore, and static classes became implementations of IQuery interface.
#region Constructors
public QueryHandler(SPWeb web, DateTime startTimeSelectedDate, DateTime endTimeSelectedDate)
{
if (web == null) throw new ArgumentNullException("web");
_web = web;
_startTimeSelectedDate = startTimeSelectedDate;
_endTimeSelectedDate = endTimeSelectedDate;
RecordsList = new List<Records>();
QueryList = new List<IQuery>
{
new NumberDocumentsPerMonthQuery(),
new NumberHeadlinesPerMonthQuery(),
new NumberLeaderboxPerMonthQuery(),
new NumberNewsPerMonthQuery(),
new NumberPagePerMonthQuery(),
new NumberPagesModifiedPerMonthQuery(),
new NumberPicturesPerMonthQuery(),
new NumberTeasingPerMonthQuery()
};
}
#endregion Constructors
#region Public Methods
// what about NullReferenceException ? C#6 : item?.Foreach(item => {}); ?
/*** NO C#6 compiler in VS2012... ***/
public void Queries()
{
foreach (var query in QueryList)
{
var queryresult = query.PreparedQuery(_web, _startTimeSelectedDate, _endTimeSelectedDate);
RecordsList.Add(query.CreateRecord(_web.Title, queryresult.Count));
}
}
Record class follow the implementation suggested by #dbraillon
Implementation of IQuery interface were added the method :
public Records CreateRecord(string source, int value)
{
return new ModifiedPagesPerMonthRecord(source, value); //or another child of Record class.
}
And voilĂ . Thank you all for the help.
You want to make collection of records, by string code of object type, and parameters.
One of many way to do it - use builder.
Firstly we need to configurate builder:
var builder = new RecordBuilder()
.RegisterBuilder("document", (source, value) => new Document(source, value))
.RegisterBuilder("headlines", (source, value) => new Headlines(source, value));
here we specify how to build record with code "document" and "headlines".
To build a record call:
builder.Build("document", "source", 1);
Builder code can by something like this
(here we look if we know how to build record of the passed type and make it):
public class RecordBuilder
{
public Records Build(string code, string source, int value)
{
Func<string, int, Records> buildAction;
if (recordBuilders.TryGetValue(code, out buildAction))
{
return buildAction(source, value);
}
return null;
}
public RecordBuilder RegisterBuilder(string code, Func<string, int, Records> buildAction)
{
recordBuilders.Add(code, buildAction);
return this;
}
private Dictionary<string, Func<string, int, Records>> recordBuilders = new Dictionary<string, Func<string, int, Records>> ();
}
public class Document : Records
{
public Document(string source, int value) : base(ContentTypesString.DocumentNew, source, value)
{
}
}
public class Headlines : Records
{
public Headlines(string source, int value) : base(ContentTypesString.HeadlinesNew, source, value)
{
}
}
Is that what you need ?
public abstract class Records
{
public string Type;
public string Source;
public int Value;
protected Records(string type, string source, int value)
{
Type = type;
Source = source;
Value = value;
}
}
public class DocumentRecords : Records
{
public DocumentRecords(string source, int value)
: base(ContentTypesString.DocumentNew, source, value) // use here
{
}
}
public class HeadlinesRecords : Records
{
public HeadlinesRecords(string source, int value)
: base(ContentTypesString.HeadlinesNew, source, value) // use here
{
}
}
public static class ContentTypesString
{
public static string DocumentNew { get { return "Document - New this Month"; } }
public static string HeadlinesNew { get { return "Headlines - New this Month"; } }
}
I am creating a new web API and would like to allow the user to specify what fields get returned to them in the URL.
My current thoughts are:
For a sample model like this:
public class Value
{
public string ValueId { get; set; }
public int Number { get; set; }
public ValueInternal Internal { get; set; }
}
public class ValueInternal
{
public int Number { get; set; }
public string Something { get; set; }
}
and a URL like this
http://example.com/api/values/?_fields=Number,Internal(Something)
would return this
[
{
"Number": 0,
"Internal": {
"Number": 0
}
}
]
I have come up with the below method of achieving this, but it has some flaws. I.e. it couldn't handle if Internal was an enumerable of ValueInternal or has no support for include all or include all except, or if T and TResult are different types. Does anyone have any suggestions on how I can improve this or if there already exists a way of doing it that I am missing.
public static Expression<Func<T, TResult>> CreateSelector<T, TResult>() where TResult : new()
{
var property = "Number,Internal(Something)";
return arg => Process<T, TResult>(arg, default(TResult), property);
}
private static TResult Process<T, TResult>(T arg, TResult output, string propertyList) where TResult : new()
{
if (output == null)
{
output = new TResult();
}
if (string.IsNullOrEmpty(propertyList))
{
return output;
}
var properties = Regex.Split(propertyList, #"(?<!,[^(]+\([^)]+),");
foreach (var property in properties)
{
var propertyName = property;
var propertyInternalsMatch = Regex.Match(property, #"\(.*(?<!,[^(]+\([^)]+)\)");
var internalPropertyList = propertyInternalsMatch.Value;
if (!string.IsNullOrEmpty(internalPropertyList))
{
propertyName = property.Replace(internalPropertyList, "");
internalPropertyList = internalPropertyList.Replace("(", "");
internalPropertyList = internalPropertyList.Replace(")", "");
}
var tProperty = arg.GetType().GetProperty(propertyName);
if(tProperty == null) continue;
var tResultProperty = output.GetType().GetProperty(propertyName);
if(tResultProperty == null) continue;
if (tProperty.PropertyType.IsPrimitive || tProperty.PropertyType.IsValueType || (tProperty.PropertyType == typeof(string)))
{
tResultProperty.SetValue(output, tProperty.GetValue(arg));
}
else
{
var propertyInstance = Activator.CreateInstance(tResultProperty.PropertyType);
tResultProperty.SetValue(output, Process(tProperty.GetValue(arg), propertyInstance, internalPropertyList));
}
}
return output;
}
After a bit more reading I think I want to do something like the answer to this question LINQ : Dynamic select but that still has the same flaws my solution had
If you use OData support on your ASP .NET Web API you can jus use $select, but if you don't want to use it or your underlying system can't be easy queried using Linq, you can use a custom contract resolver, but in this case you are just reducing the serialization size, not the internal data traffic.
public class FieldsSelectContractResolver : CamelCasePropertyNamesContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.GetIsSpecified = (t) =>
{
var fields = HttpContext.Current.Request["fields"];
if (fields != null)
{
return fields.IndexOf(member.Name, StringComparison.OrdinalIgnoreCase) > -1;
}
return true;
};
return property;
}
}
and in WebApiConfig.cs set the custom contract resolver:
var jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.ContractResolver = new FieldsSelectContractResolver();
I have a DataTable with complex objects.
For example,
class ComplexDataWrapper
{
public string Name{ get; set; }
public ComplexData Data{ get; set; }
public ComplexDataWrapper(ComplexData data)
{
this.Data = data;
this.Name = "Something";
}
public override string ToString()
{
return Name;
}
}
And now I want to bind cells from DataTable to objects of ComplexDataWrapper
So, I try something like this :
...
var column = new DataColumn() { ColumnName = columnName, DataType = typeof(ComplexDataWrapper)};
row[column] = new ComplexDataWrapper(data);
But, I want to bind for only one property, for example, Name.
And in the gridview (DataTable is a data source for this view) I want to edit this property(Name).
var complexDataWrapper = row[column] as ComplexDataWrapper;
complexDataWrapper always equals to NULL.
I know that I miss something.
So my questions : How I can bind my cell of DataTable to complex object? Plus in grid view I want to edit exactly one property of complex object.
Thanks. Hopefully, everything is clear.
So my questions : How I can bind my cell of DataTable to complex object? Plus in grid view I want to edit exactly one property of complex object.
What you need here is the ability to bind to a so called property path (e.g. obj.Prop1.Prop2). Unfortunately WinForms has limited support for that - it's supported for simple data binding (like control.DataBindings.Add(...)) but not for list data binding which is used by DataGridView control and similar.
Fortunately it's still doable with some (most of the time trivial) coding, because the data binding is build around an abstraction called PropertyDescriptor. By default it is implemented via reflection, but nothing prevents you to create your own implementation and do whatever you like inside it. That allows you to do many things that are not possible with reflection, in particular to simulate "properties" that actually do not exist.
Here we will utilize that possibility to create a "property" that actually gets/sets its value from a child property of the original property, while from outside it still looks like a single property, thus allowing to data bind to it:
public class ChildPropertyDescriptor : PropertyDescriptor
{
public static PropertyDescriptor Create(PropertyDescriptor sourceProperty, string childPropertyPath, string displayName = null)
{
var propertyNames = childPropertyPath.Split('.');
var propertyPath = new PropertyDescriptor[1 + propertyNames.Length];
propertyPath[0] = sourceProperty;
for (int i = 0; i < propertyNames.Length; i++)
propertyPath[i + 1] = propertyPath[i].GetChildProperties()[propertyNames[i]];
return new ChildPropertyDescriptor(propertyPath, displayName);
}
private ChildPropertyDescriptor(PropertyDescriptor[] propertyPath, string displayName)
: base(propertyPath[0].Name, null)
{
this.propertyPath = propertyPath;
this.displayName = displayName;
}
private PropertyDescriptor[] propertyPath;
private string displayName;
private PropertyDescriptor RootProperty { get { return propertyPath[0]; } }
private PropertyDescriptor ValueProperty { get { return propertyPath[propertyPath.Length - 1]; } }
public override Type ComponentType { get { return RootProperty.ComponentType; } }
public override bool IsReadOnly { get { return ValueProperty.IsReadOnly; } }
public override Type PropertyType { get { return ValueProperty.PropertyType; } }
public override bool CanResetValue(object component) { var target = GetTarget(component); return target != null && ValueProperty.CanResetValue(target); }
public override object GetValue(object component) { var target = GetTarget(component); return target != null ? ValueProperty.GetValue(target) : null; }
public override void ResetValue(object component) { ValueProperty.ResetValue(GetTarget(component)); }
public override void SetValue(object component, object value) { ValueProperty.SetValue(GetTarget(component), value); }
public override bool ShouldSerializeValue(object component) { var target = GetTarget(component); return target != null && ValueProperty.ShouldSerializeValue(target); }
public override AttributeCollection Attributes { get { return ValueProperty.Attributes; } }
public override string Category { get { return ValueProperty.Category; } }
public override TypeConverter Converter { get { return ValueProperty.Converter; } }
public override string Description { get { return ValueProperty.Description; } }
public override bool IsBrowsable { get { return ValueProperty.IsBrowsable; } }
public override bool IsLocalizable { get { return ValueProperty.IsLocalizable; } }
public override string DisplayName { get { return displayName ?? RootProperty.DisplayName; } }
public override object GetEditor(Type editorBaseType) { return ValueProperty.GetEditor(editorBaseType); }
public override PropertyDescriptorCollection GetChildProperties(object instance, Attribute[] filter) { return ValueProperty.GetChildProperties(GetTarget(instance), filter); }
public override bool SupportsChangeEvents { get { return ValueProperty.SupportsChangeEvents; } }
public override void AddValueChanged(object component, EventHandler handler)
{
var target = GetTarget(component);
if (target != null)
ValueProperty.AddValueChanged(target, handler);
}
public override void RemoveValueChanged(object component, EventHandler handler)
{
var target = GetTarget(component);
if (target != null)
ValueProperty.RemoveValueChanged(target, handler);
}
private object GetTarget(object source)
{
var target = source;
for (int i = 0; target != null && target != DBNull.Value && i < propertyPath.Length - 1; i++)
target = propertyPath[i].GetValue(target);
return target != DBNull.Value ? target : null;
}
}
The code is not so small, but all it does is basically delegating the calls to the corresponding methods of the property descriptor chain representing the path from the original property to the child property. Also please note that many methods of the PropertyDescriptor are used only during the design time, so creating a custom concrete runtime property descriptor usually needs only to implement ComponentType, PropertyType, GetValue and SetValue (if supported).
So far so good. This is just the first part of the puzzle. We can create a "property", now we need a way to let data binding use it.
In order to do that, we'll utilize another data binding related interface called ITypedList:
Provides functionality to discover the schema for a bindable list, where the properties available for binding differ from the public properties of the object to bind to.
In other words, it allows us to provide "properties" for the list elements. But how? If we were implementing the data source list, it would be easy. But here we want to do that for a list that we don't know in advance (I'm trying the keep the solution generic).
The solutions is to wrap the original list in onother one that will implement IList (the minimum requirement for list data binding) by delegating all the calls to the underlying list, but by implementing ITypedList will control the properties used for binding:
public static class ListDataView
{
public static IList Create(object dataSource, string dataMember, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper)
{
var source = (IList)ListBindingHelper.GetList(dataSource, dataMember);
if (source == null) return null;
if (source is IBindingListView) return new BindingListView((IBindingListView)source, propertyMapper);
if (source is IBindingList) return new BindingList((IBindingList)source, propertyMapper);
return new List(source, propertyMapper);
}
private class List : IList, ITypedList
{
private readonly IList source;
private readonly Func<PropertyDescriptor, PropertyDescriptor> propertyMapper;
public List(IList source, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper) { this.source = source; this.propertyMapper = propertyMapper; }
// IList
public object this[int index] { get { return source[index]; } set { source[index] = value; } }
public int Count { get { return source.Count; } }
public bool IsFixedSize { get { return source.IsFixedSize; } }
public bool IsReadOnly { get { return source.IsReadOnly; } }
public bool IsSynchronized { get { return source.IsSynchronized; } }
public object SyncRoot { get { return source.SyncRoot; } }
public int Add(object value) { return source.Add(value); }
public void Clear() { source.Clear(); }
public bool Contains(object value) { return source.Contains(value); }
public void CopyTo(Array array, int index) { source.CopyTo(array, index); }
public IEnumerator GetEnumerator() { return source.GetEnumerator(); }
public int IndexOf(object value) { return source.IndexOf(value); }
public void Insert(int index, object value) { source.Insert(index, value); }
public void Remove(object value) { source.Remove(value); }
public void RemoveAt(int index) { source.RemoveAt(index); }
// ITypedList
public string GetListName(PropertyDescriptor[] listAccessors) { return ListBindingHelper.GetListName(source, listAccessors); }
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
var properties = ListBindingHelper.GetListItemProperties(source, listAccessors);
if (propertyMapper != null)
properties = new PropertyDescriptorCollection(properties.Cast<PropertyDescriptor>()
.Select(propertyMapper).Where(p => p != null).ToArray());
return properties;
}
}
private class BindingList : List, IBindingList
{
private IBindingList source;
public BindingList(IBindingList source, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper) : base(source, propertyMapper) { this.source = source; }
private ListChangedEventHandler listChanged;
public event ListChangedEventHandler ListChanged
{
add
{
var oldHandler = listChanged;
if ((listChanged = oldHandler + value) != null && oldHandler == null)
source.ListChanged += OnListChanged;
}
remove
{
var oldHandler = listChanged;
if ((listChanged = oldHandler - value) == null && oldHandler != null)
source.ListChanged -= OnListChanged;
}
}
private void OnListChanged(object sender, ListChangedEventArgs e)
{
var handler = listChanged;
if (handler != null)
handler(this, e);
}
public bool AllowNew { get { return source.AllowNew; } }
public bool AllowEdit { get { return source.AllowEdit; } }
public bool AllowRemove { get { return source.AllowRemove; } }
public bool SupportsChangeNotification { get { return source.SupportsChangeNotification; } }
public bool SupportsSearching { get { return source.SupportsSearching; } }
public bool SupportsSorting { get { return source.SupportsSorting; } }
public bool IsSorted { get { return source.IsSorted; } }
public PropertyDescriptor SortProperty { get { return source.SortProperty; } }
public ListSortDirection SortDirection { get { return source.SortDirection; } }
public object AddNew() { return source.AddNew(); }
public void AddIndex(PropertyDescriptor property) { source.AddIndex(property); }
public void ApplySort(PropertyDescriptor property, ListSortDirection direction) { source.ApplySort(property, direction); }
public int Find(PropertyDescriptor property, object key) { return source.Find(property, key); }
public void RemoveIndex(PropertyDescriptor property) { source.RemoveIndex(property); }
public void RemoveSort() { source.RemoveSort(); }
}
private class BindingListView : BindingList, IBindingListView
{
private IBindingListView source;
public BindingListView(IBindingListView source, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper) : base(source, propertyMapper) { this.source = source; }
public string Filter { get { return source.Filter; } set { source.Filter = value; } }
public ListSortDescriptionCollection SortDescriptions { get { return source.SortDescriptions; } }
public bool SupportsAdvancedSorting { get { return source.SupportsAdvancedSorting; } }
public bool SupportsFiltering { get { return source.SupportsFiltering; } }
public void ApplySort(ListSortDescriptionCollection sorts) { source.ApplySort(sorts); }
public void RemoveFilter() { source.RemoveFilter(); }
}
}
Actually as you can see, I've added wrappers for other data source interfaces like IBindingList and IBindingListView. Again, the code is not so small, but it's just delegating the calls to the underlying objects (when creating one for your concrete data, you usually would inherit from List<T> or BiundingList<T> and implement only the two ITypedList members). The essential part is the GetItemProperties method implementation which along with the propertyMapper lambda allows you to replace one property with another.
With all that in place, solving the specific problem from the post is simple a matter of wrapping the DataTable and mapping the Complex property to the Complex.Name property:
class ComplexData
{
public int Value { get; set; }
}
class ComplexDataWrapper
{
public string Name { get; set; }
public ComplexData Data { get; set; } = new ComplexData();
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form();
var gridView = new DataGridView { Dock = DockStyle.Fill, Parent = form };
gridView.DataSource = ListDataView.Create(GetData(), null, p =>
{
if (p.PropertyType == typeof(ComplexDataWrapper))
return ChildPropertyDescriptor.Create(p, "Name", "Complex Name");
return p;
});
Application.Run(form);
}
static DataTable GetData()
{
var dt = new DataTable();
dt.Columns.Add("Id", typeof(int));
dt.Columns.Add("Complex", typeof(ComplexDataWrapper));
for (int i = 1; i <= 10; i++)
dt.Rows.Add(i, new ComplexDataWrapper { Name = "Name#" + i, Data = new ComplexData { Value = i } });
return dt;
}
}
To recap, custom PropertyDescriptor and ITypedList allow you to create unlimited types of views of your data, which then can be used by any data bound aware control.
I believe your architecture is flawed for what you're trying to achieve.
If you are using the gridView to edit a single property on your complex type, then there is no need to bind the entire type into the datatable the is the datasource of your grid.
Instead you should bind only the property you wish to edit, and when the the data comes back, simply assign it to the complex type in the right place.
My masters thesis is about discovering bad database design by analyzing metadata and the data stored. We do this by extracting a metadata model from a given DBMS and then running a set of rules on this metadata.
To extend this process with data analysis, we need to allow rules to query the database directly, but we must retain DBMS independence, such that queries can be applied to PostgreSQL, MSSQL and MySQL.
We have discussed a sort of functional construction of queries such as:
new Query(new Select(columnID), new From(tableID), new Where(new Equality(columnID1, columnID2)))
And then using a DBMS-specific serializer.
Another approach is to let rules handle it all by themselves:
public Query QueryDatabase(DBMS dbms)
{
if (dbms == PostgreSQL) { return "select count(1) from Users"}
if (dbms == MSSQL) {return ....}
}
Are we missing something? Does all this in fact exist in a nice library somewhere? And yes, we have looked at Entity frameworks, but they seem to rely on a statically types model of the database, which for obvious reasons cannot be created.
I should mention that we maintain an extensible rule architecture, allowing end users to implement their own rules.
To clarify what we want to achieve, look at the following query (mssql), it needs two parameters, the name of the table (#table) and the name of the column (#column):
DECLARE #TotalCount FLOAT;
SELECT #TotalCount = COUNT(1) FROM [#table];
SELECT SUM(pcount * LOG10(#TotalCount / pcount)) / (LOG10(2) * #TotalCount)
FROM (SELECT (Count([#column])) as pcount
FROM [#table]
GROUP BY [#column]) as exp1
The query measures the amount of information stored in a given attribute, by estimating the entropy. It needs to access all rows in the table. To avoid extracting all rows from the database and transferring them over a slow network connection it is better to express them in SQL an only transfer a single number.
NOTE: We DO have all the metadata we need. This question is only for accessing data!
I was not very sure of whether to add this to my already long question, edit an existing answer or what todo. Please feel free to advise. ;)
Building on mrnye answer:
new Query()
.Variable(varname => FLOAT)
.Set(varname => new Query().Count(1).From(table) )
.Select(new Aggregate().Sum(varname => "pcount * LOG10(varname / pcount)"))
.From(
new Query()
.Select(pcount => new Aggregate().Count(column)
.From(table)
.GroupBy(column)
)
Syntax errors and misuse of lambda statements aside, i played with the idea of using some extension methods for building queries. It does seem as a fairly complex approach. How would you think about such an approach?
Building on the LINQ answer:
let totalCount = Table.Count
from uv un from r in Table
group r by r["attr"]
select r.Count
select r.Count * Log2((totalCount / r.Count))
Seems fairly nice, but a helluva lot to implement...
You could achieve the same by implementing a custom LINQ provider infrastructure. The queries are generic, but the AST tree visitors that generate the SQL queries can be made pluggable. You can even mock a database using a in memory data store and translating your custom LINQ query to a LINQ to objects query!
You would need to create a provider that would know how to extract the column name from the object's indexer. Here is a basic framework that you can extend:
// Runs in LinqPad!
public class TableQueryObject
{
private readonly Dictionary<string, object> _data = new Dictionary<string, object>();
public string TableName { get; set; }
public object this[string column]
{
get { return _data.ContainsKey(column) ? _data[column] : null; }
set { if (_data.ContainsKey(column)) _data[column] = value; else _data.Add(column, value); }
}
}
public interface ITableQuery : IEnumerable<TableQueryObject>
{
string TableName { get; }
string ConnectionString { get; }
Expression Expression { get; }
ITableQueryProvider Provider { get; }
}
public interface ITableQueryProvider
{
ITableQuery Query { get; }
IEnumerable<TableQueryObject> Execute(Expression expression);
}
public interface ITableQueryFactory
{
ITableQuery Query(string tableName);
}
public static class ExtensionMethods
{
class TableQueryContext : ITableQuery
{
private readonly ITableQueryProvider _queryProvider;
private readonly Expression _expression;
public TableQueryContext(ITableQueryProvider queryProvider, Expression expression)
{
_queryProvider = queryProvider;
_expression = expression;
}
public string TableName { get { return _queryProvider.Query.TableName; } }
public string ConnectionString { get { return _queryProvider.Query.ConnectionString; } }
public Expression Expression { get { return _expression; } }
public ITableQueryProvider Provider { get { return _queryProvider; } }
public IEnumerator<TableQueryObject> GetEnumerator() { return Provider.Execute(Expression).GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
public static MethodInfo MakeGeneric(MethodBase method, params Type[] parameters)
{
return ((MethodInfo)method).MakeGenericMethod(parameters);
}
public static Expression StaticCall(MethodInfo method, params Expression[] expressions)
{
return Expression.Call(null, method, expressions);
}
public static ITableQuery CreateQuery(this ITableQueryProvider source, Expression expression)
{
return new TableQueryContext(source, expression);
}
public static IEnumerable<TableQueryObject> Select<TSource>(this ITableQuery source, Expression<Func<TSource, TableQueryObject>> selector)
{
return source.Provider.CreateQuery(StaticCall(MakeGeneric(MethodBase.GetCurrentMethod(), typeof(TSource)), source.Expression, Expression.Quote(selector)));
}
public static ITableQuery Where(this ITableQuery source, Expression<Func<TableQueryObject, bool>> predicate)
{
return source.Provider.CreateQuery(StaticCall((MethodInfo)MethodBase.GetCurrentMethod(), source.Expression, Expression.Quote(predicate)));
}
}
class SqlTableQueryFactory : ITableQueryFactory
{
class SqlTableQuery : ITableQuery
{
private readonly string _tableName;
private readonly string _connectionString;
private readonly ITableQueryProvider _provider;
private readonly Expression _expression;
public SqlTableQuery(string tableName, string connectionString)
{
_connectionString = connectionString;
_tableName = tableName;
_provider = new SqlTableQueryProvider(this);
_expression = Expression.Constant(this);
}
public IEnumerator<TableQueryObject> GetEnumerator() { return Provider.Execute(Expression).GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public string TableName { get { return _tableName; } }
public string ConnectionString { get { return _connectionString; } }
public Expression Expression { get { return _expression; } }
public ITableQueryProvider Provider { get { return _provider; } }
}
class SqlTableQueryProvider : ITableQueryProvider
{
private readonly ITableQuery _query;
public ITableQuery Query { get { return _query; } }
public SqlTableQueryProvider(ITableQuery query) { _query = query; }
public IEnumerable<TableQueryObject> Execute(Expression expression)
{
//var connecitonString = _query.ConnectionString;
//var tableName = _query.TableName;
// TODO visit expression AST (generate any sql dialect you want) and execute resulting sql
// NOTE of course the query can be easily parameterized!
// NOTE here the fun begins, just return some dummy data for now :)
for (int i = 0; i < 100; i++)
{
var obj = new TableQueryObject();
obj["a"] = i;
obj["b"] = "blah " + i;
yield return obj;
}
}
}
private readonly string _connectionString;
public SqlTableQueryFactory(string connectionString) { _connectionString = connectionString; }
public ITableQuery Query(string tableName)
{
return new SqlTableQuery(tableName, _connectionString);
}
}
static void Main()
{
ITableQueryFactory database = new SqlTableQueryFactory("SomeConnectionString");
var result = from row in database.Query("myTbl")
where row["someColumn"] == "1" && row["otherColumn"] == "2"
where row["thirdColumn"] == "2" && row["otherColumn"] == "4"
select row["a"]; // NOTE select executes as linq to objects! FTW
foreach(var a in result)
{
Console.WriteLine(a);
}
}
I think the LINQ route is the way to go, but for fun I tried to think of a solution. It needs some work, but the general idea is to have the query interface fluent and hide the implementation logic behind interfaces. Just throwing it out there as food for thought...
public interface IDBImplementation
{
public void ProcessQuery(Select query);
}
public class SqlServerImplementation : IDBImplementation
{
public void ProcessQuery(Select query)
{
string sqlQuery = "SELECT " + String.Join(", ", query.Columns)
+ " FROM " + query.TableName + " WHERE " + String.Join(" AND ", query.Conditions);
// execute query...
}
}
public class Select
{
public Select(params string[] columns)
{
Columns = columns;
}
public string[] Columns { get; set; }
public string TableName { get; set; }
public string[] Conditions { get; set; }
}
public static class Extensions
{
public static Select From(this Select select, string tableName)
{
select.TableName = tableName;
return select;
}
public static Select Where(this Select select, params string[] conditions)
{
select.Conditions = conditions;
return select;
}
}
public static class Main
{
public static void Example()
{
IDBImplementation database = new SqlServerImplementation();
var query = new Select("a", "b", "c").From("test").Where("c>5", "b<10");
database.ProcessQuery(query);
}
}
The most DBMS-independent way to retrieve information about a database is iNFORMATION_SCHEMA. See MySQL, SQL Server, PostgreSQL.
I'm kind of curious what type of rules you're thinking of. :)
What about NHibernate? http://community.jboss.org/wiki/NHibernateforNET