Type convertsion from string

  • Thread starter Thread starter Andrus
  • Start date Start date
Marc,
Personally, that isn't the way I would approach it. I would use a parser
(like on of those already cited) to find something that I can invoke - but
that doesn't necessarily mean compiling. I would use
TypeDescriptionProvider to add a runtime-only PropertyDescriptor into the
mix, and in the GetValue etc I would invoke the above expression. No need
for compiling; no need for dynamic types - just the types you already
defined in your object model. Just smart use of the
"System.ComponentModel" area. And likewise for additional read/write data
properties; put a property-bag data-store[*1] into the EntityBase object
model (Dictionary<string,object> would do), and somewhere (prefereably
static) to keep the track of the extra properties[*2], and just add in
PropertyDescriptors (from the list in [*2]) whose GetValue and SetValue
talk the the instance's property-bag (in [*1]).
The above is a little more complex than some code, but I would expect
(from experience) it to be a lot more robust than messing with compiling
code (especially user-code) and hooking it all together with reflection.

Using your code and hints I created test case below.

Issues:

1. I need that business objects derive from my base class.
How to change this code so that EntityBase<T> class is derived from object,
not form Bag ?

2. How to add dynamic calculated property which returns "Mr "+name as value?
Property value calculation can be built-in. I'm interested is it possible
to add calculated property at all.

3. Will Castle Activerecord/NHiberante lazy mode support this kind of
business objects ?
Lazy mode uses Castle DynamicProxy to catch all propeperties.

4. How to simpify this code? It looks complicated and messy.

5. In RDL report expressions I need to get dynamic property value from
static method like

class DynamicCustomer {
public static string GetName( string id ) {
return ExecScalar("SELECT name FROM customer WHERE id="+id);
}
}

When compiling I create all such methods and Equals() override dynamically.

How to create this and general Equals() methods when properties are added
using TypeDescriptor ?
To use TypeDescriptor I must create replace all GetXXXXXX (where XXXXXX is
property name ) methods
with method with two parameters like

public static string GetStringProperty( string id, string property )

but this is inconvenient to type in report designer ( I need to use hundreds
of such calls in reports).

6. What are [*1] and [*2] in your message?

7. This causes TypeInitializationException in MONO. How to run it in MONO?

Earlier said:
Note that this is a *simplified* version of such, as it only supports a
single type.
In production code I would expect Bag to act as a base-class, and as such
a few changes would have to be made to identify the correct (current) type
(rather than using typeof(Bag)).

How to chage this code so that correct type is identified ?

Andrus.


using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

static class Program {
[STAThread]
static void Main() {
Customer Customer = new Customer();
Customer.AddProperty<string>("Name");
Customer.SetValue("Name", "Marc");
MessageBox.Show(Customer.GetStringValue("Name"));
}
}

// Runtime extensible business object
public class Customer : EntityBase<Customer> {
string id;

public string Id {
get {
return id;
}
set {
id = value;
}
}
}

// from core library
public abstract class EntityBase<T> : Bag {
public virtual void SetValue(string propertyName, string propertyValue) {
PropertyDescriptor p =
TypeDescriptor.GetProperties(typeof(T))[propertyName];
ITypeDescriptorContext ctx = new SimpleContext(this, p);
object val;
val = p.Converter.ConvertFromString(ctx, propertyValue);
p.SetValue(this, val);
}

public virtual string GetStringValue(string propertyName) {
PropertyDescriptor p =
TypeDescriptor.GetProperties(typeof(T))[propertyName];
return p.GetValue(this).ToString();
}
}

interface IBag {
void OnAfterValueChanged(string propertyName);
void AddHandler(object key, EventHandler value);
void RemoveHandler(object key, EventHandler value);
void OnEvent(object key);
IBagValue GetValue(string propertyName);
}

interface IBagValue {
void ResetValue();
bool IsDefaultValue { get;}
object Value { get; set;}
event EventHandler ValueChanged;
}

interface IBagDefinition {
IBagValue Create(IBag bag);
PropertyDescriptor Property { get;}
}

sealed class BagDefinition<T> : IBagDefinition {
private readonly PropertyDescriptor property;
public PropertyDescriptor Property { get { return property; } }
private readonly T defaultValue;
public T DefaultValue { get { return defaultValue; } }
public string Name { get { return Property.Name; } }

IBagValue IBagDefinition.Create(IBag bag) {
return new BagValue<T>(bag, this);
}

public BagDefinition(string propertyName, Attribute[] attributes) {
defaultValue = default(T);
if (attributes != null) { // check for a default value
foreach (Attribute attrib in attributes) {
DefaultValueAttribute defAttrib = attrib as
DefaultValueAttribute;
if (defAttrib != null) {
defaultValue = (T)defAttrib.Value;
break;
}
}
}
property = new BagPropertyDescriptor(propertyName, attributes);
}


internal class BagPropertyDescriptor : PropertyDescriptor {
public BagPropertyDescriptor(string name, Attribute[] attributes)
: base(name, attributes) { }


private IBagValue GetBagValue(object component) {
return ((IBag)component).GetValue(Name);
}


public override object GetValue(object component) {
return GetBagValue(component).Value;
}


public override void SetValue(object component, object value) {
GetBagValue(component).Value = value;
}


public override Type ComponentType {
get { return typeof(Bag); }
}


public override Type PropertyType {
get { return typeof(T); }
}


public override bool IsReadOnly {
get { return false; }
}


public override bool CanResetValue(object component) {
return true;
}


public override void ResetValue(object component) {
GetBagValue(component).ResetValue();
}


public override bool ShouldSerializeValue(object component) {
return !GetBagValue(component).IsDefaultValue;
}
public override bool SupportsChangeEvents {
get { return true; }
}


public override void AddValueChanged(object component,
EventHandler handler) {
GetBagValue(component).ValueChanged += handler;
}

public override void RemoveValueChanged(object component,
EventHandler handler) {
GetBagValue(component).ValueChanged -= handler;
}
}
}


