I implemented entity databinding in DataGridView OnCellValuePushed method as
PropertyInfo p = entityTmp.GetType().GetProperty(
Columns[e.ColumnIndex].DataPropertyName);
// e.Value type is string
p.SetValue(entityTmp, e.Value, null);
This works only for string property types.
For other types SetValue() causes System.ArgumentException like
Object of type 'System.String' cannot be converted to type 'System.Decimal'.
How to change this code so that SetValue() works for non-string types ?
I need to convert string type (e.Value) to property type:
How to convert string to property p type ?
Andrus.
Have you tried Convert.ChangeType?
--
Jon Skeet - <sk...@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
Jon,
thank you. I changed second line to
p.SetValue(entityTmp, Convert.ChangeType(e.Value, p.PropertyType), null);
and this works.
When user enters letters to numeric cell, this line causes Exception.
How to check that conversion can be performed ?
Should I use try catch to catch this exception or is it possible to
determine conversion error before raising exception ?
Andrus.
I don't think there's any equivalent of "TryChangeType", although you
might want to encapsulate that in a method yourself to avoid polluting
the rest of your code.
Others may have better suggestions though :)
For the conversion, you might want to look at TypeConverter usage,
specifically (where p is a PropertyDescriptor):
ITypeDescriptorContext ctx = new SimpleContext(entityTmp,
p);
if (p.Converter.CanConvertFrom(ctx, typeof(string))) {
object val = p.Converter.ConvertFromString(ctx,
e.Value);
p.SetValue(entityTmp, val);
} else {
// something
}
where SimpleContext is something like:
[ImmutableObject(true)]
public class SimpleContext : ITypeDescriptorContext {
private readonly object instance;
private readonly PropertyDescriptor property;
public 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;}
}
This does 3 things that Convert.ChangeType does not:
* respects a TypeConverter specified on the property itself (rather
than the type)
* passes context to the converter, which is sometimes required if the
converter uses property metadata
* (by doing the above) more closley represents how DataGridView (and
many other bindings) themselves work
There are corresponding .ConvertTo() methods for getting the string in
the first place, and an .IsValid() method for verifying values [but
this is really intended for known-list (StandardValues) lookups]. To
try the conversion I would simply catch the exception (if one). You'd
want to wrap the SetValue() too, since the property-setter could just
as legitimately throw an exception for any reason it likes.
Marc
Thank you.
I created the following method but this causes error shown in comment.
How to fix ?
Is this best style to create this method for conversion user entered value ?
Andrus.
/// <summary>
/// Set value of property from string.
/// </summary>
/// <param name="obj">Object whose property should be set</param>
/// <param name="propertyName">Name of property to set</param>
/// <param name="propertyValue">User entered value</param>
public static void SetValue(object obj, string propertyName, string
propertyValue) {
//How to fix error in following line: 'System.Type' does not contain a
definition for 'GetPropertyDescriptor'
PropertyDescriptor p = obj.GetType().GetPropertyDescriptor();
ITypeDescriptorContext ctx = new SimpleContext(obj,p);
if (p.Converter.CanConvertFrom(ctx, typeof(string))) {
object val = p.Converter.ConvertFromString(ctx, propertyValue);
p.SetValue(obj, val);
}
else {
UIManager.OKGet( "Invalid entry");
}
}
[ImmutableObject(true)]
public class SimpleContext : ITypeDescriptorContext {
private readonly object instance;
private readonly PropertyDescriptor property;
public 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;}
}}
"Marc Gravell" <marc.g...@gmail.com> kirjutas sõnumis
news:1192508665.6...@e9g2000prf.googlegroups.com...
Marc
Thank you.
If I enter characters to numeric column I get exception "eee is not a valid
value for Decimal." at line
object val = p.Converter.ConvertFromString(ctx, propertyValue);
How to fix ?
Why CanConvertFrom() returns true ?
I want to allow users to add calculated columns to table at runtime.
Is it possible to add new calculated property to object at runtime or is
there any other solution ?
Andrus.
Exception which I got:
System.Exception was unhandled
Message="eee is not a valid value for Decimal."
Source="System"
StackTrace:
at
System.ComponentModel.BaseNumberConverter.ConvertFrom(ITypeDescriptorContext
context, CultureInfo culture, Object value)
at
System.ComponentModel.TypeConverter.ConvertFromString(ITypeDescriptorContext
context, String text)
at My.Business.StringConverter.SetValue(Object obj, String
propertyName, String propertyValue)
at
My.Windows.Forms.VirtualGrid`1.OnCellValuePushed(DataGridViewCellValueEventArgs
e)
at System.Windows.Forms.DataGridView.OnCellValuePushed(Int32
columnIndex, Int32 rowIndex, Object value)
at System.Windows.Forms.DataGridViewCell.SetValue(Int32 rowIndex,
Object value)
at
System.Windows.Forms.DataGridView.PushFormattedValue(DataGridViewCell&
dataGridViewCurrentCell, Object formattedValue, Exception& exception)
at System.Windows.Forms.DataGridView.CommitEdit(DataGridViewCell&
dataGridViewCurrentCell, DataGridViewDataErrorContexts context,
DataGridViewValidateCellInternal validateCell, Boolean fireCellLeave,
Boolean fireCellEnter, Boolean fireRowLeave, Boolean fireRowEnter, Boolean
fireLeave)
at
System.Windows.Forms.DataGridView.EndEdit(DataGridViewDataErrorContexts
context, DataGridViewValidateCellInternal validateCell, Boolean
fireCellLeave, Boolean fireCellEnter, Boolean fireRowLeave, Boolean
fireRowEnter, Boolean fireLeave, Boolean keepFocus, Boolean
resetCurrentCell, Boolean resetAnchorCell)
at System.Windows.Forms.DataGridView.CommitEditForOperation(Int32
columnIndex, Int32 rowIndex, Boolean forCurrentCellChange)
at System.Windows.Forms.DataGridView.ScrollIntoView(Int32
columnIndex, Int32 rowIndex, Boolean forCurrentCellChange)
at System.Windows.Forms.DataGridView.TabToNextCell()
at System.Windows.Forms.DataGridView.ProcessTabKey(Keys keyData)
at System.Windows.Forms.DataGridView.ProcessDialogKey(Keys keyData)
at System.Windows.Forms.Control.ProcessDialogKey(Keys keyData)
at System.Windows.Forms.TextBoxBase.ProcessDialogKey(Keys keyData)
at System.Windows.Forms.Control.PreProcessMessage(Message& msg)
...
> Why CanConvertFrom() returns true ?
CanConvertFrom doesn't take the value - it simply indicates whether it
is reasonable to try, based purely on the types. Does it make *sense*
to go from a decimal to a string. In this case, yes the TypeConverter
is willing to have a try, but that doesn't guarantee that it will work
given illegal input.
> I want to allow users to add calculated columns to table at runtime.
Yes it is. But you need to know a range of things from
System.ComponentModel. If this is a requirement, can I suggest
DataTable? It isn't ideal from many OO standpoints, but it supports
this out-of-the-box.
For dynamic property additions (this is *not* trivial), see:
http://groups.google.co.uk/group/microsoft.public.dotnet.languages.csharp/browse_thread/thread/f4d8bb5dcdb82a9f/
However, this doesn't even begin to discuss how you would parse the
expression, i.e. "Age * 2"... so again, I say: look at DataTable... or
Excel. This is not an easy area.
Marc
>> I want to allow users to add calculated columns to table at runtime.
> Yes it is. But you need to know a range of things from
> System.ComponentModel. If this is a requirement, can I suggest DataTable?
> It isn't ideal from many OO standpoints, but it supports this
> out-of-the-box.
thank you very much.
I need to use static methods (from string, Math and other classes) in
expressions.
I havent found any way to use them from DataTable column Expression
property.
How to call static methods from ADO.NET expressions ?
> For dynamic property additions (this is *not* trivial), see:
> http://groups.google.co.uk/group/microsoft.public.dotnet.languages.csharp/browse_thread/thread/f4d8bb5dcdb82a9f/
> However, this doesn't even begin to discuss how you would parse the
> expression, i.e. "Age * 2"... so again, I say: look at DataTable... or
> Excel. This is not an easy area.
I'm thinking about the following:
1. Expression are stored in SQL server table.
2. Before first access to every entity object application creates entity
class (.cs file) from table structure in database and adds expressions as
properties to this file.
3. Application compiles cs file and loads this file to memory.
This avoids writing custom TypeDescriptor and Expression parser and allows
to use full power of C# to write expressions.
Is this best solution ?
Or is it better to create custom type "ExpressionMethod" and custom type
converter which converts string to ExpressionMethod type (to assembly or to
IL code)?
Andrus.
I have GetHashCode() methods in my entity objects.
The line above causes NullReferenceException since it forces GetHashCode()
call for new entity where id is not set yet when adding new row.
How to fix ?
Andrus.
Code to reproduce:
using System;
using System.ComponentModel;
using System.Reflection;
class Test {
static void Main() {
Customer k = new Customer();
SetVal.SetValue(k, "Name", "Marc");
}
}
class Customer {
string id, name;
public string Name {
get { return name; }
set { name = value; }
}
public override int GetHashCode() {
int hash = 57;
hash = 27 * hash * id.GetHashCode();
return hash;
}
}
class SetVal {
public static void SetValue(object obj, string propertyName, string
propertyValue) {
#if false
// this works OK:
PropertyInfo p = obj.GetType().GetProperty(propertyName);
p.SetValue(obj,
propertyValue.Trim().Length == 0 ? null :
Convert.ChangeType(propertyValue, p.PropertyType), null);
#else
// this causes NRE:
PropertyDescriptor p = TypeDescriptor.GetProperties(obj)[propertyName];
ITypeDescriptorContext ctx = new SimpleContext(obj, p);
if (p.Converter.CanConvertFrom(ctx, typeof(string))) {
object val;
val = p.Converter.ConvertFromString(ctx, propertyValue);
p.SetValue(obj, val);
}
#endif
An interesting (to me) aside: if your release schedule allows, you
could also look at the DynamicQuery code from the VS 2008 samples page
on MSDN2; this is an expression parser that can read simple LINQ-style
queries and supports a few operators - but not many. The Expression
can be compiled to a delegate, for instance for invoke by a runtime
property (via something like TypeDescriptionProvider). It doesn't
support any of the Math.Whatever() methods, but it might serve as
inspiration... Or maybe I'm trying to use LINQ to solve everything at
the moment ;-p
> 2. Before first access to every entity object application creates
> entity class (.cs file) from table structure in database and adds
> expressions as properties to this file.
Warning: compiling input at runtime (and executing it) is risky unless
you really trust the source; it is very easy to insert malicious code
into a formula. Just be incredibly careful! You could white-list
approved methods, but even then parsing the expression (to check) can
be a pain...
To get this working, and to allow *some* kind of compile-time
type-safety, you'd need to use some kind of factory model, i.e.
---core code---
public abstract class SomeEntity {
public string Name {...} /// "regular" methods
public int Whatever {...}
static SomeEntity Create() {
... compiles if necessary, and returns a new SomeEntityImp()
}
}
---compile template---
public class SomeEntityImp : SomeEntity {
// ... additional properties (formulae) get inserted here
}
> Is this best solution ?
I don't think either is very ideal; and either could be abused... all
it needs is somebody to create a recursive expression (or do something
super-super-exponential), and *boom*. This is quite
However, if your GetHashCode() throws an exception for an object that
is in use like this, then your GetHashCode() is wrong.
> public override int GetHashCode() {
> int hash = 57;
> hash = 27 * hash * id.GetHashCode();
> return hash;
> }
First, I'd gracefull handle the case where id is null.
Second, unless this is just illustrative, note that the 57 and 27 do
nothing.
public override int GetHashCode() {
return id == null ? 17 : id.GetHashCode(); // why not...
}
Note also that it is important to keep GetHashCode() and Equals() on
speaking terms with eachother. You probably have done, but just
thought I'd mention it... If a.Equals(b), then a.GetHashCode() *must*
equal b.GetHashCode(); however, it is not 2-way; it is perfectly fine
for the hash-codes to match and yet for a.Equals(b) to return false.
Marc
> To get this working, and to allow *some* kind of compile-time type-safety,
> you'd need to use some kind of factory model, i.e.
> ---core code---
> public abstract class SomeEntity {
> public string Name {...} /// "regular" methods
> public int Whatever {...}
> static SomeEntity Create() {
> ... compiles if necessary, and returns a new SomeEntityImp()
> }
> }
> ---compile template---
> public class SomeEntityImp : SomeEntity {
> // ... additional properties (formulae) get inserted here
> }
I'm planning to create "standard" entity assembly containing core properties
which are used from application code in stongly typed way.
This assembly is referenced from VS2005 projects.
At application start but before accesing entity classes, application
compiles new assembly containing additional properties which customers may
have added to server tables.
This eliminates need of factory pattern.
Andrus.
(assuming we're still talking about formulae properties)
Fair enough, but you still need to get hold of the new types and
create instances of them. When I have done similar, I have found that
the best approach is actually a generics-trick (see below), since you
then don't need any reflection (except for the initial startup). The
only other options involve some kind of factory, or getting hold of
the ConstructorInfo instances everywhere.
But again, I strongly warn against taking user-entered formulae and
compiling into code! To me, this would rank right up there with
"&admin=false" (as a web-page query-string), or SQL-injection
vulnerability.
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.
Generics trick to compile with typed code, but execute for a dynamic
(sub)type:
using System;
using System.Reflection;
static class Program
{
static void Main()
{
// would actually be from dynamic assembly
Assembly dynamicAssembly = Assembly.GetExecutingAssembly();
// get the user's type
Type dynamicType = dynamicAssembly.GetType("Dynamic");
// find generic SomeBodyOfCode
MethodInfo mi = typeof(Program).GetMethod(
"SomeBodyOfCode",
BindingFlags.Static | BindingFlags.NonPublic);
// get constructed closed SomeBodyOfCode
mi = mi.MakeGenericMethod(dynamicType);
// invoke
mi.Invoke(null, null);
}
private static void SomeBodyOfCode<T>()
where T : EntityBase, new() {
// can use all EntityBase properties &
// behaviors, new(), etc - but no reflection
// involved
}
}
// from core library
public abstract class EntityBase { }
// from dynamic library
public class Dynamic : EntityBase { }
I need extensible entity classes.
I do'nt understand how your trick can be used to extend Customer
class with new properties. I tried code below but this causes NRE.
So I'm planning to use the following:
1. For application design, create unsigned entity assembly containing common
methods.
public class Customer : EntityBase<Customer> {
string id;
public string Id {
get {
return id;
}
set {
id = value;
}
}
}
This assembly is referenced from projects and allows to use all standard
properties and methods.
2. At application startup, create fully Customer class assembly from table
structure containing
(if user adds) additional properties like
public class Customer: EntityBase<Customer> {
string id;
public string Id {
get {
return id;
}
set {
id = value;
}
}
string name;
public string Name {
get {
return name;
}
set {
name = value;
}
}
public string FullName {
get {
return "Mr " + Name;
}
}
}
and replace dll file in disk with this new file.
3. Now .NET if foolished and it loads the new assembly at runtime expecting
that this is original assembly shipped with application.
This does not require to use any reflection calls.
> But again, I strongly warn against taking user-entered formulae and
> compiling into code! To me, this would rank right up there with
> "&admin=false" (as a web-page query-string), or SQL-injection
> vulnerability.
I'm creating client-server desktop application which runs in client side.
Client can crack this application in any way. Yes, it can corrupt his/her
local computer and corrupt his data in server.
However, client has acces to his computer and to his data anyway and can
corrupt them in any other ways also.
SQL Server asks from user/name password and allows to acces only authorized
data at protocol level. There is no way to use this application for
unauthorized access to restriced resources in server. So I do'nt see ANY
vulnerabilites here. I recommend user to backup data before changing
formulas.
> 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.
How to create TypeDescriptionProvider which compiles poperty expression to
static method and executes it in the fly?
This would be good solution which allows creating dynamic assemblies only
for extensible properites.
Code causing NRE:
using System;
using System;
using System.Reflection;
static class Program {
static void Main() {
// would actually be from dynamic assembly
Assembly dynamicAssembly = Assembly.GetExecutingAssembly();
// get the user's type
Type dynamicType = dynamicAssembly.GetType("CustomerExtension");
// find generic SomeBodyOfCode
MethodInfo mi = typeof(Program).GetMethod(
"EntityBase",
BindingFlags.Static | BindingFlags.NonPublic);
// get constructed closed SomeBodyOfCode
mi = mi.MakeGenericMethod(dynamicType);
// invoke
mi.Invoke(null, null);
// Need to create customer object containing properties
// and methods defined in runtime.
Customer Customer = new Customer();
// set dynamic property, called from DataGridView PushCellValue
Customer.SetValue("Name", "Marc");
}
}
// from core library
public class Customer : EntityBase<Customer> {
string id;
public string Id {
get {
return id;
}
set {
id = value;
}
}
}
// from core library
public abstract class EntityBase<T> {
public virtual void SetValue(string propertyName, string propertyValue) {
PropertyInfo p = GetType().GetProperty(propertyName);
p.SetValue(this, propertyValue, null);
}
}
// from dynamic library
public class CustomerExtension : Customer {
string name;
public string Name {
get {
return name;
}
set {
name = value;
}
}
public string FullName {
get {
return "Mr " + Name;
}
}
}
Which all brings me back to my main point: dynamically building code
from source like this is messy! Especially with reflection thrown
in...
> How to create TypeDescriptionProvider which compiles poperty
> expression to static method and executes it in the fly?
> This would be good solution which allows creating dynamic assemblies
> only for extensible properites.
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.
Marc
> You are using the method-lookup from my code ("SomeBodyOfCode"), but with
> your class name ("EntityBase") - i.e. you have mis-translated the code I
> posted. Instead of using the generics trick (as suggested) you are
> sticking with reflection. Fine, it'll work, but it is much harder. Just
> use:
> Customer cust = (Customer) Activator.CreateInstance(dynamicType) and it
> should work - cust will be an instance of CustomerExtension, which
> inherits from Customer so the cast will work - *assuming* that you got the
> references correct when compiling the dynamic assembly.
I created two versions of dynamic customer definition which can be tested in
code below by changing #if true to #if false.
Results are the same, both require to use
reflection one time but CreateInstance requires less code and does not
require factory class.
Why your generic trick is better ?
Why you do'nt use MakeGenericType instead of MakeGenericMethod ?
Code which I posted in previous message using dynamically compiled assembly
which replaces customer assembly at runtime. This allows to use Customer
type with dynamic properies without instantiating object. How to use
extended customer type directly in this case?
Andrus.
using System;
using System.Reflection;
using System.Windows.Forms;
static class Program {
static void Main() {
// would actually be from dynamic assembly
Assembly dynamicAssembly = Assembly.GetExecutingAssembly();
// get the user's type
Type dynamicType = dynamicAssembly.GetType("CustomerExtension");
#if true
// use shorter way
CustomerBase DynamicCustomer =
(CustomerBase)Activator.CreateInstance(dynamicType);
#else
// Use Marc generic trick
MethodInfo mi = typeof(BusinessObjectFactory).GetMethod(
"CustomerFactory",
BindingFlags.Static | BindingFlags.Public);
// get constructed closed SomeBodyOfCode
mi = mi.MakeGenericMethod(dynamicType);
// invoke
CustomerBase DynamicCustomer = (CustomerBase)mi.Invoke(null, null);
#endif
DynamicCustomer.SetValue("Name", "Marc");
MessageBox.Show(DynamicCustomer.GetStringValue("FullName"));
}
}
// Required only for Mark Generic trick
public class BusinessObjectFactory {
public static CustomerBase CustomerFactory<T>()
where T : CustomerBase, new() {
// can use all EntityBase properties &
// behaviors, new(), etc - but no reflection
// involved
return new T();
}
}
// from core library
public class CustomerBase : EntityBase<CustomerBase> {
string id;
public string Id {
get {
return id;
}
set {
id = value;
}
}
}
// from core library
public abstract class EntityBase<T> {
public virtual void SetValue(string propertyName, string propertyValue) {
PropertyInfo p = GetType().GetProperty(propertyName);
p.SetValue(this, propertyValue, null);
}
public virtual string GetStringValue(string propertyName) {
PropertyInfo p = GetType().GetProperty(propertyName);
return p.GetValue(this, null).ToString();
}
}
// from dynamic library
public class CustomerExtension : CustomerBase {
> which replaces customer assembly at runtime.
not entirely convinced of this... since you can't even properly unload
an assembly, and your code must be referencing the dll to talk to the
(compile-time) Customer class... are you strong-naming?
It seems like you're trying to pull the wool over the runtime's
eyes... you might get away with it, but I wouldn't recommend it... but
if it works...
> This allows to use Customer type with dynamic properies without instantiating object.
> How to use extended customer type directly in this case?
I'm sorry, I don't understand the question. However, the generic
method (executing with T as the extended customer type) *is* using the
extended customer type directly. I don't know if this was the
question.
> // Required only for Mark Generic trick
I had no need of a factory class (as shown in my example), so don't
blame me for this inclusion... this is yours, not mine. I simply
switched into an ineer method (quite normal when refactoring), run
running that generic method with a dynamic T.
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 you wrote:
> 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
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.
---
> 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
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)));
Marc
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.
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
> 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.
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
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
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
> 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 ?
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
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);
}
}
}
[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);
> 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);
}
}
}
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.
How to handle AppDomain assembly load resolve event and force .NET to use
ExtendedCustomer type when it wants to load Customer type ?
>> Array CustomerList = Customer.FindAll();
> 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.
> 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
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.)
--
Jon Skeet - <sk...@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
Not daft at all - after all, you haven't had chapter 11 yet ;)
But feel free to release those LINQ chapters to [ahem] help?
Marc
> 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
You also wrote earlier that runtime properties are not supported in Linq, is
this true ?
> * 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)
I was unable to make AR FindAll() work with Activerecord/NH. FindAll() is
static method and does not find properties in child class.
I was able to extend linq entity class at runtime using the code below.
In design time I have dummy assembly EntityExtension.dll which contains
using System.Data.Linq.Mapping;
[Table(Name = "Customer")]
public class Customer : EntityBase.CustomerWithStandardProperties {}
This dummy assembly is deleted when application is deployed.
In runtime I use AssemblyResolve to create extended entity using code below.
Is this best solution ?
Andrus.
using System;
using System.Windows.Forms;
using System.Reflection;
using System.CodeDom.Compiler;
class Program {
static void Main(string[] args) {
// delete dummy assembly which is used in design time only.
// this is required to run form VS2008 IDE.
System.IO.File.Delete("EntityExtension.dll");
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
Form f = new Form();
DataGridView dgv = new DataGridView();
f.Controls.Add(dgv);
dgv.DataSource = GetData();
Application.Run(f);
}
static Assembly CurrentDomain_AssemblyResolve(object sender,
ResolveEventArgs args) {
if (!args.Name.StartsWith("EntityExtension") ) {
return null;
}
return CreateAssembly();
}
static Assembly CreateAssembly() {
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters compilerParameters = new CompilerParameters();
compilerParameters.GenerateInMemory = true;
compilerParameters.ReferencedAssemblies.Add("EntityBase.dll");
// use postgresql linq provider
compilerParameters.ReferencedAssemblies.Add(@"C:\dblinq2007\DbLinq\bin\DbLinq.Pgsql.Prototype.dll");
compilerParameters.ReferencedAssemblies.Add("System.dll");
compilerParameters.ReferencedAssemblies.Add(@"c:\Program Files\Reference
Assemblies\Microsoft\Framework\v3.5\System.Data.Linq.dll");
CompilerResults compilerResults =
provider.CompileAssemblyFromSource(compilerParameters,
@"
using System;
using System.Data.Linq.Mapping;
[Table(Name = ""customer"")]
public class Customer : EntityBase.CustomerWithStandardProperties {
protected string _a_a;
// sample dynamic property
[Column(Name = ""a_a"", DbType = ""character"")]
public string A_a {
get { return _a_a; }
set { _a_a = value; _isModified_ = true; }
}
// sample calculated field
public string xxNimi {
get { return ""Mr "" + Nimi; }
}
public Customer(string id, string a_a):base(id) {
_a_a = a_a;
}}
");
return compilerResults.CompiledAssembly;
}
public object GetData() {
mydb db;
const string connStr = "server=localhost;";
db = newmydb(connStr);
var q = from k in db.Customers select k;
return q.ToList();
}
}
}
> In runtime I use AssemblyResolve to create extended entity using
> code below.
> Is this best solution ?
I'd be very cautious of this myself - you are providing a substitute
assembly. As long as you get the name + version correct (and strong
name if signed, but then you need to deploy the snk) then it will
probably work, but it doesn't seem a typical use-case. The main use of
AssemblyResolve would be to load the /expected/ dll (perhaps requiring
download) from alternative locations. If you can get it to work, then
great!
Marc