I'm having a bit of a problem with a data serialization method I'm using to copy objects. Here's the method:
public static class ObjectDuplicator
{
public static T Clone<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("the Type must be serializable.", "source");
}
if (Object.ReferenceEquals(source, null)) //dont try to serialize a null object
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
}
The problem is this: when I call this method using the code below
public void AddJob(Job job)
{
if (!Jobs.Contains(job))
{
Job newcopy = Utilities.ObjectDuplicator.Clone<Job>(job);
Jobs.Add(newcopy);
}
}
it throws this exception:
System.InvalidCastException was unhandled
Message=Unable to cast object of type 'KH.CharacterClasses.Freelancer' to type 'KH.CharacterClasses.Job'
Now, the type of job I'm adding is an inherited class from Job, (Freelancer) and the code for those two classes is below
[Serializable]
public class Job : Ability
{
protected JobCommand basecommand1;
protected JobCommand basecommand2;
protected JobCommand basecommand3;
protected JobCommand basecommand4;
protected JobCommand command1;
protected JobCommand command2;
protected JobCommand command3;
protected JobCommand command4;
bool mastered;
protected FFJob job;
protected string name;
int level;
public FFJob SetJob
{
get
{
return job;
}
}
public bool Mastered
{
get
{
return mastered;
}
}
public JobCommand Command1
{
get
{
return command1;
}
set
{
command1 = value;
}
}
public JobCommand DefaultCommand1
{
get
{
return basecommand1;
}
}
public JobCommand Command2
{
get
{
return command2;
}
set
{
command2 = value;
}
}
public JobCommand DefaultCommand2
{
get
{
return basecommand2;
}
}
public JobCommand Command3
{
get
{
return command3;
}
set
{
command3 = value;
}
}
public JobCommand DefaultCommand3
{
get
{
return basecommand3;
}
}
public JobCommand Command4
{
get
{
return command4;
}
set
{
command4 = value;
}
}
public JobCommand DefaultCommand4
{
get
{
return basecommand4;
}
}
public Job(string name, string description, int jobID)
: base(name, description, jobID, -1, -1, null, null, -1, -1)
{
}
public static bool operator ==(Job job1, Job job2)
{
if (System.Object.ReferenceEquals(job1, job2))
return true;
if (((object)job1 == null) || ((object)job2 == null))
return false;
return (job1.Name == job2.Name && job1.UID == job2.UID);
}
public static bool operator !=(Job job1, Job job2)
{
return !(job1 == job2);
}
// public abstract void CharacterModifier(BaseCharacter character);
// public abstract void CharacterDemodifier(BaseCharacter character);
}
[Serializable]
public class Freelancer : Job
{
public Freelancer()
: base("Freelancer", "A character not specializing in any class. Can combine the power of all mastered Jobs.", Globals.JobID.ID)
{
basecommand1 = JobCommand.Attack;
basecommand2 = JobCommand.Free;
basecommand3 = JobCommand.Free;
basecommand4 = JobCommand.Items;
command1 = basecommand1;
command2 = basecommand2;
command3 = basecommand3;
command4 = basecommand4;
job = FFJob.Freelancer;
}
}
I'm a bit stumped here because I know the ObjectDuplicator method does work. In fact, this code HAS worked before, but that was on a different computer, and I haven't looked at it in awhile. I'm a little stumped as to why the casting fails here. If someone could help me out with whats wrong thatd be great. If you need more details, just say what you need. I asked this question yesterday, but didn't get a workable answer.
Thanks
Try replacing
return (T)formatter.Deserialize(stream);
with
var result = formatter.Deserialize(stream);
for (Type now = result.GetType(); now != null; now = now.BaseType)
MessageBox.Show(now.FullName);
return result as T;
What does Clone<T> return? Do you see KH.CharacterClasses.Job in a list of base types? Seems like it's not the base type for Freelancer.
I would never place a return statement into a using clause! Do this instead:
object tClone = null;
using (Stream stream = new MemoryStream()) {
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
tClone = formatter.Deserialize(stream);
}
return (T)tClone;
If the exception is still thrown, then your types are indeed incompatible...
Figured out the solution here:
Casting Error when using serialization
Related
I have a problem whereby I wish to generate a JSON field where the field name is known at runtime e.g.:
{ "known_at_run_time": ["test","test","test"] }
So I tried implementing it this way, yet whenever I run my unit test I get an error saying that that my custom JsonConverter cannot be created. Here is my code:
TermFilter.cs
public enum ExecutionType { plain, fielddata, #bool, and, or }
[JsonObject(MemberSerialization.OptIn)]
public class TermFilter
{
#region PROPERTIES
private JsonTuple query;
private ExecutionType execution;
private string _execution;
private bool _cache;
#endregion
#region CONSTRUCTOR
public TermFilter()
{
try
{
this.query = null;
this.Execution = ExecutionType.plain;
this.Cache = true;
}
catch(Exception)
{
throw;
}
}
public TermFilter(ExecutionType execution)
: this()
{
try
{
this.Execution = execution;
}
catch (Exception)
{
throw;
}
}
public TermFilter(ExecutionType execution, bool cache)
: this(execution)
{
try
{
this.Cache = cache;
}
catch (Exception)
{
throw;
}
}
public TermFilter(string field, string[] terms)
:this()
{
try
{
this.Query = new JsonTuple(field, new HashSet<string>(terms));
}
catch (Exception)
{
throw;
}
}
#endregion
#region GET/SET
//[JsonProperty(ItemConverterType = typeof(JsonTupleConverter))]
//[JsonProperty]
[JsonConverter( typeof(JsonTupleConverter) )]
public JsonTuple Query
{
get { return query; }
set { query = value; }
}
public ExecutionType Execution
{
get { return execution; }
set
{
execution = value;
_execution = value.ToString();
}
}
[JsonProperty(PropertyName = "execution")]
public string _Execution
{
get { return _execution; }
set { _execution = value; }
}
[JsonProperty(PropertyName = "_cache")]
public bool Cache
{
get { return _cache; }
set { _cache = value; }
}
#endregion
#region METHODS
public TermFilter AddTerm(string term)
{
try
{
if (!this.query.Data.Contains(term))
this.query.Data.Add(term);
return this;
}
catch (Exception)
{
throw;
}
}
public string ToJson()
{
try
{
var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
settings.Converters.Add(new JsonTupleConverter(new Type[] { typeof(JsonTuple) }));
settings.NullValueHandling = NullValueHandling.Ignore;
return JsonConvert.SerializeObject(this, settings);
//return JsonConvert.SerializeObject(this, Formatting.None, new JsonTupleConverter(typeof(JsonTuple)));
//return JsonConvert.SerializeObject(this, Formatting.None, new JsonConverter[] { new JsonTupleConverter(typeof(JsonTuple)), });
}
catch (Exception)
{
throw;
}
}
#endregion
}
JsonTuple.cs
public class JsonTuple
{
#region PROPERTIES
private string field;
private HashSet<string> data;
#endregion
#region CONSTRUCTOR
public JsonTuple()
{
try
{
this.field = null;
this.data = null;
}
catch (Exception)
{
throw;
}
}
public JsonTuple(string field, HashSet<string> data)
{
try
{
this.field = field;
this.data = data;
}
catch (Exception)
{
throw;
}
}
#endregion
#region GET/SET
public string Field
{
get { return field; }
set { field = value; }
}
public HashSet<string> Data
{
get { return data; }
set { data = value; }
}
#endregion
#region METHODS
public string ToJson()
{
try
{
return JsonConvert.SerializeObject(this, Formatting.None, new JsonTupleConverter(typeof(JsonTuple)));
}
catch (Exception)
{
throw;
}
}
#endregion
}
JsonTupleConverter.cs
public class JsonTupleConverter : JsonConverter
{
private readonly Type[] _types;
public JsonTupleConverter(params Type[] types)
{
try
{
_types = types;
}
catch (Exception)
{
throw;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
try
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else if (!_types.Any(_t => _t == value.GetType()))
{
serializer.Serialize(writer, value);
}
else
{
JsonTuple tuple = (JsonTuple)value;
if ((tuple != null) && (tuple.Field != null) && (tuple.Data != null))
{
JToken entityToken = null;
if (tuple.Data != null)
entityToken = JToken.FromObject(tuple.Data);
JObject o = new JObject();
o.AddFirst(new JProperty(tuple.Field, entityToken));
o.WriteTo(writer);
}
}
}
catch (Exception)
{
throw;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == objectType);
}
}
Test.cs
[TestMethod]
public void TermFieldSertialization()
{
try
{
TermFilter filter = new TermFilter("test.field", new string[] {"test1", "test2", "test3"});
Assert.IsNotNull(filter);
string sampleJson = filter.ToJson();
Assert.IsNotNull(sampleJson);
}
catch (Exception)
{
throw;
}
}
What am I doing wrong? Any information will help.
First, try removing the [JsonConverter] attribute from the Query property of your TermFilter class. You don't need it because the Query property is a JsonTuple, and you are already passing an instance of your JsonTupleConverter to the JsonConvert.SerializeObject() method inside your ToJson() method, specifying that it can handle JsonTuples. This will get rid of the error.
However, there is still another issue. It seems that your intent is to get the Query property to serialize to JSON, but as things stand now this will not happen. This is because you have marked your TermFilter class with [JsonObject(MemberSerialization.OptIn)], and the Query property lacks a [JsonProperty] attribute to signal that you want that property to be included in the output. You will need to add [JsonProperty("query")] to fix that. Once you have done that, you should get the output you expect.
As an aside, you don't need to catch exceptions if you only intend to throw them again without doing anything else with them. I see that pattern everywhere in your code. Instead, just leave out the try/catch altogether; it does exactly the same thing and will make your code much more concise. Only catch an exception if you intend to handle it.
I think that your exception is occurring because JsonTupleConverter doesn't have a parameterless constructor.
public JsonTupleConverter() { }
If you add that, the error should go away, but your code might not work because then it'd probably be trying to use a converter without the types set up correctly.
Maybe you should just be serializing it as a dictionary? E.g.
var myDict = new Dictionary<string, List<string>>
{
{ "known_at_run_time", new List<string> { "test","test","test" } }
};
string ser = JsonConvert.SerializeObject(myDict);
// ser is {"known_at_run_time":["test","test","test"]}
I'm writing a fluent registration for a cache provider, but for some reason my generics are not happy. I'm getting an error on this bit: value = _loadFunction();
Cannot implicitly convert type 'T' to 'T [HttpRuntimeCache.cs(10)]
Code Below:
using IDM.CMS3.Service.Cache;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;
namespace IDM.CMS3.Web.Public.CacheProviders
{
public class HttpRuntimeCache<T> : IFluentCacheProvider<T>
{
string _key;
Func<T> _loadFunction;
DateTime? _absoluteExpiry;
TimeSpan? _relativeExpiry;
public HttpRuntimeCache()
{
}
public IFluentCacheProvider<T> Key(string key)
{
_key = key;
return this;
}
public IFluentCacheProvider<T> Load(Func<T> loadFunction)
{
_loadFunction = loadFunction;
return this;
}
public IFluentCacheProvider<T> AbsoluteExpiry(DateTime absoluteExpiry)
{
_absoluteExpiry = absoluteExpiry;
return this;
}
public IFluentCacheProvider<T> RelativeExpiry(TimeSpan relativeExpiry)
{
_relativeExpiry = relativeExpiry;
return this;
}
public T Value()
{
return FetchAndCache<T>();
}
public void InvalidateCacheItem(string cacheKey)
{
throw new NotImplementedException();
}
T FetchAndCache<T>()
{
T value;
if (!TryGetValue<T>(_key, out value))
{
value = _loadFunction();
if (!_absoluteExpiry.HasValue)
_absoluteExpiry = Cache.NoAbsoluteExpiration;
if (!_relativeExpiry.HasValue)
_relativeExpiry = Cache.NoSlidingExpiration;
HttpContext.Current.Cache.Insert(_key, value, null, _absoluteExpiry.Value, _relativeExpiry.Value);
}
return value;
}
bool TryGetValue<T>(string key, out T value)
{
object cachedValue = HttpContext.Current.Cache.Get(key);
if (cachedValue == null)
{
value = default(T);
return false;
}
else
{
try
{
value = (T)cachedValue;
return true;
}
catch
{
value = default(T);
return false;
}
}
}
}
}
The T FetchAndCache<T> and bool TryGetValue<T> are redefining a new T type separate from the one declared on the class level. I think once you remove the extra generic declaration it should work fine. That is, rewrite them to be:
T FetchAndCache()
{
...
}
bool TryGetValue(string key, out T value)
{
...
}
Once you do that, the compiler will recognize the T here as the one declared on the class.
Is it possible to make xUnit test work when you don't specify optional parameter values in InlineDataAttribute?
Example:
[Theory]
[InlineData(1, true)] // works
[InlineData(2)] // error
void Test(int num, bool fast=true){}
Yes it is. There are many ways to do it by redefining some original xunit attributes.
The following code is one of them, which would give you some idea.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class OptionalTheoryAttribute : TheoryAttribute
{
protected override IEnumerable<ITestCommand> EnumerateTestCommands(IMethodInfo method)
{
var result = (List<ITestCommand>)base.EnumerateTestCommands(method);
try
{
return TransferToSupportOptional(result, method);
}
catch (Exception ex)
{
result.Clear();
result.Add(new LambdaTestCommand(method, () =>
{
throw new InvalidOperationException(
String.Format("An exception was thrown while getting data for theory {0}.{1}:\r\n{2}",
method.TypeName, method.Name, ex)
);
}));
}
return result;
}
private static IEnumerable<ITestCommand> TransferToSupportOptional(
IEnumerable<ITestCommand> testCommands, IMethodInfo method)
{
var parameterInfos = method.MethodInfo.GetParameters();
testCommands.OfType<TheoryCommand>().ToList().ForEach(
testCommand => typeof(TheoryCommand)
.GetProperty("Parameters")
.SetValue(testCommand, GetParameterValues(testCommand, parameterInfos)));
return testCommands;
}
private static object[] GetParameterValues(TheoryCommand testCommand, ParameterInfo[] parameterInfos)
{
var specifiedValues = testCommand.Parameters;
var optionalValues = GetOptionalValues(testCommand, parameterInfos);
return specifiedValues.Concat(optionalValues).ToArray();
}
private static IEnumerable<object> GetOptionalValues(TheoryCommand command, ParameterInfo[] parameterInfos)
{
return Enumerable.Range(command.Parameters.Length, parameterInfos.Length - command.Parameters.Length)
.ToList().Select(i =>
{
EnsureIsOptional(parameterInfos[i]);
return Type.Missing;
});
}
private static void EnsureIsOptional(ParameterInfo parameterInfo)
{
if (!parameterInfo.IsOptional)
{
throw new ArgumentException(string.Format(
"The parameter '{0}' should be optional or specified from data attribute.",
parameterInfo));
}
}
}
internal class LambdaTestCommand : TestCommand
{
private readonly Assert.ThrowsDelegate lambda;
public LambdaTestCommand(IMethodInfo method, Assert.ThrowsDelegate lambda)
: base(method, null, 0)
{
this.lambda = lambda;
}
public override bool ShouldCreateInstance
{
get
{
return false;
}
}
public override MethodResult Execute(object testClass)
{
try
{
lambda();
return new PassedResult(testMethod, DisplayName);
}
catch (Exception ex)
{
return new FailedResult(testMethod, ex, DisplayName);
}
}
}
public class OptionalTheoryTest
{
[OptionalTheory]
[InlineData(1)]
[InlineData(1, true)]
public void TestMethod(int num, bool fast = true)
{
// Arrange
// Act
// Assert
Assert.Equal(1, num);
Assert.True(fast);
}
}
I have a DataTable with complex objects.
For example,
class ComplexDataWrapper
{
public string Name{ get; set; }
public ComplexData Data{ get; set; }
public ComplexDataWrapper(ComplexData data)
{
this.Data = data;
this.Name = "Something";
}
public override string ToString()
{
return Name;
}
}
And now I want to bind cells from DataTable to objects of ComplexDataWrapper
So, I try something like this :
...
var column = new DataColumn() { ColumnName = columnName, DataType = typeof(ComplexDataWrapper)};
row[column] = new ComplexDataWrapper(data);
But, I want to bind for only one property, for example, Name.
And in the gridview (DataTable is a data source for this view) I want to edit this property(Name).
var complexDataWrapper = row[column] as ComplexDataWrapper;
complexDataWrapper always equals to NULL.
I know that I miss something.
So my questions : How I can bind my cell of DataTable to complex object? Plus in grid view I want to edit exactly one property of complex object.
Thanks. Hopefully, everything is clear.
So my questions : How I can bind my cell of DataTable to complex object? Plus in grid view I want to edit exactly one property of complex object.
What you need here is the ability to bind to a so called property path (e.g. obj.Prop1.Prop2). Unfortunately WinForms has limited support for that - it's supported for simple data binding (like control.DataBindings.Add(...)) but not for list data binding which is used by DataGridView control and similar.
Fortunately it's still doable with some (most of the time trivial) coding, because the data binding is build around an abstraction called PropertyDescriptor. By default it is implemented via reflection, but nothing prevents you to create your own implementation and do whatever you like inside it. That allows you to do many things that are not possible with reflection, in particular to simulate "properties" that actually do not exist.
Here we will utilize that possibility to create a "property" that actually gets/sets its value from a child property of the original property, while from outside it still looks like a single property, thus allowing to data bind to it:
public class ChildPropertyDescriptor : PropertyDescriptor
{
public static PropertyDescriptor Create(PropertyDescriptor sourceProperty, string childPropertyPath, string displayName = null)
{
var propertyNames = childPropertyPath.Split('.');
var propertyPath = new PropertyDescriptor[1 + propertyNames.Length];
propertyPath[0] = sourceProperty;
for (int i = 0; i < propertyNames.Length; i++)
propertyPath[i + 1] = propertyPath[i].GetChildProperties()[propertyNames[i]];
return new ChildPropertyDescriptor(propertyPath, displayName);
}
private ChildPropertyDescriptor(PropertyDescriptor[] propertyPath, string displayName)
: base(propertyPath[0].Name, null)
{
this.propertyPath = propertyPath;
this.displayName = displayName;
}
private PropertyDescriptor[] propertyPath;
private string displayName;
private PropertyDescriptor RootProperty { get { return propertyPath[0]; } }
private PropertyDescriptor ValueProperty { get { return propertyPath[propertyPath.Length - 1]; } }
public override Type ComponentType { get { return RootProperty.ComponentType; } }
public override bool IsReadOnly { get { return ValueProperty.IsReadOnly; } }
public override Type PropertyType { get { return ValueProperty.PropertyType; } }
public override bool CanResetValue(object component) { var target = GetTarget(component); return target != null && ValueProperty.CanResetValue(target); }
public override object GetValue(object component) { var target = GetTarget(component); return target != null ? ValueProperty.GetValue(target) : null; }
public override void ResetValue(object component) { ValueProperty.ResetValue(GetTarget(component)); }
public override void SetValue(object component, object value) { ValueProperty.SetValue(GetTarget(component), value); }
public override bool ShouldSerializeValue(object component) { var target = GetTarget(component); return target != null && ValueProperty.ShouldSerializeValue(target); }
public override AttributeCollection Attributes { get { return ValueProperty.Attributes; } }
public override string Category { get { return ValueProperty.Category; } }
public override TypeConverter Converter { get { return ValueProperty.Converter; } }
public override string Description { get { return ValueProperty.Description; } }
public override bool IsBrowsable { get { return ValueProperty.IsBrowsable; } }
public override bool IsLocalizable { get { return ValueProperty.IsLocalizable; } }
public override string DisplayName { get { return displayName ?? RootProperty.DisplayName; } }
public override object GetEditor(Type editorBaseType) { return ValueProperty.GetEditor(editorBaseType); }
public override PropertyDescriptorCollection GetChildProperties(object instance, Attribute[] filter) { return ValueProperty.GetChildProperties(GetTarget(instance), filter); }
public override bool SupportsChangeEvents { get { return ValueProperty.SupportsChangeEvents; } }
public override void AddValueChanged(object component, EventHandler handler)
{
var target = GetTarget(component);
if (target != null)
ValueProperty.AddValueChanged(target, handler);
}
public override void RemoveValueChanged(object component, EventHandler handler)
{
var target = GetTarget(component);
if (target != null)
ValueProperty.RemoveValueChanged(target, handler);
}
private object GetTarget(object source)
{
var target = source;
for (int i = 0; target != null && target != DBNull.Value && i < propertyPath.Length - 1; i++)
target = propertyPath[i].GetValue(target);
return target != DBNull.Value ? target : null;
}
}
The code is not so small, but all it does is basically delegating the calls to the corresponding methods of the property descriptor chain representing the path from the original property to the child property. Also please note that many methods of the PropertyDescriptor are used only during the design time, so creating a custom concrete runtime property descriptor usually needs only to implement ComponentType, PropertyType, GetValue and SetValue (if supported).
So far so good. This is just the first part of the puzzle. We can create a "property", now we need a way to let data binding use it.
In order to do that, we'll utilize another data binding related interface called ITypedList:
Provides functionality to discover the schema for a bindable list, where the properties available for binding differ from the public properties of the object to bind to.
In other words, it allows us to provide "properties" for the list elements. But how? If we were implementing the data source list, it would be easy. But here we want to do that for a list that we don't know in advance (I'm trying the keep the solution generic).
The solutions is to wrap the original list in onother one that will implement IList (the minimum requirement for list data binding) by delegating all the calls to the underlying list, but by implementing ITypedList will control the properties used for binding:
public static class ListDataView
{
public static IList Create(object dataSource, string dataMember, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper)
{
var source = (IList)ListBindingHelper.GetList(dataSource, dataMember);
if (source == null) return null;
if (source is IBindingListView) return new BindingListView((IBindingListView)source, propertyMapper);
if (source is IBindingList) return new BindingList((IBindingList)source, propertyMapper);
return new List(source, propertyMapper);
}
private class List : IList, ITypedList
{
private readonly IList source;
private readonly Func<PropertyDescriptor, PropertyDescriptor> propertyMapper;
public List(IList source, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper) { this.source = source; this.propertyMapper = propertyMapper; }
// IList
public object this[int index] { get { return source[index]; } set { source[index] = value; } }
public int Count { get { return source.Count; } }
public bool IsFixedSize { get { return source.IsFixedSize; } }
public bool IsReadOnly { get { return source.IsReadOnly; } }
public bool IsSynchronized { get { return source.IsSynchronized; } }
public object SyncRoot { get { return source.SyncRoot; } }
public int Add(object value) { return source.Add(value); }
public void Clear() { source.Clear(); }
public bool Contains(object value) { return source.Contains(value); }
public void CopyTo(Array array, int index) { source.CopyTo(array, index); }
public IEnumerator GetEnumerator() { return source.GetEnumerator(); }
public int IndexOf(object value) { return source.IndexOf(value); }
public void Insert(int index, object value) { source.Insert(index, value); }
public void Remove(object value) { source.Remove(value); }
public void RemoveAt(int index) { source.RemoveAt(index); }
// ITypedList
public string GetListName(PropertyDescriptor[] listAccessors) { return ListBindingHelper.GetListName(source, listAccessors); }
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
var properties = ListBindingHelper.GetListItemProperties(source, listAccessors);
if (propertyMapper != null)
properties = new PropertyDescriptorCollection(properties.Cast<PropertyDescriptor>()
.Select(propertyMapper).Where(p => p != null).ToArray());
return properties;
}
}
private class BindingList : List, IBindingList
{
private IBindingList source;
public BindingList(IBindingList source, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper) : base(source, propertyMapper) { this.source = source; }
private ListChangedEventHandler listChanged;
public event ListChangedEventHandler ListChanged
{
add
{
var oldHandler = listChanged;
if ((listChanged = oldHandler + value) != null && oldHandler == null)
source.ListChanged += OnListChanged;
}
remove
{
var oldHandler = listChanged;
if ((listChanged = oldHandler - value) == null && oldHandler != null)
source.ListChanged -= OnListChanged;
}
}
private void OnListChanged(object sender, ListChangedEventArgs e)
{
var handler = listChanged;
if (handler != null)
handler(this, e);
}
public bool AllowNew { get { return source.AllowNew; } }
public bool AllowEdit { get { return source.AllowEdit; } }
public bool AllowRemove { get { return source.AllowRemove; } }
public bool SupportsChangeNotification { get { return source.SupportsChangeNotification; } }
public bool SupportsSearching { get { return source.SupportsSearching; } }
public bool SupportsSorting { get { return source.SupportsSorting; } }
public bool IsSorted { get { return source.IsSorted; } }
public PropertyDescriptor SortProperty { get { return source.SortProperty; } }
public ListSortDirection SortDirection { get { return source.SortDirection; } }
public object AddNew() { return source.AddNew(); }
public void AddIndex(PropertyDescriptor property) { source.AddIndex(property); }
public void ApplySort(PropertyDescriptor property, ListSortDirection direction) { source.ApplySort(property, direction); }
public int Find(PropertyDescriptor property, object key) { return source.Find(property, key); }
public void RemoveIndex(PropertyDescriptor property) { source.RemoveIndex(property); }
public void RemoveSort() { source.RemoveSort(); }
}
private class BindingListView : BindingList, IBindingListView
{
private IBindingListView source;
public BindingListView(IBindingListView source, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper) : base(source, propertyMapper) { this.source = source; }
public string Filter { get { return source.Filter; } set { source.Filter = value; } }
public ListSortDescriptionCollection SortDescriptions { get { return source.SortDescriptions; } }
public bool SupportsAdvancedSorting { get { return source.SupportsAdvancedSorting; } }
public bool SupportsFiltering { get { return source.SupportsFiltering; } }
public void ApplySort(ListSortDescriptionCollection sorts) { source.ApplySort(sorts); }
public void RemoveFilter() { source.RemoveFilter(); }
}
}
Actually as you can see, I've added wrappers for other data source interfaces like IBindingList and IBindingListView. Again, the code is not so small, but it's just delegating the calls to the underlying objects (when creating one for your concrete data, you usually would inherit from List<T> or BiundingList<T> and implement only the two ITypedList members). The essential part is the GetItemProperties method implementation which along with the propertyMapper lambda allows you to replace one property with another.
With all that in place, solving the specific problem from the post is simple a matter of wrapping the DataTable and mapping the Complex property to the Complex.Name property:
class ComplexData
{
public int Value { get; set; }
}
class ComplexDataWrapper
{
public string Name { get; set; }
public ComplexData Data { get; set; } = new ComplexData();
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form();
var gridView = new DataGridView { Dock = DockStyle.Fill, Parent = form };
gridView.DataSource = ListDataView.Create(GetData(), null, p =>
{
if (p.PropertyType == typeof(ComplexDataWrapper))
return ChildPropertyDescriptor.Create(p, "Name", "Complex Name");
return p;
});
Application.Run(form);
}
static DataTable GetData()
{
var dt = new DataTable();
dt.Columns.Add("Id", typeof(int));
dt.Columns.Add("Complex", typeof(ComplexDataWrapper));
for (int i = 1; i <= 10; i++)
dt.Rows.Add(i, new ComplexDataWrapper { Name = "Name#" + i, Data = new ComplexData { Value = i } });
return dt;
}
}
To recap, custom PropertyDescriptor and ITypedList allow you to create unlimited types of views of your data, which then can be used by any data bound aware control.
I believe your architecture is flawed for what you're trying to achieve.
If you are using the gridView to edit a single property on your complex type, then there is no need to bind the entire type into the datatable the is the datasource of your grid.
Instead you should bind only the property you wish to edit, and when the the data comes back, simply assign it to the complex type in the right place.
I have been working with a project for last 4 months. We are using a custom framework for the development. The problem I am talking about, was working for all other classes. But for the first time I am facing this weird incident. Now Straight to break point.
My framework code is like
public static List<ViewNotSetBillableCoursesEntity> GetAllNotSetBillableCources()
{
try
{
List<ViewNotSetBillableCoursesEntity> entities = new List<ViewNotSetBillableCoursesEntity>();
string command = SELECT;
SqlConnection sqlConnection = MSSqlConnectionHandler.GetConnection();
SqlDataReader dataReader = QueryHandler.ExecuteSelect(command, sqlConnection);
entities = Maps(dataReader);
dataReader.Close();
return entities;
}
catch (Exception exception)
{
throw exception;
}
}
In the above method the dataReader is sent to Maps method.
The Maps method is ......
private static List<ViewNotSetBillableCoursesEntity> Maps(SqlDataReader theReader)
{
SQLNullHandler nullHandler = new SQLNullHandler(theReader);
// the incident is happening here, the SQLNullHandler is given below.
List<ViewNotSetBillableCoursesEntity> entities = null;
while (theReader.Read())
{
if (entities == null)
{
entities = new List<ViewNotSetBillableCoursesEntity>();
}
ViewNotSetBillableCoursesEntity entity = Mapper(nullHandler);
entities.Add(entity);
}
return entities;
}
The SQLNullHandler is given below:
puplic Class SQLNullHandler
{
private IDataReader _reader;
public SQLNullHandler(IDataReader reader)
{
_reader = reader;
}
#region Get Null value
public static object GetNullValue(int Value)
{
if(Value==0)
{
return null;
}
else
{
return Value;
}
}
public static object GetNullValue(double Value)
{
if (Value == 0)
{
return null;
}
else
{
return Value;
}
}
public static object GetNullValue(decimal Value)
{
if (Value == 0)
{
return null;
}
else
{
return Value;
}
}
public static object GetNullValue(DateTime Value)
{
if(DateTime.MinValue==Value)
{
return null;
}
else
{
return Value;
}
}
public static object GetNullValue(string Value)
{
if(Value.Length<=0)
{
return null;
}
else
{
return Value;
}
}
#endregion
public IDataReader Reader
{
get{return _reader;}
}
public bool IsNull(int index)
{
return _reader.IsDBNull(index);
}
public int GetInt32(int i)
{
return _reader.IsDBNull(i)? 0 : _reader.GetInt32(i);
}
public byte GetByte(int i)
{
return _reader.IsDBNull(i)? (byte)0 : _reader.GetByte(i);
}
//and so on for all possible type for this app
}
The Funny thing is for all these classes these methods and lines of code work very fine, but in this scenario after the line SQLNullHandler nullHandler = new SQLNullHandler(theReader); the datareder becomes empty.
My questions are
Why is this Happening and next,
what can be done to solve this problem?