sealed class BagValue<T> : IBagValue, ITypeDescriptorContext {
private T value;
private readonly IBag bag;
void IBagValue.ResetValue() {
Value = Definition.DefaultValue;
}
bool IBagValue.IsDefaultValue {
get {
return EqualityComparer<T>.Default.Equals(Value,
Definition.DefaultValue);
}
}
private readonly BagDefinition<T> definition;
public IBag Bag { get { return bag; } }
public BagDefinition<T> Definition { get { return definition; } }
public BagValue(IBag bag, BagDefinition<T> definition) {
if (bag == null) throw new ArgumentNullException("bag");
if (definition == null) throw new ArgumentNullException("definition");
this.bag = bag;
this.definition = definition;
Value = Definition.DefaultValue;
}

public T Value {
get { return value; }
set {
if (EqualityComparer<T>.Default.Equals(Value, value))
return;
this.value = value;
Bag.OnAfterValueChanged(Definition.Name);
Bag.OnEvent(Definition);
}
}


public event EventHandler ValueChanged {
add { Bag.AddHandler(Definition, value); }
remove { Bag.RemoveHandler(Definition, value); }
}


object IBagValue.Value {
get { return Value; }
set { Value = (T)value; }
}


IContainer ITypeDescriptorContext.Container {
get {
return null;
}
}
object ITypeDescriptorContext.Instance { get { return Bag; } }
void ITypeDescriptorContext.OnComponentChanged() {
Bag.OnAfterValueChanged(Definition.Name);
}
bool ITypeDescriptorContext.OnComponentChanging() { return true; }
PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor {
get { return Definition.Property; }
}
object IServiceProvider.GetService(Type serviceType) {
return null;
}
}


public /*sealed*/ class Bag : IBag, INotifyPropertyChanged {

private EventHandlerList events;
void IBag.AddHandler(object key, EventHandler handler) {
AddHandler(key, handler);
}
void IBag.RemoveHandler(object key, EventHandler handler) {
RemoveHandler(key, handler);
}
void IBag.OnEvent(object key) {
OnEvent(key);
}
private void AddHandler(object key, Delegate handler) {
if (handler == null) return;
if (events == null) events = new EventHandlerList();
events.AddHandler(key, handler);
}
private void RemoveHandler(object key, Delegate handler) {
if (events == null || handler == null) return;
events.RemoveHandler(key, handler);
}
private void OnEvent(object key) {
if (events == null) return;
EventHandler handler = events[key] as EventHandler;
if (handler != null) handler(this, EventArgs.Empty);
}
public event PropertyChangedEventHandler PropertyChanged {
add { AddHandler(EVENT_PropertyChanged, value); }
remove { RemoveHandler(EVENT_PropertyChanged, value); }
}
private static readonly object EVENT_PropertyChanged = new object();
private void OnPropertyChanged(string propertyName) {
if (events == null) return;
PropertyChangedEventHandler handler = events[EVENT_PropertyChanged] as
PropertyChangedEventHandler;
if (handler != null) handler(this, new
PropertyChangedEventArgs(propertyName));
}


void IBag.OnAfterValueChanged(string propertyName) {
OnPropertyChanged(propertyName);
}


IBagValue IBag.GetValue(string propertyName) {
return GetValue(propertyName);
}


private readonly Dictionary<string, IBagValue> values =
new Dictionary<string, IBagValue>(StringComparer.InvariantCulture);


private IBagValue GetValue(string propertyName) {
lock (values) {
IBagValue value;
if (!values.TryGetValue(propertyName, out value)) {
value = CreateValue(this, propertyName);
values.Add(propertyName, value);
}
return value;
}
}


static readonly Dictionary<string, IBagDefinition> defintions =
new Dictionary<string,
IBagDefinition>(StringComparer.InvariantCulture);


public static PropertyDescriptor AddProperty<T>(string propertyName,
params Attribute[] attributes) {
BagDefinition<T> def = new BagDefinition<T>(propertyName, attributes);
lock (defintions) {
defintions.Add(propertyName, def);
BagDescriptionProvider.ResetProperties();
}
return def.Property;
}


internal static PropertyDescriptorCollection GetProperties() {
lock (defintions) {
PropertyDescriptor[] props = new PropertyDescriptor[defintions.Count];
int i = 0;
foreach (IBagDefinition def in defintions.Values) {
props[i++] = def.Property;
}
return new PhantomPropertyDescriptorCollection(props, false);
}
}


internal sealed class PhantomPropertyDescriptorCollection :
PropertyDescriptorCollection {


public PhantomPropertyDescriptorCollection(PropertyDescriptor[]
properties, bool readOnly)
: base(properties, readOnly) {
}


public override PropertyDescriptor Find(string name, bool ignoreCase) {
PropertyDescriptor prop = base.Find(name, ignoreCase);
if (prop == null) {
prop = AddProperty<object>(name);
Add(prop);
}
return prop;
}
}


static IBagValue CreateValue(IBag bag, string propertyName) {
lock (defintions) {
return defintions[propertyName].Create(bag);
}
}


public Bag() { }

static Bag() {
BagDescriptionProvider.Initialize();
}
}


