EDIT
Ok, I've got it, it's not serializable.........so, how do I go about serializing it?
Scenario
I have a custom BindableDictionary
that I use to bind with a grid
control for automatically updating
the grid when the underlying
datasource changes.
I now want to extend my application
to use WCF, so that when the
server-side has finished updating
the BindableDictionary with new
values, it can pass this dictionary
to the client, which in turn can
perform the update on the Client
GUI.
Problem
How do I go about sending this custom
BindableDictionary over the wire?
What is the best way to implement this?
The reason I ask about an implementation method is that every example I've seen of Duplex, Callbacks or the Observer Pattern for "PUSHING" my data to the client, only uses an example of passing strings, and it's very primitive - Whenever I update the examples to use my custom BindableDictionary, I cannot get it to work.
Am I missing something?
BindableDictionary
public class BindableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IBindingList
{
private Dictionary<TKey, TValue> source = new Dictionary<TKey, TValue>();
void IBindingList.AddIndex(PropertyDescriptor property) { }
object IBindingList.AddNew() { throw new NotImplementedException(); }
bool IBindingList.AllowEdit { get { return false; } }
bool IBindingList.AllowNew { get { return false; } }
bool IBindingList.AllowRemove { get { return false; } }
void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction) { }
int IBindingList.Find(PropertyDescriptor property, object key) { throw new NotImplementedException(); }
bool IBindingList.IsSorted { get { return false; } }
void IBindingList.RemoveIndex(PropertyDescriptor property) { }
void IBindingList.RemoveSort() { }
ListSortDirection IBindingList.SortDirection { get { return ListSortDirection.Ascending; } }
PropertyDescriptor IBindingList.SortProperty { get { return null; } }
bool IBindingList.SupportsChangeNotification { get { return true; } }
bool IBindingList.SupportsSearching { get { return false; } }
bool IBindingList.SupportsSorting { get { return false; } }
int System.Collections.IList.Add(object value) { throw new NotImplementedException(); }
void System.Collections.IList.Clear() { Clear(); }
bool System.Collections.IList.Contains(object value) { if (value is TKey) { return source.ContainsKey((TKey)value); } else if (value is TValue) { return source.ContainsValue((TValue)value); } return false; }
int System.Collections.IList.IndexOf(object value) { return -1; }
void System.Collections.IList.Insert(int index, object value) { throw new NotImplementedException(); }
bool System.Collections.IList.IsFixedSize { get { return false; } }
bool System.Collections.IList.IsReadOnly { get { return true; } }
void System.Collections.IList.Remove(object value) { if (value is TKey) { Remove((TKey)value); } }
void System.Collections.IList.RemoveAt(int index) { throw new NotImplementedException(); }
object System.Collections.IList.this[int index] { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
private ListChangedEventHandler listChanged;
event ListChangedEventHandler IBindingList.ListChanged
{
add { listChanged += value; }
remove { listChanged -= value; }
}
protected virtual void OnListChanged(ListChangedEventArgs e)
{
var evt = listChanged;
if (evt != null) evt(this, e);
}
public void Add(TKey key, TValue value)
{
source.Add(key, value);
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}
public bool Remove(TKey key)
{
if (source.Remove(key))
{
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
return true;
}
return false;
}
public TValue this[TKey key]
{
get
{
return source[key];
}
set
{
source[key] = value;
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}
}
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
((ICollection<KeyValuePair<TKey, TValue>>)source).Add(item);
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
if (((ICollection<KeyValuePair<TKey, TValue>>)source).Remove(item))
{
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
return true;
}
return false;
}
public bool ContainsKey(TKey key) { return source.ContainsKey(key); }
public ICollection<TKey> Keys { get { return source.Keys; } }
public bool TryGetValue(TKey key, out TValue value) { return source.TryGetValue(key, out value); }
public ICollection<TValue> Values { get { return source.Values; } }
public void Clear() { source.Clear(); }
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) { return ((ICollection<KeyValuePair<TKey, TValue>>)source).Contains(item); }
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { ((ICollection<KeyValuePair<TKey, TValue>>)source).CopyTo(array, arrayIndex); }
public int Count { get { return source.Count; } }
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly { get { return ((ICollection<KeyValuePair<TKey, TValue>>)source).IsReadOnly; } }
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return source.GetEnumerator(); }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
bool ICollection.IsSynchronized { get { return false; } }
object ICollection.SyncRoot { get { return null; } }
void ICollection.CopyTo(Array array, int arrayIndex) { ((ICollection)source).CopyTo(array, arrayIndex); }
}
If you have a specific set of key/values for your BindableDictionary, you could set up your own "data-transfer" object that reflects those values:
[DataContract]
public class MyDTOType
{
[DataMember]
public (keytype) Key { get; set; }
[DataMember]
public (valuetype) Value { get; set; }
}
and then have a DTO object that contains a List<MyDTOType>:
[DataContract]
public class MyDTOList
{
[DataMember]
public List<MyDTOType> ListOfKeyValues { get; set; }
}
Now, as long as you have serializable types for the (keytype) and the (valuetype) in your MyDTOType class, this will be able to travel across the wire in WCF:
[ServiceContract]
interface IMyService
{
[OperationContract]
public MyDTOList GetAllValues(int someCriteria);
}
and the last step you need to find a way to handle nicely is converting your BindableDictionary<TKey, TValue) to a MyDTOList that contains a list of MyDTOType instances that contain a key of TKey type and a value of TValue type. You could probably use something like AutoMapper to handle this conversion on an item-by-item basis.
The answer was that the dictionary was not serializable.
I have designed a serializable dictionary now that works.
Related
I noticed on ListBoxes and LongListSelectors, that the item bound property requests are deferred until they are scrolled toward.
Is it possible to do the same thing in relation to custom positioned items in a ScrollViewer, such that the item doesn't make any request for its bound property values until it is scrolled toward? Is there any easy setting to make this happen automatically?
If not, then what's the simplest way of doing it otherwise?
Its called Lazy Load
here you have an example of Lazy List:
public class LazyCollection<T> : IList<T>, IList, INotifyCollectionChanged, INotifyPropertyChanged
{
private const int LOAD_THRESHOLD = 3;
private ObservableCollection<T> _innerCollection;
public LazyCollection(Func<int, IEnumerable<T>> fetch)
: this(fetch, null)
{ }
public LazyCollection(Func<int, IEnumerable<T>> fetch, IEnumerable<T> items)
{
_fetch = fetch;
_innerCollection = new ObservableCollection<T>(items ?? new T[0]);
this.AttachEvents();
this.HasMoreItems = true;
}
private void AttachEvents()
{
_innerCollection.CollectionChanged += (s, e) => OnCollectionChanged(e);
((INotifyPropertyChanged)_innerCollection).PropertyChanged += (s, e) => OnPropertyChanged(e);
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, e);
}
#endregion
#region INotifyCollectionChanged
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (this.CollectionChanged != null)
this.CollectionChanged(this, e);
}
#endregion
#region IList<T>
public int IndexOf(T item)
{
return _innerCollection.IndexOf(item);
}
public void Insert(int index, T item)
{
_innerCollection.Insert(index, item);
}
public void RemoveAt(int index)
{
_innerCollection.RemoveAt(index);
}
public T this[int index]
{
get
{
Debug.WriteLine("LazyCollection - Reading item {0}", index);
return _innerCollection[index];
}
set { _innerCollection[index] = value; }
}
public void Add(T item)
{
_innerCollection.Add(item);
}
public void Clear()
{
_innerCollection.Clear();
}
public bool Contains(T item)
{
return _innerCollection.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_innerCollection.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _innerCollection.Count; }
}
public bool IsReadOnly
{
get { return ((IList<T>)_innerCollection).IsReadOnly; }
}
public bool Remove(T item)
{
return _innerCollection.Remove(item);
}
public IEnumerator<T> GetEnumerator()
{
Debug.WriteLine("LazyCollection - GetEnumerator");
return _innerCollection.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
Debug.WriteLine("LazyCollection - GetEnumerator explicit");
return ((IEnumerable)_innerCollection).GetEnumerator();
}
#endregion
#region IList
int IList.Add(object value)
{
return ((IList)_innerCollection).Add(value);
}
bool IList.Contains(object value)
{
return ((IList)_innerCollection).Contains(value);
}
int IList.IndexOf(object value)
{
return ((IList)_innerCollection).IndexOf(value);
}
void IList.Insert(int index, object value)
{
((IList)_innerCollection).Insert(index, value);
}
bool IList.IsFixedSize
{
get { return ((IList)_innerCollection).IsFixedSize; }
}
bool IList.IsReadOnly
{
get { return ((IList)_innerCollection).IsReadOnly; }
}
void IList.Remove(object value)
{
((IList)_innerCollection).Remove(value);
}
object IList.this[int index]
{
get
{
if (index > this.Count - LOAD_THRESHOLD)
{
this.TryLoadMoreItems();
}
Debug.WriteLine("LazyCollection - Reading item {0} IList", index);
return ((IList)_innerCollection)[index];
}
set { ((IList)_innerCollection)[index] = value; }
}
void ICollection.CopyTo(Array array, int index)
{
((IList)_innerCollection).CopyTo(array, index);
}
bool ICollection.IsSynchronized
{
get { return ((IList)_innerCollection).IsSynchronized; }
}
object ICollection.SyncRoot
{
get { return ((IList)_innerCollection).SyncRoot; }
}
#endregion
public T[] GetLoadedItems()
{
return _innerCollection.ToArray();
}
public void ResetLoadedItems(IEnumerable<T> list)
{
_innerCollection.Clear();
foreach (var i in list)
_innerCollection.Add(i);
this.HasMoreItems = true;
}
public bool HasMoreItems { get; set; }
private bool _isLoading = false;
private Func<int, IEnumerable<T>> _fetch;
private async Task TryLoadMoreItems()
{
if (_isLoading || !this.HasMoreItems)
return;
try
{
_isLoading = true;
Debug.WriteLine("LazyCollection - Loading more items skip {0}", this.Count);
List<T> items = _fetch != null ? (_fetch(Count)).ToList() : new List<T>();
if (items.Count == 0)
{
Debug.WriteLine("LazyCollection - No items returned, Loading disabled", this.Count);
this.HasMoreItems = false;
}
items.ForEach(x => _innerCollection.Add(x));
Debug.WriteLine("LazyCollection - Items added. Total count: {0}", this.Count);
}
finally
{
_isLoading = false;
}
}
}
public class SearchResults : LazyCollection<Verse>
{
public SearchResults()
: base(count => GetSearchResult(), GetSearchResult())
{
}
public static int _index = 0;
public static string CurrentSearch = "";
public static List<Verse> AllVerses = new List<Verse>();
private static List<Verse> GetSearchResult()
{
List<Verse> results = new List<Verse>();
string lower = CurrentSearch.ToLower();
bool resultsChanged = false;
for (int index = _index; index < AllVerses.Count; index++)
{
Verse verse = AllVerses[index];
if (verse.Content.ToLower().Contains(lower) || verse.Header.ToLower().Contains(lower))
{
results.Add(verse);
resultsChanged = true;
}
if ((index >= (AllVerses.Count / 200) + _index || index + 1 == AllVerses.Count) && resultsChanged && (results.Count > 10 || AllVerses.Count == index + 1))
{
_index = index + 1;
return results;
}
}
return results;
}
}
private ISessionFactory GetSessionFactory(string sessionFactoryConfigPath)
{
GetFullSessionFactoryFor(sessionFactoryConfigPath);
while (!sessionFactoryReady) Thread.Sleep(1000);
return (ISessionFactory)sessionFactories[sessionFactoryConfigPath];
}
private void GetFullSessionFactory(string sessionFactoryConfigPath)
{
ThreadPool.QueueUserWorkItem(state =>
{
ISessionFactory sessionFactory=null;
FluentConfiguration fluentConfiguration = fluentConfiguration.ExposeConfiguration(c => c.SetProperty("sessionfactoryname","somevalue"))
.Mappings(m =>
{
m.FluentMappings
.AddFromAssembly(Assembly.Load("nameofassembly"))
.Conventions.Add(DefaultLazy.Always(),
OptimisticLock.Is(x => x.All()),
DynamicUpdate.AlwaysTrue(),
DynamicInsert.AlwaysFalse(),
DefaultCascade.None()
)
.Conventions.AddFromAssemblyOf<"SomeConvention">();
}
);
sessionFactory = fluentConfiguration.BuildSessionFactory();
});
}
I am creating minisession factory on main thread(not shown here) and full session factory on second thread.
The problem is when it hits buildsessionfactory the code never returns back.Am i doing it right?
public class NHibernateBaseDAO<T>
{
public NHibernateBaseDAO(string sessionFactoryConfigPath, int sessionId)
{
SessionFactoryConfigPath = sessionFactoryConfigPath;
SessionId = sessionId;
public bool Save(T entity)
{
bool saveSuccessful = true;
try
{
NHibernateSession.Save(entity);
}
catch (NHibernate.HibernateException)
{
saveSuccessful = false;
}
return saveSuccessful;
}
public bool SaveOrUpdate(T entity)
{
bool saveSuccessful = true;
try
{
NHibernateSession.SaveOrUpdate(entity);
}
catch (NHibernate.HibernateException)
{
saveSuccessful = false;
}
return saveSuccessful;
}
public void Delete(T entity)
{
NHibernateSession.Delete(entity);
}
public void CommitChanges()
{
if (NHibernateSessionManager.Instance.HasOpenTransactionOn(SessionFactoryConfigPath, this.SessionId))
{
NHibernateSessionManager.Instance.GetSessionFrom(SessionFactoryConfigPath, this.SessionId).Flush();
NHibernateSessionManager.Instance.CommitTransactionOn(SessionFactoryConfigPath, this.SessionId);
}
else
{
NHibernateSessionManager.Instance.GetSessionFrom(SessionFactoryConfigPath, this.SessionId).Flush();
}
}
public void BeginTransaction()
{
NHibernateSessionManager.Instance.BeginTransactionOn(SessionFactoryConfigPath, this.SessionId);
}
public void RollbackTransaction()
{
NHibernateSessionManager.Instance.RollbackTransactionOn(SessionFactoryConfigPath, this.SessionId);
}
public bool IsDirty()
{
return NHibernateSession.IsDirty();
}
public IQueryable<T> Query() {
return (IQueryable<T>)NHibernateSession.Query<T>();
}
protected ISession NHibernateSession
{
get
{
return NHibernateSessionManager.Instance.GetSessionFrom(SessionFactoryConfigPath, this.SessionId);
}
}
protected readonly string SessionFactoryConfigPath;
protected int SessionId;
protected System.Data.IDbConnection DbConnection
{
get { return NHibernateSessionManager.Instance.GetDbConnection(SessionFactoryConfigPath, this.SessionId); }
}
/// <summary>
/// Return a list of object arrays. use this for general queries
/// </summary>
public System.Collections.IEnumerable GetSqlQuery(string queryString, IList<Criterion> criterion, Type returnType)
{
queryString += CriteriaToSql(criterion);
return NHibernateSession.CreateQuery(queryString).Enumerable();
}
protected ICriteria AddCriteria(IList<Criterion> criterion)
{
ICriteria criteria = NHibernateSession.CreateCriteria(persistentType);
foreach (Criterion criterium in criterion)
{
switch (criterium.Comparison)
{
case SqlComparison.StartsWith:
criteria.Add(Restrictions.InsensitiveLike(criterium.Property, criterium.Value1.ToString(), MatchMode.Start));
break;
case SqlComparison.Contains:
criteria.Add(Restrictions.InsensitiveLike(criterium.Property, criterium.Value1.ToString(), MatchMode.Anywhere));
break;
case SqlComparison.Equals:
criteria.Add(Restrictions.Eq(criterium.Property, criterium.Value1));
break;
case SqlComparison.Between:
criteria.Add(Restrictions.Between(criterium.Property, criterium.Value1, criterium.Value2));
break;
case SqlComparison.MoreThan:
criteria.Add(Restrictions.Gt(criterium.Property, criterium.Value1));
break;
case SqlComparison.LessThan:
criteria.Add(Restrictions.Lt(criterium.Property, criterium.Value2));
break;
case SqlComparison.InList:
criteria.Add(Restrictions.In(criterium.Property, (System.Collections.IList)criterium.Value1));
break;
}
}
return criteria;
}
protected string CriteriaToSql(IList<Criterion> criterion)
{
}
/// <summary>
/// Get delimiter for data, defaults to ' unless specifed for data type
/// </summary>
protected string[] GetDelimiter(object value)
{
}
public class Criterion
{
public Criterion(string property, SqlComparison comparison, object value1)
{
Property = property;
Comparison = comparison;
Value1 = value1;
}
public Criterion(string property, SqlComparison comparison, object value1, object value2)
{
Property = property;
Comparison = comparison;
Value1 = value1;
Value2 = value2;
}
public Criterion(string property, SqlComparison comparison, object value1, bool not)
{
Property = property;
Comparison = comparison;
Value1 = value1;
Not = not;
}
public Criterion(string property, SqlComparison comparison, object value1, object value2, bool not)
{
Property = property;
Comparison = comparison;
Value1 = value1;
Value2 = value2;
Not = not;
}
public string Property { get; set; }
public bool Not { get; set; }
public SqlComparison Comparison { get; set; }
public object Value1 { get; set; }
public object Value2 { get; set; }
}
public enum SqlComparison { StartsWith, Contains, Equals, Between, MoreThan, LessThan, InList }
}
one last question please. I am using generic class to access sessionfactory so i cannot explicitly access the minisession. with this how do i access minisession based only on certain entities if the full session is not available and full session factory when it is available.
you never set sessionfactoryready to true
Update: a more complete example.
void Main()
{
Database.InitRealFactoryAsync("<sessionFactoryConfigPath>");
var minifactory = Database.GetMiniFactory("<sessionFactoryConfigPath>");
// Do some stuff with minifactory
var realsessionfactory = Database.SessionFactory;
// Do stuff with real factory
}
static class Database
{
private static ISessionFactory sessionFactory;
public void InitRealFactoryAsync(string sessionFactoryConfigPath)
{
ThreadPool.QueueUserWorkItem(state =>
{
sessionFactory = Fluently.Configure()
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.Load("nameofassembly"))
.Conventions.Add(DefaultLazy.Always(),
OptimisticLock.Is(x => x.All()),
DynamicUpdate.AlwaysTrue(),
DynamicInsert.AlwaysFalse(),
DefaultCascade.None())
.Conventions.AddFromAssemblyOf<FoxproDateConvention>())
.BuildSessionFactory();
});
}
public ISessionFactory GetMiniFactory(string sessionFactoryConfigPath)
{
var assembly = Assembly.Load("nameofassembly");
return Fluently.Configure()
.Mappings(m => m.FluentMappings.Add(assembly.GetTypes().Where(Filter).ToArray())
.Conventions.Add(DefaultLazy.Always(),
OptimisticLock.Is(x => x.All()),
DynamicUpdate.AlwaysTrue(),
DynamicInsert.AlwaysFalse(),
DefaultCascade.None())
.Conventions.AddFromAssemblyOf<FoxproDateConvention>())
.BuildSessionFactory();
}
public static ISessionFactory SessionFactory
{
get {
while (sessionFactory == null) Thread.Sleep(1000);
return sessionFactory;
}
}
}
UpdateUpdate:
void Main()
{
Database.InitRealFactoryAsync("<sessionFactoryConfigPath>");
Database.InitMiniFactory("<sessionFactoryConfigPath>");
using (var session = Database.GetSession(true))
{
// Do some stuff where minifactory is enough
}
using (var session = Database.GetSession())
{
// Do stuff with real factory
}
...
}
// class Database
public ISession GetSession()
{
return GetSession(false);
}
public ISession GetSession(bool miniFactoryIsEnough)
{
if (realSessionfactory != null)
return realSessionfactory.OpenSession();
if (miniFactoryIsEnough)
return miniSessionfactory.OpenSession();
else
{
while (realSessionFactory == null) Thread.Sleep(1000);
return realSessionfactory.OpenSession();
}
}
Update: "access minisession based only on certain entities"
you need to specify the type you want to use in the session:
public ISession GetSession(Type persistentType)
{
if (fullSessionfactory != null)
return realSessionfactory.OpenSession();
if (miniFactory.GetClassMetadata(persistentType) != null)
return miniSessionfactory.OpenSession();
else
{
// minifactory doesnt contain the type needed, wait for full factory
while (fullSessionFactory == null) Thread.Sleep(1000);
return fullSessionfactory.OpenSession();
}
}
some additional advice
do not catch (NHibernate.HibernateException)
you lose valuable information and the calling code can't really decide what to do when false is returned
session state is inconsistent see https://stackoverflow.com/a/1819150/671619
FlushMode should be Flushmode.Commit and public void CommitChanges() can be written as
var session = NHibernateSession;
if (session.Transaction.IsActiv)
{
session.Transaction.Commit();
}
cut out the whole sessionId stuff as it seems to provide no value. hold the session instead of sessionId instead
I have a sql table with a string column that could contain null, empty string or a double value.
I want to map this column to a C# property that is a double, defaulting to zero when the column is null or empty.
Can I do this with a fluent-nhibernate mapping?
I tried this:
Map(p => p.doubleProperty).CustomSqlType("varchar(20)").CustomType("double").Default("0");
and variations on this theme but I always get an error that the conversion failed.
For now I've gone with a custom type which allows me to use
Map(p=>p.doubleProperty).CustomType<DoubleString>();
Which will do for my current needs.
I'll leave the question open for now in case someone comes up with an easier solution.
Code for the DoubleString type is below.
public class DoubleString : IUserType
{
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x == null || y == null)
{
return false;
}
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var valueToGet = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
double returnValue = 0.0;
double.TryParse(valueToGet, out returnValue);
return returnValue;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
object valueToSet = ((double)value).ToString();
NHibernateUtil.String.NullSafeSet(cmd, valueToSet, index);
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get
{
return new[] { new SqlType(DbType.String) };
}
}
public Type ReturnedType
{
get { return typeof(double); }
}
public bool IsMutable
{
get { return true; }
}
}
I would just map this to a string and in your entity have a property that is a double that does the conversion. Seems easier and cleaner than doing it in the mapping.
Maybe something like this:
public double Price
{
get
{
double price = -1.0;
Double.TryParse(stringProperty, out price);
return price;
}
set { stringProperty = value.ToString(); }
}
I solved kind of the same problem using two Properties which both referred to the same private variable. In my example the database contains a varchar which is NOT USED or ACTUAL, i therefor wanted a bool in my application.
private bool _myBool= true;
public virtual bool MyBool{ get { return _myBool; } set { _myBool= value; } }
public virtual string MyString
{
get { return _myBool ? "ACTUAL" : "NOT USED"; }
set { _myBool= value.Equals("NOT USED"); }
}
}
I'm trying to take advantage of Bidirectional serialization of some relational Linq-2-Sql generated entity classes. When using Unidirectional option everything works just fine, bu the moment I add IsReferenceType=true, objects fail to get transported over the tcp binding.
Sample code:
Entity class:
[Table(Name="dbo.Blocks")]
[DataContract()]
public partial class Block : INotifyPropertyChanging, INotifyPropertyChanged
{
private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
private long _ID;
private int _StatusID;
private string _Name;
private bool _IsWithControlPoints;
private long _DivisionID;
private string _SHAPE;
private EntitySet<BlockByWorkstation> _BlockByWorkstations;
private EntitySet<PlanningPointAppropriation> _PlanningPointAppropriations;
private EntitySet<Neighbor> _Neighbors;
private EntitySet<Neighbor> _Neighbors1;
private EntitySet<Task> _Tasks;
private EntitySet<PlanningPointByBlock> _PlanningPointByBlocks;
private EntitySet<ControlPointByBlock> _ControlPointByBlocks;
private EntityRef<Division> _Division;
private bool serializing;
#region Extensibility Method Definitions
partial void OnLoaded();
partial void OnValidate(System.Data.Linq.ChangeAction action);
partial void OnCreated();
partial void OnIDChanging(long value);
partial void OnIDChanged();
partial void OnStatusIDChanging(int value);
partial void OnStatusIDChanged();
partial void OnNameChanging(string value);
partial void OnNameChanged();
partial void OnIsWithControlPointsChanging(bool value);
partial void OnIsWithControlPointsChanged();
partial void OnDivisionIDChanging(long value);
partial void OnDivisionIDChanged();
partial void OnSHAPEChanging(string value);
partial void OnSHAPEChanged();
#endregion
public Block()
{
this.Initialize();
}
[Column(Storage="_ID", AutoSync=AutoSync.OnInsert, DbType="BigInt NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
[DataMember(Order=1)]
public override long ID
{
get
{
return this._ID;
}
set
{
if ((this._ID != value))
{
this.OnIDChanging(value);
this.SendPropertyChanging();
this._ID = value;
this.SendPropertyChanged("ID");
this.OnIDChanged();
}
}
}
[Column(Storage="_StatusID", DbType="Int NOT NULL")]
[DataMember(Order=2)]
public int StatusID
{
get
{
return this._StatusID;
}
set
{
if ((this._StatusID != value))
{
this.OnStatusIDChanging(value);
this.SendPropertyChanging();
this._StatusID = value;
this.SendPropertyChanged("StatusID");
this.OnStatusIDChanged();
}
}
}
[Column(Storage="_Name", DbType="NVarChar(255)")]
[DataMember(Order=3)]
public string Name
{
get
{
return this._Name;
}
set
{
if ((this._Name != value))
{
this.OnNameChanging(value);
this.SendPropertyChanging();
this._Name = value;
this.SendPropertyChanged("Name");
this.OnNameChanged();
}
}
}
[Column(Storage="_IsWithControlPoints", DbType="Bit NOT NULL")]
[DataMember(Order=4)]
public bool IsWithControlPoints
{
get
{
return this._IsWithControlPoints;
}
set
{
if ((this._IsWithControlPoints != value))
{
this.OnIsWithControlPointsChanging(value);
this.SendPropertyChanging();
this._IsWithControlPoints = value;
this.SendPropertyChanged("IsWithControlPoints");
this.OnIsWithControlPointsChanged();
}
}
}
[Column(Storage="_DivisionID", DbType="BigInt NOT NULL")]
[DataMember(Order=5)]
public long DivisionID
{
get
{
return this._DivisionID;
}
set
{
if ((this._DivisionID != value))
{
if (this._Division.HasLoadedOrAssignedValue)
{
throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
}
this.OnDivisionIDChanging(value);
this.SendPropertyChanging();
this._DivisionID = value;
this.SendPropertyChanged("DivisionID");
this.OnDivisionIDChanged();
}
}
}
[Column(Storage="_SHAPE", DbType="Text", UpdateCheck=UpdateCheck.Never)]
[DataMember(Order=6)]
public string SHAPE
{
get
{
return this._SHAPE;
}
set
{
if ((this._SHAPE != value))
{
this.OnSHAPEChanging(value);
this.SendPropertyChanging();
this._SHAPE = value;
this.SendPropertyChanged("SHAPE");
this.OnSHAPEChanged();
}
}
}
[Association(Name="Block_BlockByWorkstation", Storage="_BlockByWorkstations", ThisKey="ID", OtherKey="BlockID")]
[DataMember(Order=7, EmitDefaultValue=false)]
public EntitySet<BlockByWorkstation> BlockByWorkstations
{
get
{
if ((this.serializing
&& (this._BlockByWorkstations.HasLoadedOrAssignedValues == false)))
{
return null;
}
return this._BlockByWorkstations;
}
set
{
this._BlockByWorkstations.Assign(value);
}
}
[Association(Name="Block_PlanningPointAppropriation", Storage="_PlanningPointAppropriations", ThisKey="ID", OtherKey="MasterBlockID")]
[DataMember(Order=8, EmitDefaultValue=false)]
public EntitySet<PlanningPointAppropriation> PlanningPointAppropriations
{
get
{
if ((this.serializing
&& (this._PlanningPointAppropriations.HasLoadedOrAssignedValues == false)))
{
return null;
}
return this._PlanningPointAppropriations;
}
set
{
this._PlanningPointAppropriations.Assign(value);
}
}
[Association(Name="Block_Neighbor", Storage="_Neighbors", ThisKey="ID", OtherKey="FirstBlockID")]
[DataMember(Order=9, EmitDefaultValue=false)]
public EntitySet<Neighbor> Neighbors
{
get
{
if ((this.serializing
&& (this._Neighbors.HasLoadedOrAssignedValues == false)))
{
return null;
}
return this._Neighbors;
}
set
{
this._Neighbors.Assign(value);
}
}
[Association(Name="Block_Neighbor1", Storage="_Neighbors1", ThisKey="ID", OtherKey="SecondBlockID")]
[DataMember(Order=10, EmitDefaultValue=false)]
public EntitySet<Neighbor> Neighbors1
{
get
{
if ((this.serializing
&& (this._Neighbors1.HasLoadedOrAssignedValues == false)))
{
return null;
}
return this._Neighbors1;
}
set
{
this._Neighbors1.Assign(value);
}
}
[Association(Name="Block_Task", Storage="_Tasks", ThisKey="ID", OtherKey="BlockID")]
[DataMember(Order=11, EmitDefaultValue=false)]
public EntitySet<Task> Tasks
{
get
{
if ((this.serializing
&& (this._Tasks.HasLoadedOrAssignedValues == false)))
{
return null;
}
return this._Tasks;
}
set
{
this._Tasks.Assign(value);
}
}
[Association(Name="Block_PlanningPointByBlock", Storage="_PlanningPointByBlocks", ThisKey="ID", OtherKey="BlockID")]
[DataMember(Order=12, EmitDefaultValue=false)]
public EntitySet<PlanningPointByBlock> PlanningPointByBlocks
{
get
{
if ((this.serializing
&& (this._PlanningPointByBlocks.HasLoadedOrAssignedValues == false)))
{
return null;
}
return this._PlanningPointByBlocks;
}
set
{
this._PlanningPointByBlocks.Assign(value);
}
}
[Association(Name="Block_ControlPointByBlock", Storage="_ControlPointByBlocks", ThisKey="ID", OtherKey="BlockID")]
[DataMember(Order=13, EmitDefaultValue=false)]
public EntitySet<ControlPointByBlock> ControlPointByBlocks
{
get
{
if ((this.serializing
&& (this._ControlPointByBlocks.HasLoadedOrAssignedValues == false)))
{
return null;
}
return this._ControlPointByBlocks;
}
set
{
this._ControlPointByBlocks.Assign(value);
}
}
[Association(Name="Division_Block", Storage="_Division", ThisKey="DivisionID", OtherKey="ID", IsForeignKey=true, DeleteOnNull=true, DeleteRule="CASCADE")]
public Division Division
{
get
{
return this._Division.Entity;
}
set
{
Division previousValue = this._Division.Entity;
if (((previousValue != value)
|| (this._Division.HasLoadedOrAssignedValue == false)))
{
this.SendPropertyChanging();
if ((previousValue != null))
{
this._Division.Entity = null;
previousValue.Blocks.Remove(this);
}
this._Division.Entity = value;
if ((value != null))
{
value.Blocks.Add(this);
this._DivisionID = value.ID;
}
else
{
this._DivisionID = default(long);
}
this.SendPropertyChanged("Division");
}
}
}
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void SendPropertyChanging()
{
if ((this.PropertyChanging != null))
{
this.PropertyChanging(this, emptyChangingEventArgs);
}
}
protected virtual void SendPropertyChanged(String propertyName)
{
if ((this.PropertyChanged != null))
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void attach_BlockByWorkstations(BlockByWorkstation entity)
{
this.SendPropertyChanging();
entity.Block = this;
}
private void detach_BlockByWorkstations(BlockByWorkstation entity)
{
this.SendPropertyChanging();
entity.Block = null;
}
private void attach_PlanningPointAppropriations(PlanningPointAppropriation entity)
{
this.SendPropertyChanging();
entity.Block = this;
}
private void detach_PlanningPointAppropriations(PlanningPointAppropriation entity)
{
this.SendPropertyChanging();
entity.Block = null;
}
private void attach_Neighbors(Neighbor entity)
{
this.SendPropertyChanging();
entity.FirstBlock = this;
}
private void detach_Neighbors(Neighbor entity)
{
this.SendPropertyChanging();
entity.FirstBlock = null;
}
private void attach_Neighbors1(Neighbor entity)
{
this.SendPropertyChanging();
entity.SecondBlock = this;
}
private void detach_Neighbors1(Neighbor entity)
{
this.SendPropertyChanging();
entity.SecondBlock = null;
}
private void attach_Tasks(Task entity)
{
this.SendPropertyChanging();
entity.Block = this;
}
private void detach_Tasks(Task entity)
{
this.SendPropertyChanging();
entity.Block = null;
}
private void attach_PlanningPointByBlocks(PlanningPointByBlock entity)
{
this.SendPropertyChanging();
entity.Block = this;
}
private void detach_PlanningPointByBlocks(PlanningPointByBlock entity)
{
this.SendPropertyChanging();
entity.Block = null;
}
private void attach_ControlPointByBlocks(ControlPointByBlock entity)
{
this.SendPropertyChanging();
entity.Block = this;
}
private void detach_ControlPointByBlocks(ControlPointByBlock entity)
{
this.SendPropertyChanging();
entity.Block = null;
}
private void Initialize()
{
this._BlockByWorkstations = new EntitySet<BlockByWorkstation>(new Action<BlockByWorkstation>(this.attach_BlockByWorkstations), new Action<BlockByWorkstation>(this.detach_BlockByWorkstations));
this._PlanningPointAppropriations = new EntitySet<PlanningPointAppropriation>(new Action<PlanningPointAppropriation>(this.attach_PlanningPointAppropriations), new Action<PlanningPointAppropriation>(this.detach_PlanningPointAppropriations));
this._Neighbors = new EntitySet<Neighbor>(new Action<Neighbor>(this.attach_Neighbors), new Action<Neighbor>(this.detach_Neighbors));
this._Neighbors1 = new EntitySet<Neighbor>(new Action<Neighbor>(this.attach_Neighbors1), new Action<Neighbor>(this.detach_Neighbors1));
this._Tasks = new EntitySet<Task>(new Action<Task>(this.attach_Tasks), new Action<Task>(this.detach_Tasks));
this._PlanningPointByBlocks = new EntitySet<PlanningPointByBlock>(new Action<PlanningPointByBlock>(this.attach_PlanningPointByBlocks), new Action<PlanningPointByBlock>(this.detach_PlanningPointByBlocks));
this._ControlPointByBlocks = new EntitySet<ControlPointByBlock>(new Action<ControlPointByBlock>(this.attach_ControlPointByBlocks), new Action<ControlPointByBlock>(this.detach_ControlPointByBlocks));
this._Division = default(EntityRef<Division>);
OnCreated();
}
[OnDeserializing()]
[System.ComponentModel.EditorBrowsableAttribute(EditorBrowsableState.Never)]
public void OnDeserializing(StreamingContext context)
{
this.Initialize();
}
[OnSerializing()]
[System.ComponentModel.EditorBrowsableAttribute(EditorBrowsableState.Never)]
public void OnSerializing(StreamingContext context)
{
this.serializing = true;
}
[OnSerialized()]
[System.ComponentModel.EditorBrowsableAttribute(EditorBrowsableState.Never)]
public void OnSerialized(StreamingContext context)
{
this.serializing = false;
}
}
App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service behaviorConfiguration="debugging"
name="DBServicesLibrary.DBService">
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="DBServicesLibrary.DBServiceBehavior">
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
<behavior name="debugging">
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Host part:
ServiceHost svh = new ServiceHost(typeof(DBService));
svh.AddServiceEndpoint(
typeof(DBServices.Contract.IDBService),
new NetTcpBinding(),
"net.tcp://localhost:8000");
Client part:
ChannelFactory<DBServices.Contract.IDBService> scf;
scf = new ChannelFactory<DBServices.Contract.IDBService>(new NetTcpBinding(),"net.tcp://localhost:8000");
_serv = scf.CreateChannel();
((IContextChannel)_serv).OperationTimeout = new TimeSpan(0, 5, 0);
L2ST4 will help you.
and I quote...
"LINQ to SQL supports the basic DataContract serialization out of the box. This basic serialization had no mechanism to tell it which end of an association was the owner to prevent serialization getting stuck in a loop and so LINQ to SQL would only serialize one side of the relationship."
It's not enough to just add IsReferenceType=true to your DataContract attribute. You also have to make sure all properties have the DataMember attribute. When you set the serialization mode to unidirectional it only automatically adds the DataMember attribute in one direction. For example, it will automatically put the attribute on Group.Users, but you will have to add it to User.Group yourself. I recently tried manually adding in IsReferenceType=true and DataMember and it worked.
I have a SQL Server DB with a recursive table:
MyTable:
ID : string PrimaryKey
Parent: string references MyTable - NOTNULL !!
and map with Fluent NHibernate to
class MyTable
{
public virtual string ID {get; set;}
public virtual MyTable Parent {get; set;}
}
My problem is that Parent should be null in my C# app if the column Parent is "" (empty string) in the database and vice versa. Unfortunately I can't change the column type to accept NULL!
I tried to use IEmptyInterceptor but I don't get it working.
Thanks in advance,
forki
You need to have an IUserType for the primary key column, which does the special NULL value handling.
public MyTableMap()
{
Id(x => x.EntryNo)
// Since the PK is a string, it must be assigned by the application.
.GeneratedBy.Assigned()
.SetAttribute("type", typeof(SpecialNullValueStringType).AssemblyQualifiedName);
References(x => x.Parent);
}
public class SpecialNullValueStringType : IUserType
{
#region IUserType Members
public bool IsMutable
{
get { return false; }
}
public Type ReturnedType
{
get { return typeof(string); }
}
public SqlType[] SqlTypes
{
get { return new[] { NHibernateUtil.String.SqlType }; }
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var obj = NHibernateUtil.String.NullSafeGet(rs, names[0]);
if (obj == null)
{
return null;
}
var value = (string) obj;
if (String.IsNullOrEmpty(value))
{
return null;
}
return value;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null)
{
((IDataParameter) cmd.Parameters[index]).Value = String.Empty;
}
else
{
((IDataParameter) cmd.Parameters[index]).Value = value;
}
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x == null || y == null)
{
return false;
}
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x == null ? typeof(string).GetHashCode() + 473 : x.GetHashCode();
}
#endregion
}
I found a (messy) way to get this working:
public class NullEventListener : IPreUpdateEventListener, IPreInsertEventListener, IPreLoadEventListener
{
#region IPreInsertEventListener Members
public bool OnPreInsert(PreInsertEvent preInsertEvent)
{
var instance = preInsertEvent.Entity as MyTable;
if (instance == null)
return false;
if (instance.Parent == null)
Set(preInsertEvent.Persister, preInsertEvent.State, "Parent", string.Empty);
return false;
}
#endregion
#region IPreLoadEventListener Members
public void OnPreLoad(PreLoadEvent preLoadEvent)
{
var instance = preLoadEvent.Entity as MyTable;
if (instance == null)
return;
try
{
// this is really messy!!
var parent = Get(preLoadEvent.Persister, preLoadEvent.State, "Parent") as MyTable;
if (parent == null || parent.ID == "")
throw new Exception("Set to null");
}
catch (Exception)
{
Set(preLoadEvent.Persister, preLoadEvent.State, "Parent", null);
}
return;
}
#endregion
#region IPreUpdateEventListener Members
public bool OnPreUpdate(PreUpdateEvent preUpdateEvent)
{
var instance = preUpdateEvent.Entity as MyTable;
if (instance == null)
return false;
if (instance.Parent == null)
Set(preUpdateEvent.Persister, preUpdateEvent.State, "Parent", string.Empty);
return false;
}
#endregion
private static void Set(IEntityPersister persister, object[] state, string propertyName, object value)
{
int index = Array.IndexOf(persister.PropertyNames, propertyName);
if (index == -1)
return;
state[index] = value;
}
private static object Get(IEntityPersister persister, object[] state, string propertyName)
{
int index = Array.IndexOf(persister.PropertyNames, propertyName);
if (index == -1)
return null;
return state[index];
}
}
Thanks and regards,
forki
I'd go for a IUserType which would convert empty string to nulls and vice versa. Two methods to pay attention to are NullSafeGet and NullSafeSet.
Not sure though how custom types integrate with Fluent NHibernate.
I tried to implement IUserType for my mapping:
public class MyCustomString : IUserType
{
public Type ReturnedType
{
get { return typeof (MyTable); }
}
public SqlType[] SqlTypes
{
get { return new[] {NHibernateUtil.String.SqlType}; }
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
object obj = NHibernateUtil.String.NullSafeGet(rs, names[0]);
if (obj == null) return null;
var s = (string) obj;
if (s == "")
return null;
using (ISession session = SessionHelper.OpenSession())
{
using (session.BeginTransaction())
{
return MyTable.Get(session, s);
}
}
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
((IDataParameter) cmd.Parameters[index]).Value = value == null ? 0 : ((MyTable) value).EntryNo;
}
...
}
and changed the mapping to
public MyTableMap()
{
Id(x => x.EntryNo);
Map(x => x.Parent).CustomTypeIs<MyCustomString>();
// References() doesn't allow CustomTypeIs()
// References(x => x.Parent).CustomTypeIs<MyCustomString>();
}
This seems to work for my root - but it always opens a session to get the right parent.
And it is not lazy - so it always retrieves all parents up to the root :-(
This can't be the right way. I don't want to open a new session - but otherwise I am returning a string and get a runtime type error.
Have you considered using the Null Object Pattern instead?