sealed class BagDescriptionProvider : TypeDescriptionProvider {
static readonly BagTypeDescriptor descriptor;

[MethodImpl(MethodImplOptions.NoInlining)]
internal static void Initialize() { } // to force static ctor
static BagDescriptionProvider() {
ICustomTypeDescriptor parent =
TypeDescriptor.GetProvider(typeof(Bag)).GetTypeDescriptor(typeof(Bag));
descriptor = new BagTypeDescriptor(parent);
TypeDescriptor.AddProvider(new BagDescriptionProvider(), typeof(Bag));
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType,
object instance) {
return descriptor;
}


internal static void ResetProperties() {
descriptor.ResetProperties();
}
}


sealed class BagTypeDescriptor : CustomTypeDescriptor {
public BagTypeDescriptor(ICustomTypeDescriptor parent)
: base(parent) {
if (parent == null) throw new ArgumentNullException("parent");
}
private PropertyDescriptorCollection properties;
internal void ResetProperties() {
properties = null;
}
public override PropertyDescriptorCollection GetProperties(Attribute[]
attributes) {
return GetProperties();
}
public override PropertyDescriptorCollection GetProperties() {
if (properties == null) {
properties = Bag.GetProperties();
}
return properties;
}
}




[ImmutableObject(true)]
class SimpleContext : ITypeDescriptorContext {
readonly object instance;
readonly PropertyDescriptor property;

internal SimpleContext(object instance, PropertyDescriptor
property) {
this.instance = instance;
this.property = property;
}

public IContainer Container { get { return null; } }
public object Instance { get { return instance; } }
public void OnComponentChanged() { }
public bool OnComponentChanging() { return true; }

public PropertyDescriptor PropertyDescriptor {
get {
return property;
}
}

public object GetService(Type serviceType) { return null; }
}
 
If you are only referring to regular value properties (a user-defined
DateTime called "DateOfBirth", etc), then you can do this without all the
messing. Putting together a runtime-extensible (property-bag) class isn't
actually all that hard. I have posted examples to this forum several
times. TypeDescriptionProvider is probably the way to go in such a case,
but it can't handle text formulae unless you bolt a parser onto the end.

I realized that for ActiveRecord I need to add propertites to type, not to
object.

How to add property to type at runtime or create new type which contains new
regular value property defined at runtime ?
As I know this is not possible without dynamically compiling.

Andrus.
 
1: the main purpose of Bag here was to provide something that the
propdesc could talk to; an interface would serve just as well; can
provide example [see later]
2: by using an alternative PropertyDescriptor subclass; can provide
example [see later]
3,5,7: I haven no idea; not really used any of them much...
6: sorry; I was unclear; the first reference to each should have
served as a label, the second reference to each should have served as
a back reference, but *1 was somehow to put the values, and *2 was
somehow to track the properties

---
How to add property to type at runtime
I'll assume (from previous) that this means static? Pretty much can't
AFAIK
or create new type which contains new
regular value property defined at runtime ?
It would have to be Reflection.Emit or dynamic compilation (compiler,
code-dom, etc)

In 3,5,7 and "static" you've added needs that weren't mentioned
originally. I maintain that runtime properties are a very good way to
go *in general*, and most of the native .NET code (including winform/
wpf binding, etc) will copy perfectly with this. However, given the
external factors (mono being a biggie!) I can see how keeping it as
simple as possible would be desirable. I was just trying to offer my
best advice on the (limited) information provided at the time. If you
have different needs, or you simply don't agree (which is fine), then
"compile away". But I genuinely would advise you to parse and white-
list input... I don't know how isolated each user is from each other,
but if user A can create a formula that user B then uses, is similar
to an xss risk? If user A only affects user A then less of a problem.

Anyway, I can (if you want) provide examples for 1, 2 and hopefully 4,
but I don't intend going to the trouble unless you say so - as it
sounds like you may have overriding compatibility issues with this
approach. In which case, sorry to have wasted your time. Better to
have considered both though, I guess!

Marc
 
OK; I realise (last post) that this approach might not be suitable
(due to your "static" and "mono" requirements) - however, for the
benefit of the list, here is some sample code which:
* allows runtime (instance) properties to be added to a Type
* supports the idea of user-written formula properties
* does not have any base-class dependencies

Note that I have not fully implemented the "formula" logic, since this
would ideally use a parser to do the eval. I tried to bolt in
"DynamicQuery" (VS2008 sample), but this wouldn't compile with the
latest Orcas build. I cited several other such parsers in an earlier
post in this chain, but for now I left it just writing something
involving the "Name" property that exists in the example. But you get
the idea...

I haven't added many comments. Time pressures... long(ish) code
warning...

Marc


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

static class Program {
static void Main() {
// example setup (app startup, for example)
Extensible.Add<Customer,DateTime>("DateOfBirth",DateTime.MinValue);
Extensible.Add<Customer, int?>("ShoeSize", null);
Extensible.Add<Customer, bool>("Enabled", true);
Extensible.Add<Customer>("Formula", "Boo!"); // not fully
implemented!

// example UI showing working without knowledge
Customer c = new Customer();
BindingList<Customer> customers = new BindingList<Customer>();
customers.Add(c);
Application.EnableVisualStyles();
using(Form f = new Form())
using (PropertyGrid pg = new PropertyGrid())
using (DataGridView dgv = new DataGridView()) {
pg.Dock = DockStyle.Right;
dgv.Dock = DockStyle.Fill;
f.Controls.Add(pg);
f.Controls.Add(dgv);
dgv.DataSource = customers;
pg.SelectedObject = c;
Application.Run(f);
}
}
}

// example entity; in this case, *happens* to also
// implement INotifyPropertyChanged; nice, but not
// a requirement
class Customer : IExtensible, INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
// regular
string name = "abc";
public string Name {
get { return name; }
set { name = value; OnPropertyChanged(name); }
}
// support for extended
private Dictionary<string, object> values;
bool IExtensible.TryGetPropertyValue(string propertyName, out
object value) {
if (values != null && values.TryGetValue(propertyName, out
value)) {
return true;
}
value = null;
return false;
}
void IExtensible.SetPropertyValue(string propertyName, object
value) {
if (values == null) values = new Dictionary<string, object>();
values[propertyName] = value;
OnPropertyChanged(propertyName);
}
protected void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new
PropertyChangedEventArgs(propertyName));
}
}

// ----------------------------------------
// SEPARATE CODE AREA (base libs)
// (perhaps a separate assembly)

interface IExtensible { // untyped, but methods could be generic
bool TryGetPropertyValue(string propertyName, out object value);
void SetPropertyValue(string propertyName, object value);
}

static class Extensible {
public static void Remove<T>(string name) where T : IExtensible {
ExtensibleInner<T>.Remove(name);
}
public static void Add<TEntity, TValue>(string name, TValue
defaultValue,
params Attribute[] attributes)
where TEntity : IExtensible {
ExtensibleInner<TEntity>.Add<TValue>(name, defaultValue,
attributes);
}
public static void Add<T>(string name, string expression, params
Attribute[] attributes)
where T : IExtensible {
ExtensibleInner<T>.Add(name, expression, attributes);
}

static class ExtensibleInner<T> where T : IExtensible {
static readonly SortedDictionary<string, PropertyDescriptor>
props
= new SortedDictionary<string, PropertyDescriptor>
(StringComparer.InvariantCultureIgnoreCase);
internal static void Remove(string name) {
props.Remove(name);
propsCollection = null; // reset cache
}
internal static void Add<TValue>(string name, TValue
defaultValue, Attribute[] attributes) {
GenericPropertyDescriptor<TValue> prop
= new GenericPropertyDescriptor<TValue>(
name, defaultValue, attributes);
props.Add(prop.Name, prop);
propsCollection = null; // reset cache
}
internal static void Add(string name, string expression,
Attribute[] attributes) {
ExpressionPropertyDescriptor prop
= new ExpressionPropertyDescriptor(name, expression,
attributes);
props.Add(prop.Name, prop);
propsCollection = null; // reset cache
}

static PropertyDescriptorCollection propsCollection;
static PropertyDescriptorCollection PropertyCollection {
get {
if (propsCollection == null) {
PropertyDescriptor[] propArray = new
PropertyDescriptor[props.Count];
props.Values.CopyTo(propArray, 0);
propsCollection = new
PropertyDescriptorCollection(propArray, true);
}
return propsCollection;
}
}

static ExtensibleInner() {
PropertyDescriptorCollection oldProps =
TypeDescriptor.GetProperties(typeof(T));
foreach (PropertyDescriptor oldProp in oldProps) {
props.Add(oldProp.Name, oldProp);
}
TypeDescriptionProvider provider =
TypeDescriptor.GetProvider(typeof(T));
provider = new
ExtensibleTypeDescriptionProvider(provider);
TypeDescriptor.AddProvider(provider, typeof(T));
}
sealed class ExtensibleTypeDescriptor : CustomTypeDescriptor {
public ExtensibleTypeDescriptor(ICustomTypeDescriptor
parent)
: base(parent) { }
public override PropertyDescriptorCollection
GetProperties(Attribute[] attributes) {
return GetProperties(); // TODO filter
}
public override PropertyDescriptorCollection
GetProperties() {
return PropertyCollection;
}
}
sealed class ExpressionPropertyDescriptor : PropertyDescriptor
{
readonly string Expression;
internal ExpressionPropertyDescriptor(string name, string
expression, Attribute[] attributes)
: base(name, attributes) {
// TODO: parse, and identify which other properties
are needed as parameters
Expression = expression;
}
public override Type ComponentType {
get { return typeof(T); }
}
public override Type PropertyType {
get { return typeof(object); }
}
public override bool CanResetValue(object component) {
return false;
}
public override void ResetValue(object component) {
throw new NotImplementedException();
}
public override bool IsReadOnly {
get { return true; }
}
public override void SetValue(object component, object
value) {
throw new NotImplementedException();
}
public override bool ShouldSerializeValue(object
component) {
return false; // assume only serialize data, not
expression results
}
// helper method for getting ad-hoc property values (used
by expression code)
static object GetValue(string propertyName, object
component) {
return
PropertyCollection[propertyName].GetValue(component);
}
public override object GetValue(object component) {
// TODO: invoke expression (may involve
// other properties, i.e. GetValue(otherProperty,
component)
object name = GetValue("Name", component);
return string.Format("(EVAL) {0}", name);
}
}
sealed class GenericPropertyDescriptor<TValue> :
PropertyDescriptor {
readonly TValue DefaultValue;
internal GenericPropertyDescriptor(string name, TValue
defaultValue, Attribute[] attributes)
: base(name, attributes) {
DefaultValue = defaultValue;
}
public override bool IsReadOnly {
get { return false; }
}
public override Type ComponentType {
get { return typeof(T); }
}
public override Type PropertyType {
get { return typeof(TValue); }
}
public override object GetValue(object component) {
object value;
if
(!((IExtensible)component).TryGetPropertyValue(Name, out value)) {
value = DefaultValue;
}
return value;
}
public override void SetValue(object component, object
value) {
TValue typedVal = (TValue)value; // to enforce
type-safety
((IExtensible)component).SetPropertyValue(Name,
typedVal);
}
public override bool ShouldSerializeValue(object
component) {
return !Equals(GetValue(component), DefaultValue);
}
public override void ResetValue(object component) {
((IExtensible)component).SetPropertyValue(Name,
DefaultValue);
}
public override bool CanResetValue(object component) {
return true;
}
}
sealed class ExtensibleTypeDescriptionProvider :
TypeDescriptionProvider {
readonly ICustomTypeDescriptor descriptor;
internal
ExtensibleTypeDescriptionProvider(TypeDescriptionProvider parent)
: base(parent) {
descriptor = new
ExtensibleTypeDescriptor(base.GetTypeDescriptor(typeof(T)));
}
public override ICustomTypeDescriptor
GetTypeDescriptor(Type objectType, object instance) {
return descriptor;
}
}
}
}
 
Oh, and note that I did not bother making the static Add etc
thread-safe; again, time-pressure - but in most scenarios you'd be
likely to configure this all at startup, anyway - so not *necessarily*
an urgent risk.

Marc
 
OK; I realise (last post) that this approach might not be suitable (due to
your "static" and "mono" requirements) - however, for the benefit of the
list, here is some sample code which:
* allows runtime (instance) properties to be added to a Type
* supports the idea of user-written formula properties
* does not have any base-class dependencies

thank you. Excellent.

I tried your code with Castle ActiveRecord (AR) but AR does not
read dynamically added propertes from database for unknown reason.
Grid shows only default property value.

AR requires some attributes before each property like

[Castle.ActiveRecord.Property(Column = "name", NotNull = true, Length = 80)]
public virtual string Name {
get { return name; }
set {
name= value;
NotifyPropertyChanged("Name");
}
}

How to add also properties specified in line

[Castle.ActiveRecord.Property(Column = "name", NotNull = true, Length = 80)]

when adding new property using

Extensible.Add<Customer, string>("Name", "" );

?

2. Why you used using commands in Main() ?
There is no need to dispose winforms controls in deterministics way.

Andrus.
 
1:
AR requires some attributes before each property like
I'm not familiar with AR, but I left a "params Attribute[] attributes"
for exactly this... just add (note you need more lines to give what
the compiler does for you):

Castle.ActiveRecord.PropertyAttribute attrib = new
Castle.ActiveRecord.PropertyAttribute();
attrib.Column = "name";
attrib.NotNull = true;
attrib.Length = 80;
Extensible.Add<Customer, string>("Name", "", attrib); // can add other
attributes as needed

But I can't guarantee that it will work!

2:
Why you used using commands in Main() ?
There is no need to dispose winforms controls in deterministics way.
If I had used .ShowDialog() there would be ;-p
Given that I know they won't live beyond the scope. I see no harm
in .Dispose()ing them.

Marc
 
Marc,
Castle.ActiveRecord.PropertyAttribute attrib = new
Castle.ActiveRecord.PropertyAttribute();
attrib.Column = "name";
attrib.NotNull = true;
attrib.Length = 80;
Extensible.Add<Customer, string>("Name", "", attrib); // can add other
attributes as needed

But I can't guarantee that it will work!

I added those lines but grid still shows only default value in Name column.
I got the following reply in MONO mailing list from Robert Jordan:

TypeDescriptors and properties added to types at runtime are highly
unrelated features. You don't really add properties to types using
TypeDescriptors. Properties added this way are only visible to UI Designers
and other components that explicitly deal with TypeDescriptors (like
PropertyGrid, IIRC).

If you really meant this kind of extension, you could implement
ICustomTypeDescriptor. It's slightly more work than using the new
TypeDescriptionProvider, but it's at least possible.

I see the following possibilites:

1. Switch back to dynamic compiling.
2. Implement ICustomTypeDescriptor.
3. ActiveRecord+NHibernate is open source. Try to debug it and find what is
happening. Unfortunately this is large amout of sophisticated sode.

What to do ?

Andrus.
 
Atually, TypeDescriptionProvider is the 2.0 *extension* to
ICustomTypeDescriptor, allowing for separation of concerns between the
entity class and the class dealing with how to represent it - and
allowing for type-centric (rather than instance-centric) rules. Any
code that supports ICustomTypeDescriptor would *typically* be using
TypeDescriptor.GetProperties(), which supports both
ICustomTypeDescriptor and TypeDesriptionProvider (and indeed, regular
reflection).

If mono has an implementation that works for ICustomTypeDescriptor,
but fails for TypeDescriptionProvider, then I suggest that the mono
support for TypeDescriptionProvider is flawed, as it has failed in its
primary aim.

Generally, in (non mono) .NET code, the core binding code worries
about all of this for you - in particular via the route:

BindingContext -> BindingManagerBase -> GetItemProperties()

Equally, the behind-the-scenes binding code deals with *other* models,
such as ITypedList (which *also* has a GetItemProperties() method).
And the consumer shouldn't have to worry themselves about any of it.

PropertyGrid (which you cited) is an exception to most rules, in that
(for building the tree) it is more worried about what the
TypeConverter has to say (via GetProperties()) - but note that for
simple implementations (like ExandableObjectConverter) it simply uses
TypeDescriptor.GetProperties(), so again the ICustomTypeDescriptor and
TypeDescriptionProvider rules are observed.

But in the end, the rub is : if it doesn't work for you, don't use it!
I was simply trying to highlight that there *are* other ways of
working that /typically/ work very well. But not in every situation.

As per an earlier post:
[q] If you have different needs, or you simply don't agree (which is
fine), then "compile away".[/q]

Marc
 
For reference, I lookied into ActiveRecord; ActiveRecordModelBuilder
works on Type.GetProperties(BindingFlags) and uses PropertyInfo
throughout including the various public APIs; so no - it isn't going
to support this type of usage.

Perhaps a shame - it could possibly be both significantly quicker for
bulk data access, and more versatile (runtime extensible) if it used
TypeDescriptor.GetProperties(Type), using PropertyDescriptor in place
of PropertyInfo. Oh well; On the other hand, I can see how it fits
stricter OO, and probably has better mono compatibility if you rule
out runtime meta-properties. Shucks.

Marc
 
I also looked at NHibernate; same thing, so it is consistent. It also
has the common sense not to directly invoke the PropertyInfo, but to
emit IL for the same (which is what I meant when mentioning
performance); so it does a good job.

So yep, definitely won't work with meta-properties. If you want your
custom properties to go down to the database as extra fields you'll
need to compile on-the-fly. And I learnt something in the process, so
I know a few more limits of [ab]using the System.ComponentModel. I
also know that LINQ works in the same way as NHibernate, so it is an
important point (mainly for me).

I hope your dynamic compile strategy works well. I'd be interested to
know how it goes; as you can see, dynamic properties is a luxury I am
used to. If I want to play with the new tools (LINQ etc) then I'm
going to have to re-learn a few things. I might as well start now...

Marc
 
Marc,
I hope your dynamic compile strategy works well. I'd be interested to know
how it goes; as you can see, dynamic properties is a luxury I am used to.
If I want to play with the new tools (LINQ etc) then I'm going to have to
re-learn a few things. I might as well start now...

I tried to use dyamic compile trick but nhibernate still loads and uses old
assembly.

I described this issue in this newsgroup in thread "Duplicate assembly
loading"
(summary posted below again) .

This is .NET fundamental issue and I havent found any solution for it.

Do you have any idea how to use dynamic compiling to create dynamic
properties on types?

Or is there any ORM tool which supports TypeDescriptor ?

Andrus.

My post was:

..NET 2 WinForms application.

In need to load dynamically generated assembly from isolated storage or from
temp directory due to Vista UAC.

In VS 2005 IDE I stepped over two lines:

Assembly activeRecordAssembly =
Assembly.LoadFrom(@"c:\temp\ModelEntity.dll");

ActiveRecordStarter.Initialize(activeRecordAssembly, source);

VS output window after first line is OK:

'Myapp.vshost.exe' (Managed): Loaded 'c:\temp\ModelEntity.dll', No symbols
loaded.

After stepping over second line Output windows shows that assembly is loaded
again! :

'Myapp.vshost.exe' (Managed): Loaded
'I:\raamat\Myapp\bin\Debug\ModelEntity.dll', No symbols loaded.

How to prevent ModelEntity.dll duplicate loading ?
It loads wrong assembly. I need that first loaded assembly is used or
assembly is loaded from temp directory.

How to force .net to look into c:\temp directory first for assembly ?
How to load this assembly from isolated storage ?

Unanswered message:

ActiveRecord dynamically builds an NHibernate mapping definition.

Part of that NHibernate mapping is the type information. It's expressed in
the usual .NET manner:

fully.qualified.typename, assemblyname

What's happening is that ActiveRecord is building up a mapping for one of
types inside ModelEntity.DLL and defining it like this:

"Namespace.ModelEntity.ClassName, ModelEntity"

(this is assuming "ModelEntity" is the name of the assembly). As you can
see, there's no path information in there (by definition) so I'm guessing
when this information is passed to NHibernate, it loads some information
using the fully qualified type information above and then normal .NET rules
for locating assemblies happens (current directory, then probing paths).

How to force .NET to search c:\temp directory first for assemblies ?
 
How to force .NET to search c:\temp directory first for assemblies ?

I suspect the problem is that it already thinks it is loaded... this
why I was suggesting (a few posts back) subclassing, so your core
assembly knows about the abstract Customer, and the dynamic assembly
knows about the concrete CustomerImp : Customer; we then end up back
in the scenario of getting the right instances (i.e. new CustomerImp,
not Customer) - which is where we started this conversation ;-p As
long as NHibernate sees the CustomerImp type and instances, then it
should be happy.

As an aside, and an alternative to loading the assembly, another
option would be to Emit it. I haven't tried it yet (it is on my list
to investigate), but RunSharp might be an option:
http://www.codeproject.com/useritems/runsharp.asp - a bit higher level
that IL, but (presumably) enough to declare a Type that subclasses
another and add a few simple field/property pairs with attributes. The
formula remains an issue, and again it would come down to how you want
to handle them. Personally I'd still be tempted to run a parser (for
security)...
The advantage of Reflection.Emit (I can't speak for RunSharp yet) is
that you get a Type directly at the end. Then switch into a generic
method for that type (as per an earlier post) and you can "new T()"
and job done - essentially using the generics framework to provide a
factory. Or you could use a regular factory pattern.

I will look into RunSharp when I get some time... of course, building
a C# string or a codedom model would also presumably work...

Marc
 
Here is a working-ish implementation of genuine dynamic Type
generation using Run#; I tweaked Complete() to return the generated
Type, and it *really* needs support for attributes (I will feed both
of these back to the author), but note that it appears to offer good
operator support, which might make wrting a formula parser a complete
breeze. Note I only demonstrate properties here, but you get the
idea...
Worth considering at least...

Marc

public abstract class Customer {
private DateTime _dateOfBirth;
public DateTime DateOfBirth {
get { return _dateOfBirth; }
set { _dateOfBirth = value; }
}
delegate T Constructor<T>();
static Constructor<Customer> ctor;
public static void CreateHandler(object sender,
AddingNewEventArgs e) {
e.NewObject = Create();
}
public static void SetConcreteType(Type type) {
MethodInfo mi =
typeof(Customer).GetMethod("SetConcreteType", BindingFlags.Static |
BindingFlags.Public,
null, Type.EmptyTypes, null);
mi = mi.MakeGenericMethod(type);
mi.Invoke(null, null);

}
public static void SetConcreteType<T>() where T : Customer,
new() {
ctor = delegate { return new T(); };
}
public static Customer Create() {
return ctor();
}
}
class Program {
static void Main(string[] args) {
AssemblyGen asm = new
AssemblyGen("DynamicImp.dll");
TypeGen type = asm.Public.Class("CustomerImp",
typeof(Customer));
FieldGen field = type.Private.Field(typeof(string),
"_name");
PropertyGen prop = type.Public.SimpleProperty(field,
"Name");
Customer.SetConcreteType(type.Complete());

// note: if you want the dgv to show "Name" in the
metadata, then
// need to use generics trick (from previous) to switch
into
// a SomeMethod<T>() where T : Customer [for
T=CustomerImp]
// (in which case, you also don't need the AddingNew
handler)
BindingList<Customer> bl = new
BindingList<Customer>();
bl.AddingNew += Customer.CreateHandler;
Customer demo = bl.AddNew();
using (Form f = new Form())
using (DataGridView dgv = new DataGridView())
using (PropertyGrid pg = new PropertyGrid()) {
pg.Dock = DockStyle.Right;
dgv.Dock = DockStyle.Fill;
dgv.DataSource = bl;
pg.SelectedObject = demo;
f.Controls.AddRange(new Control[] {pg, dgv});
Application.Run(f);
}
}
}
 
Note you can also use something like following, and then your main UI
code doesn't need to worry about the flip between Customer and
CustomerImp; it is all handled under the covers (and note that the
reflection *only* happens in the SetConcreteType method; the rest of
the code is regular calls):

[Customer]
static Constructor<Customer> ctor;
static Constructor<IBindingList> bindingListCtor;
static Constructor<IList> listCtor;
public static void SetConcreteType<T>() where T : Customer,
new() {
ctor = delegate { return new T(); };
listCtor = delegate { return new List<T>(); };
bindingListCtor = delegate { return new
BindingList<T>(); };
}
public static IBindingList CreateBindingList() {
return bindingListCtor();
}
private static IList CreateList<T>() {
return listCtor();
}

[Main]
IBindingList list = Customer.CreateBindingList();
Customer c = Customer.Create();
list.Add(c);
 
Marc,
Note you can also use something like following, and then your main UI
code doesn't need to worry about the flip between Customer and
CustomerImp; it is all handled under the covers (and note that the
reflection *only* happens in the SetConcreteType method; the rest of
the code is regular calls):

Thank you.
ActiveRecord uses calls to type static method to return list, e.q

Array CustomerList = Customer.FindAll();

Where Customer is customer type, not object.
How to use your sample with types, with ActiveRecord ?
I tried to test your hints by creating sample code below but got error in

ActiveRecordStarter.RegisterTypes(extendedType);

persistent class ExtendedCustomer, ql1jodz0 not found

How to fix ?

Andrus.

Main program:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Castle.ActiveRecord;
using System.Collections;
using Castle.ActiveRecord.Framework.Config;
using System.Reflection;
using System.CodeDom.Compiler;
using ModelEntity;

static class Program {
static void Main() {

Hashtable properties = new Hashtable();
properties.Add("hibernate.connection.driver_class",
"NHibernate.Driver.NpgsqlDriver");
properties.Add("hibernate.dialect",
"NHibernate.Dialect.PostgreSQL81Dialect");

properties.Add("hibernate.connection.provider",
"NHibernate.Connection.DriverConnectionProvider");

properties.Add("hibernate.default_schema", "public");
properties.Add("hibernate.connection.connection_string",
"Encoding=UNICODE;Server=localhost;CommandTimeout=60;Database=eeva;User
Id=admin;Password=pa");

properties.Add("hibernate.cache.provider_class",
"NHibernate.Caches.SysCache.SysCacheProvider,
NHibernate.Caches.SysCache");

InPlaceConfigurationSource source = new InPlaceConfigurationSource();
source.Add(typeof(ActiveRecordBase), properties);

ActiveRecordStarter.Initialize(source);
Customer.RegisterType();

Array customers = Customer.GetFilteredList();
Form f = new Form();
DataGridView dgv = new DataGridView();
dgv.Dock = DockStyle.Fill;
f.Controls.Add(dgv);
dgv.DataSource = customers;
Application.Run(f);
}
}

ModelEntity.dll source:

using Castle.ActiveRecord;
using System.CodeDom.Compiler;
using System;

namespace ModelEntity {
[ActiveRecord("klient", Schema = "firma1", DynamicUpdate = true,
DynamicInsert = true, Lazy = true, Cache = CacheEnum.NonStrictReadWrite)]
public class Customer : ModelGenericBase<Customer> {

string kood;
[PrimaryKey("kood", Length = 12)]
public virtual string Kood {
get { return kood; }
set {
kood = value;
}
}
}

public abstract class ModelGenericBase<T> :
ActiveRecordValidationBase<T> where T : class {

static Type extendedType;

public static Array GetFilteredList() {
return FindAll(extendedType);
}


public static void RegisterType() {
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters compilerParameters = new CompilerParameters();
compilerParameters.ReferencedAssemblies.Add("Castle.ActiveRecord.dll");
compilerParameters.ReferencedAssemblies.Add("ModelEntity.dll");
compilerParameters.GenerateInMemory = true;
CompilerResults compilerResults =
provider.CompileAssemblyFromSource(compilerParameters,
@"
using Castle.ActiveRecord;
using ModelEntity;

[ActiveRecord(""klient"", Schema = ""firma1"", DynamicUpdate = true,
DynamicInsert = true, Lazy = true)]
public class ExtendedCustomer:Customer {

string name;
[Property]
public virtual string Name {
get { return name; }
set {
name = value;
}
}
}
");

if (compilerResults.Errors.HasErrors) {
string msg;
msg = compilerResults.Errors.Count.ToString() + " Errors:";

for (int x = 0; x < compilerResults.Errors.Count; x++)
msg = msg + "\r\nLine: " +
compilerResults.Errors[x].Line.ToString() + " - " +
compilerResults.Errors[x].ErrorText;
throw new ApplicationException(msg);
}
extendedType =
compilerResults.CompiledAssembly.GetType("ExtendedCustomer");

// inner exception:
//{"persistent class ExtendedCustomer, ql1jodz0 not found"}
ActiveRecordStarter.RegisterTypes(extendedType);
}
}
}
 
persistent class ExtendedCustomer, ql1jodz0 not found
How to fix ?

This sounds like an NHibernate issue finding the mappings (which I
presume the ActiveRecord attribute should fix), and I simply know nada
in that area. Sorry.
Array CustomerList = Customer.FindAll();
It might end up looking for ExtendedCustomer.FindAll(), which it may-
or-may-not find.

I guess at the end of the day your requirement (to add dynamic
properties to the entity) is very hard to reconcile with a
"regular" (fixed) entity model. I'm trying to think of ways out of the
hole, but:
* runtime properties - not supported by ActiveRecord / NHibernate
* subclassing - in theory should work; currently seeing an NHibernate
issue [probably fixable with NHibernate knowledge], but might also be
hard to get the FindAll() etc working unless you do this on the
subclass (perhaps forwarding to a private method on the base-class)

If neither of those works, then I guess you could compile the entire
Customer type itself at runtime, but gives you *no* compile-time use
of Customer in your main app; would be tortuous to write your app.

Other than that - you simply might not be able to model the extra data
this way, but rather may be forced to use a name/value list
(dictionary) hanging off the object (presumably NHibernate could model
this to a entity [one]-to-name/value pair [many] relationship?)
Depending on your UI requirements, dynamic (runtime) properties could
be used to facade the list into virtual properties of the entity... if
it is useful.

But again, I don't know NHibernate enough to be sure.
 
This sounds like an NHibernate issue finding the mappings (which I
presume the ActiveRecord attribute should fix), and I simply know nada
in that area. Sorry.

How to handle AppDomain assembly load resolve event and force .NET to use
ExtendedCustomer type when it wants to load Customer type ?
It might end up looking for ExtendedCustomer.FindAll(), which it may-
or-may-not find.

ExtendedCustomer.FindAll() generates select sql select statement which
selects extended properties also.
I guess at the end of the day your requirement (to add dynamic
properties to the entity) is very hard to reconcile with a
"regular" (fixed) entity model. I'm trying to think of ways out of the
hole, but:
* runtime properties - not supported by ActiveRecord / NHibernate
* subclassing - in theory should work; currently seeing an NHibernate
issue [probably fixable with NHibernate knowledge], but might also be
hard to get the FindAll() etc working unless you do this on the
subclass (perhaps forwarding to a private method on the base-class)

If neither of those works, then I guess you could compile the entire
Customer type itself at runtime, but gives you *no* compile-time use
of Customer in your main app; would be tortuous to write your app.

Other than that - you simply might not be able to model the extra data
this way, but rather may be forced to use a name/value list
(dictionary) hanging off the object (presumably NHibernate could model
this to a entity [one]-to-name/value pair [many] relationship?)

NHibernate as Dynamic mapping attribute but I havent found any way to use it
from ActiveRecord.
Depending on your UI requirements, dynamic (runtime) properties could
be used to facade the list into virtual properties of the entity... if
it is useful.

I generated database class in VCSE 2008 Beta 2 using PostgreSQL LINQ driver.
It generates code
public partial class MyDatabase : MContext

{

public MyDatabase(string connStr):base(connStr)

{

Customers = new MTable<Customer>(this);

Products = new MTable<Product>(this);

}}

Issues:

1. How to force this constructor to load ExtendendCustomer type created in
memory at runtime and use it instead of Customer class ?

2. How to use LINQ to return all fields from table like SQL SELECT * or AR
FindAll() ?
LINQ does not allow to use from c in db.Customers select * ;

3. How to create and load ExtendendCustomer class only when it is accessed
like AR RegisterTypes() method ? Currently generated class for LINQ
requires to generate extended classes for all tables in database.

4. For virtual grid I need AR SlicedFind() method functionality:
How to return next n entities staring from mth entity ie. select statement

SELECT * FROM customer OFFSET :m LIMIT :n

How to force LINQ to perform such query ?

Andrus.
 
1. How to force this constructor to load ExtendendCustomer type created in
memory at runtime and use it instead of Customer class ?
This is a real toughie; you are trying to hoodwink the CLR, and that
rarely ends well. In short, I don't think you can.

2. How to use LINQ to return all fields from table like SQL SELECT * or AR
FindAll() ?
Return the entity; "from cust in db.Customers return cust;"
3. How to create and load ExtendendCustomer class only when it is accessed
like AR RegisterTypes() method ? Currently generated class for LINQ
requires to generate extended classes for all tables in database.
no idea
4. For virtual grid I need AR SlicedFind() method functionality:
How to return next n entities staring from mth entity ie. select statement
Answered by Nicholas in your other thread
 
Marc Gravell said:
Return the entity; "from cust in db.Customers return cust;"

Slight correction: from cust in db.Customers select cust;

Alternatively, just use db.Customers. (That would certainly work in
LINQ to SQL, but it depends on your LINQ provider.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top