How to format SQL Server DateTimeOffset and move to Oracle using OracleBulkCopy - sql-server

I'm trying to move SQL Server DateTimeOffset or Oracle using OracleBulkCopy.
For DateTimeOffset(1) the target data type is Timestamp(1) With Time Zone.
If I do a select of the DateTimeOffset(1) column on SQL Server I receive the following:
2007-05-08 12:35:29.1 +12:15
When I try to move this to Oracle I receive:
ORA-01843: not a valid month
It makes sense and I believe the month needs to first, but If I run INSERT into MyOracleTable values('2007-05-08 12:35:29.1 +12:15') I can insert just fine.
I've tried Converting the datetimeoffset to various formats on the SQL Server side. I receive various errors one being:
ORA-01855: AM/A.M. or PM/P.M. required
The NLS_TIMESTAMP_TZ_FORMAT parameter is:
YYYY-MM-DD HH24:MI:SSXFF TZR
Thank you for your help!

My idea is to create a wrapper for SqlDataReader and do the conversion inside:
using (SqlConnection conn = new SqlConnection("Data Source=.;Initial Catalog=test;Integrated Security=True;"))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("Select * from table_2", conn))
{
using (SqlDataReader sqlReader = cmd.ExecuteReader())
{
using (ReaderWrapper reader = new ReaderWrapper(sqlReader))
{
using (OracleBulkCopy bulkCopy = new OracleBulkCopy("data source=oracle;user id=user;password=secret", OracleBulkCopyOptions.Default))
{
bulkCopy.DestinationTableName = "tb_date";
bulkCopy.WriteToServer(reader);
}
}
}
}
}
The wrapper itself is bulky, but very simple:
internal sealed class ReaderWrapper : IDataReader
{
#region Fields
private bool _disposed;
private IDataReader _reader;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ReaderWrapper"/> class.
/// </summary>
/// <param name="reader">The wrapped reader.</param>
public ReaderWrapper(IDataReader reader)
{
this._reader = reader;
}
#endregion
#region Properties
/// <summary>
/// Gets the number of columns in the current row.
/// </summary>
/// <value></value>
/// <returns>
/// When not positioned in a valid recordset, 0; otherwise, the number of columns in the current record. The default is -1.
/// </returns>
public int FieldCount
{
get { return this._reader.FieldCount; }
}
#endregion
#region Indexers
/// <summary>
/// Gets the column located at the specified index.
/// </summary>
/// <param name="i">The zero-based index of the column to get.</param>
/// <returns>The column located at the specified index.</returns>
/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="FieldCount"/>.</exception>
public object this[int i]
{
get { return this.GetValue(i); }
}
/// <summary>
/// Gets the column with the specified name.
/// </summary>
/// <param name="name">The name of the column to find.</param>
/// <returns>The column with the specified name.</returns>
/// <exception cref="System.IndexOutOfRangeException">No column with the specified name was found.</exception>
public object this[string name]
{
get { return this._reader[name]; }
}
#endregion
#region Methods
/// <summary>
/// Advances the <see cref="T:System.Data.IDataReader"/> to the next record.
/// </summary>
/// <returns>
/// <see langword="true"/> if there are more rows; otherwise, <see langword="false"/>.
/// </returns>
public bool Read()
{
return this._reader.Read();
}
/// <summary>
/// Return the value of the specified field.
/// </summary>
/// <param name="i">The index of the field to find.</param>
/// <returns>
/// The <see cref="T:System.Object"/> which will contain the field value upon return.
/// </returns>
/// <exception cref="T:System.IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"/>.
/// </exception>
public object GetValue(int i)
{
object sqlValue = this._reader[i];
DateTimeOffset? dateValue = sqlValue as DateTimeOffset?;
if (dateValue == null)
{
return sqlValue;
}
// Ensure that DateTimeOffset can be converted to Oracle
OracleTimeStampTZ oracleDate = new OracleTimeStampTZ(dateValue.Value.UtcDateTime, dateValue.Value.Offset.ToString());
return oracleDate;
}
/// <summary>
/// Gets the <see cref="T:System.Type" /> information corresponding to the type of <see cref="T:System.Object" /> that would be returned from <see cref="M:System.Data.IDataRecord.GetValue(System.Int32)" />.
/// </summary>
/// <param name="i">The index of the field to find.</param>
/// <returns>
/// The <see cref="T:System.Type" /> information corresponding to the type of <see cref="T:System.Object" /> that would be returned from <see cref="M:System.Data.IDataRecord.GetValue(System.Int32)" />.
/// </returns>
/// <exception cref="NotSupportedException"></exception>
Type IDataRecord.GetFieldType(int i)
{
return this._reader.GetFieldType(i);
}
public int GetOrdinal(string name)
{
return this._reader.GetOrdinal(name);
}
public bool IsDBNull(int i)
{
return this.IsDBNull(i);
}
/// <summary>
/// Closes the <see cref="T:System.Data.IDataReader"/> Object.
/// </summary>
public void Close()
{
this._reader.Dispose();
}
/// <summary>
/// Performs application-defined tasks associated with freeing,
/// releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
if (!this._disposed)
{
this._reader.Dispose();
this._disposed = true;
}
}
#endregion
#region Not Implemented
bool IDataReader.IsClosed
{
get { throw new NotSupportedException(); }
}
void IDataReader.Close()
{
throw new NotSupportedException();
}
int IDataReader.Depth
{
get { throw new NotSupportedException(); }
}
DataTable IDataReader.GetSchemaTable()
{
throw new NotSupportedException();
}
bool IDataReader.NextResult()
{
throw new NotSupportedException();
}
int IDataReader.RecordsAffected
{
get { throw new NotSupportedException(); }
}
bool IDataRecord.GetBoolean(int i)
{
throw new NotSupportedException();
}
byte IDataRecord.GetByte(int i)
{
throw new NotSupportedException();
}
long IDataRecord.GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
{
throw new NotSupportedException();
}
char IDataRecord.GetChar(int i)
{
throw new NotSupportedException();
}
long IDataRecord.GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
{
throw new NotSupportedException();
}
IDataReader IDataRecord.GetData(int i)
{
throw new NotSupportedException();
}
string IDataRecord.GetDataTypeName(int i)
{
throw new NotSupportedException();
}
DateTime IDataRecord.GetDateTime(int i)
{
throw new NotSupportedException();
}
decimal IDataRecord.GetDecimal(int i)
{
throw new NotSupportedException();
}
double IDataRecord.GetDouble(int i)
{
throw new NotSupportedException();
}
float IDataRecord.GetFloat(int i)
{
throw new NotSupportedException();
}
Guid IDataRecord.GetGuid(int i)
{
throw new NotSupportedException();
}
short IDataRecord.GetInt16(int i)
{
throw new NotSupportedException();
}
int IDataRecord.GetInt32(int i)
{
throw new NotSupportedException();
}
long IDataRecord.GetInt64(int i)
{
throw new NotSupportedException();
}
string IDataRecord.GetName(int i)
{
throw new NotSupportedException();
}
string IDataRecord.GetString(int i)
{
throw new NotSupportedException();
}
int IDataRecord.GetValues(object[] values)
{
throw new NotSupportedException();
}
#endregion
The actual conversion is done here:
public object GetValue(int i)
{
object sqlValue = this._reader[i];
DateTimeOffset? dateValue = sqlValue as DateTimeOffset?;
if (dateValue == null)
{
return sqlValue;
}
// Ensure that DateTimeOffset can be converted to Oracle
OracleTimeStampTZ oracleDate = new OracleTimeStampTZ(dateValue.Value.UtcDateTime, dateValue.Value.Offset.ToString());
return oracleDate;
}

Related

WPF ListView with large collection hangs GUI

++++++ Link to example project ++++++
I have a file that can contain thousands of lines of logged messages. I am parsing this file and adding each line (as a log event) to a collection. This collection should then be shown in a ListView.
As below:
<ListView
Grid.Row="0"
Margin="5"
ItemsSource="{Binding SelectedSerilogFileLog.LogEvents}"
ScrollViewer.CanContentScroll="False"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedItem="{Binding SelectedLogEvent}"
SelectionMode="Single">
The parsing of the file (this one contains 2500+ log events) and adding to the collection takes around 100ms. Then when the bound collection is updated with the ReplaceContent method (this suppresses the collectionchanged event firing on every item added) the GUI hangs, but I cannot see why or what can be causing this.
MainWindow.cs
...
/// <summary>
///
/// </summary>
public SerilogFileLog SelectedSerilogFileLog
{
get => selectedSerilogFileLog; set
{
if (selectedSerilogFileLog != null)
{
SelectedSerilogFileLog.OnSerilogParserFinished -= OnSerilogParserFinished;
SelectedSerilogFileLog.OnSerilogParserProgressChanged -= OnSerilogParserProgressChanged;
}
selectedSerilogFileLog = value;
if (selectedSerilogFileLog != null)
{
ParserProgress = 0;
SelectedSerilogFileLog.OnSerilogParserFinished += OnSerilogParserFinished;
SelectedSerilogFileLog.OnSerilogParserProgressChanged += OnSerilogParserProgressChanged;
sw.Start();
SelectedSerilogFileLog.Parse();
}
NotifyPropertyChanged(nameof(SelectedSerilogFileLog));
}
}
...
private void Button_Click(object sender, RoutedEventArgs e)
{
SelectedSerilogFileLog = null;
SelectedSerilogFileLog = new SerilogFileLog() { FilePath = "Application20210216.log" };
}
The parsing and loading of the items occurs in a separate Task.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace LargeListViewTest.Classes
{
public class SerilogFileLog : INotifyPropertyChanged
{
private LogEvent lastLogEvent;
private ObservableCollectionEx<LogEvent> logEvents;
private string name;
private string description;
private string filePath;
private Regex patternMatching;
private string matchExpression = #"^(?<DateTime>[^|]+)\| (?<Level>[^|]+) \| (?<MachineName>[^|]+) \| (?<Source>[^|]+) \| (?<Message>[^$]*)$";
public delegate void SerilogParserProgressHandler(int Percentage);
public delegate void SerilogParserFinishedHandler();
/// <summary>
///
/// </summary>
public event SerilogParserProgressHandler OnSerilogParserProgressChanged;
/// <summary>
///
/// </summary>
public event SerilogParserFinishedHandler OnSerilogParserFinished;
/// <summary>
/// Gets or sets the LogEvents.
/// </summary>
public ObservableCollectionEx<LogEvent> LogEvents { get => logEvents; private set { logEvents = value; NotifyPropertyChanged(nameof(LogEvents)); } }
/// <summary>
/// Gets or sets the Name.
/// </summary>
public string Name { get => name; private set { name = value; NotifyPropertyChanged(nameof(Name)); } }
/// <summary>
/// Gets or sets the Description.
/// </summary>
public string Description { get => description; private set { description = value; NotifyPropertyChanged(nameof(Description)); } }
/// <summary>
/// Gets or sets the FilePath.
/// </summary>
public string FilePath
{
get => filePath;
set
{
filePath = value;
Name = Path.GetFileNameWithoutExtension(value);
Description = FilePath;
}
}
/// <summary>
///
/// </summary>
public SerilogFileLog()
{
LogEvents = new ObservableCollectionEx<LogEvent>();
patternMatching = new Regex(matchExpression, RegexOptions.Singleline | RegexOptions.Compiled);
}
/// <summary>
///
/// </summary>
public void Parse()
{
Task task = Task.Factory.StartNew(() => { InternalParse(); });
}
/// <summary>
///
/// </summary>
private void InternalParse()
{
OnSerilogParserProgressChanged?.Invoke(0);
try
{
if (!string.IsNullOrWhiteSpace(FilePath))
{
Console.WriteLine("Starting parse for {0}", FilePath);
long currentLength = 0;
FileInfo fi = new FileInfo(FilePath);
if (fi.Exists)
{
Console.WriteLine("Parsing Serilog file: {0}.", FilePath);
fi.Refresh();
List<LogEvent> parsedLogEvents = new List<LogEvent>();
StringBuilder sb = new StringBuilder();
using (FileStream fileStream = fi.Open(FileMode.Open, FileAccess.Read, FileShare.Write))
using (var streamReader = new StreamReader(fileStream))
{
while (streamReader.Peek() != -1)
{
sb.Append(streamReader.ReadLine());
LogEvent newLogEvent = ParseLogEvent(sb.ToString());
if (newLogEvent != null)
{
parsedLogEvents.Add(newLogEvent);
lastLogEvent = newLogEvent;
}
OnSerilogParserProgressChanged?.Invoke((int)(currentLength * 100 / fi.Length));
currentLength = currentLength + sb.ToString().Length;
sb.Clear();
}
}
LogEvents.ReplaceContent(parsedLogEvents);
}
Console.WriteLine("Finished parsing Serilog {0}.", FilePath);
}
}
catch (Exception ex)
{
Console.WriteLine("Error parsing Serilog." + ex.Message);
}
OnSerilogParserProgressChanged?.Invoke(100);
SerilogParserFinishedHandler onSerilogParserFinished = OnSerilogParserFinished;
if (onSerilogParserFinished == null)
return;
OnSerilogParserFinished();
}
/// <summary>
///
/// </summary>
/// <param name="mes"></param>
/// <returns></returns>
private LogEvent ParseLogEvent(string mes)
{
LogEvent logEvent = new LogEvent();
Match matcher = patternMatching.Match(mes);
try
{
if (matcher.Success)
{
logEvent.Message = matcher.Groups["Message"].Value;
DateTime dt;
if (!DateTime.TryParse(matcher.Groups["DateTime"].Value, out dt))
{
Console.WriteLine("Failed to parse date {Value}", matcher.Groups["DateTime"].Value);
}
logEvent.DateTime = dt;
logEvent.Level = matcher.Groups["Level"].Value;
logEvent.MachineName = matcher.Groups["MachineName"].Value;
logEvent.Source = matcher.Groups["Source"].Value;
}
else
{
if ((string.IsNullOrEmpty(mes) || (!Char.IsDigit(mes[0])) || !Char.IsDigit(mes[1])) && lastLogEvent != null)
{
// seems to be a continuation of the previous line, add it to the last event.
lastLogEvent.Message += Environment.NewLine;
lastLogEvent.Message += mes;
logEvent = null;
}
else
{
Console.WriteLine("Message parsing failed.");
}
if (logEvent != null)
logEvent.Message = mes;
}
}
catch (Exception ex)
{
Console.WriteLine("ParseLogEvent exception." + ex.Message);
}
return logEvent;
}
#region INotify
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string p) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p));
#endregion
}
}
I have an ObservableCollectionEx class that extends the default ObservableCollection, this class suppresses the collection changed event until all the items have been added/replaced.
/// <summary>
/// Adds the supplied items to the collection and raises a single <see cref="CollectionChanged"/> event
/// when the operation is complete.
/// </summary>
/// <param name="items">The items to add.</param>
public void AddRange(IEnumerable<T> items, bool notifyAfter = true)
{
if (null == items)
{
throw new ArgumentNullException("items");
}
if (items.Any())
{
try
{
SuppressChangeNotification();
CheckReentrancy();
foreach (var item in items)
{
Add(item);
}
}
finally
{
if (notifyAfter)
FireChangeNotification();
suppressOnCollectionChanged = false;
}
}
}
/// <summary>
/// Replaces the content of the collection with the supplied items and raises a single <see cref="CollectionChanged"/> event
/// when the operation is complete.
/// </summary>
/// <param name="items">The items to replace the current content.</param>
public void ReplaceContent(IEnumerable<T> items)
{
SuppressChangeNotification();
ClearItems();
AddRange(items);
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!suppressOnCollectionChanged)
{
#if NoCrossThreadSupport
base.OnCollectionChanged(e);
#else
using (BlockReentrancy())
{
NotifyCollectionChangedEventHandler eventHandler = CollectionChanged;
if (eventHandler == null)
return;
Delegate[] delegates = eventHandler.GetInvocationList();
// Walk the invocation list
foreach (NotifyCollectionChangedEventHandler handler in delegates)
{
DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
// If the subscriber is a DispatcherObject and different thread
if (dispatcherObject != null && !dispatcherObject.CheckAccess())
{
// Invoke handler in the target dispatcher's thread
dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, this, e);
}
else // Execute handler as is
handler(this, e);
}
}
#endif
}
}
I have tried using a List but I got the same behaviour.
Any ideas?

How determine fault StoryBoard

The initial problem is enough known - "Cannot animate '(0).(1)' on an immutable object instance".
There are many questions here in SO about it but all the solutions are more fixes or crutches. And most of questions are linked to concrete part of code.
Also there are few topics about this problem with possible causes:
https://wpftutorial.net/DebuggingAnimations.html
https://blogs.msdn.microsoft.com/mikehillberg/2006/09/25/tip-cannot-animate-on-an-immutable-object-instance/
I have huge corporate app where a have hundreds styles and storyboards. I can't disable them step by step and it's painstaking work to looking for problem part of code.
I look at these bug not from side of looking for in many xamls but from side of loging. I tried to research info in InvalidOperationException that is raised but there is no useful info like control place in xaml or smth else.
Also one idea is to create class inherited from Storyboard and to override methods.
But there is no methods to override.
Can someone propose how to log the internality of storyboard or other class that is responsible of animation?
At last I found sulution.
You should add classes: listener to animation, AttachedProperty and custom StoryBoard.
public static class TriggerTracing
{
static TriggerTracing()
{
// Initialise WPF Animation tracing and add a TriggerTraceListener
PresentationTraceSources.Refresh();
PresentationTraceSources.AnimationSource.Listeners.Clear();
PresentationTraceSources.AnimationSource.Listeners.Add(new TriggerTraceListener());
PresentationTraceSources.AnimationSource.Switch.Level = SourceLevels.All;
}
#region TriggerName attached property
/// <summary>
/// Gets the trigger name for the specified trigger. This will be used
/// to identify the trigger in the debug output.
/// </summary>
/// <param name="trigger">The trigger.</param>
/// <returns></returns>
public static string GetTriggerName(TriggerBase trigger)
{
return (string)trigger.GetValue(TriggerNameProperty);
}
/// <summary>
/// Sets the trigger name for the specified trigger. This will be used
/// to identify the trigger in the debug output.
/// </summary>
/// <param name="trigger">The trigger.</param>
/// <returns></returns>
public static void SetTriggerName(TriggerBase trigger, string value)
{
trigger.SetValue(TriggerNameProperty, value);
}
public static readonly DependencyProperty TriggerNameProperty =
DependencyProperty.RegisterAttached(
"TriggerName",
typeof(string),
typeof(TriggerTracing),
new UIPropertyMetadata(string.Empty));
#endregion
#region TraceEnabled attached property
/// <summary>
/// Gets a value indication whether trace is enabled for the specified trigger.
/// </summary>
/// <param name="trigger">The trigger.</param>
/// <returns></returns>
public static bool GetTraceEnabled(TriggerBase trigger)
{
return (bool)trigger.GetValue(TraceEnabledProperty);
}
/// <summary>
/// Sets a value specifying whether trace is enabled for the specified trigger
/// </summary>
/// <param name="trigger"></param>
/// <param name="value"></param>
public static void SetTraceEnabled(TriggerBase trigger, bool value)
{
trigger.SetValue(TraceEnabledProperty, value);
}
public static readonly DependencyProperty TraceEnabledProperty =
DependencyProperty.RegisterAttached(
"TraceEnabled",
typeof(bool),
typeof(TriggerTracing),
new UIPropertyMetadata(false, OnTraceEnabledChanged));
private static void OnTraceEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var triggerBase = d as EventTrigger;
if (triggerBase == null)
return;
if (!(e.NewValue is bool))
return;
if ((bool)e.NewValue)
{
// insert dummy story-boards which can later be traced using WPF animation tracing
var storyboard = new TriggerTraceStoryboard(triggerBase, TriggerTraceStoryboardType.Enter);
triggerBase.Actions.Insert(0, new BeginStoryboard() { Storyboard = storyboard });
//storyboard = new TriggerTraceStoryboard(triggerBase, TriggerTraceStoryboardType.Exit);
//triggerBase.ExitActions.Insert(0, new BeginStoryboard() { Storyboard = storyboard });
}
else
{
// remove the dummy storyboards
//foreach (TriggerActionCollection actionCollection in new[] { triggerBase.EnterActions, triggerBase.ExitActions })
foreach (TriggerActionCollection actionCollection in new[] { triggerBase.Actions })
{
foreach (TriggerAction triggerAction in actionCollection)
{
BeginStoryboard bsb = triggerAction as BeginStoryboard;
if (bsb != null && bsb.Storyboard != null && bsb.Storyboard is TriggerTraceStoryboard)
{
actionCollection.Remove(bsb);
break;
}
}
}
}
}
#endregion
private enum TriggerTraceStoryboardType
{
Enter, Exit
}
/// <summary>
/// A dummy storyboard for tracing purposes
/// </summary>
private class TriggerTraceStoryboard : Storyboard
{
public TriggerTraceStoryboardType StoryboardType { get; private set; }
public TriggerBase TriggerBase { get; private set; }
public TriggerTraceStoryboard(TriggerBase triggerBase, TriggerTraceStoryboardType storyboardType)
{
TriggerBase = triggerBase;
StoryboardType = storyboardType;
}
}
/// <summary>
/// A custom tracelistener.
/// </summary>
private class TriggerTraceListener : TraceListener
{
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
{
base.TraceEvent(eventCache, source, eventType, id, format, args);
if (format.StartsWith("Storyboard has begun;"))
{
TriggerTraceStoryboard storyboard = args[1] as TriggerTraceStoryboard;
if (storyboard != null)
{
// add a breakpoint here to see when your trigger has been
// entered or exited
// the element being acted upon
object targetElement = args[5];
// the namescope of the element being acted upon
INameScope namescope = (INameScope)args[7];
TriggerBase triggerBase = storyboard.TriggerBase;
string triggerName = GetTriggerName(storyboard.TriggerBase);
var str = "";
var element = targetElement as DependencyObject;
while (element != null)
{
str += element.ToString() + Environment.NewLine;
element = VisualTreeHelper.GetParent(element);
}
LoggingInfrastructure.DefaultLogger.Log(...);
}
}
}
public override void Write(string message)
{
}
public override void WriteLine(string message)
{
}
}
Then you could add property to xaml where you need:
<EventTrigger Ui:TriggerTracing.TriggerName="CopyTextBlockStyle PreviewMouseLeftButtonDown"
Ui:TriggerTracing.TraceEnabled="True" RoutedEvent="PreviewMouseLeftButtonDown">

MVVM bind RelayCommand CanExecute to a Property?

I have a Timer and three buttons to control it: Start, Stop, and Pause.
Each button is bound to a RelayCommand.
I have a TimerState property of type enum TimerState. (This is useful for setting various GUI elements.)
Is there a way to somehow bind the RelayCommands' CanExecute functionality to the TimerState property?
Currently, I have 3 methods that look like this:
private bool CanStartTimer()
{
return (TimerState == TimerState.Stopped || TimerState == TimerState.Paused);
}
In the TimerState setter, I call
StartTimerCmd.RaiseCanExecuteChanged();
Is there a better way bind the CanExecute state of the RelayCommands to a property like TimerState?
Thanks for any insight.
I've implemented a class to handle commands, actually it's based on DelegateCommand because i'm using PRISM but it could easily be changed to be used with RelayCommand or any other class implementing ICommand
It could have bugs, i've not yet fully tested it, however it works fine in my scenarios, here it is:
public class MyDelegateCommand<TViewModel> : DelegateCommand where TViewModel : INotifyPropertyChanged {
private List<string> _PropertiesToWatch;
public MyDelegateCommand(TViewModel viewModelInstance, Action executedMethod)
: base(executedMethod) {
}
public MyDelegateCommand(TViewModel viewModelInstance, Action executedMethod, Func<bool> canExecuteMethod)
: base(executedMethod, canExecuteMethod) {
}
/// <summary>
///
/// </summary>
/// <param name="viewModelInstance"></param>
/// <param name="executedMethod"></param>
/// <param name="selector"></param>
public MyDelegateCommand(TViewModel viewModelInstance, Action executedMethod, Func<bool> canExecuteMethod, Expression<Func<TViewModel, object>> propertiesToWatch)
: base(executedMethod, canExecuteMethod) {
_PropertiesToWatch = RegisterPropertiesWatcher(propertiesToWatch);
viewModelInstance.PropertyChanged += PropertyChangedHandler;
}
/// <summary>
/// handler that, everytime a monitored property changes, calls the RaiseCanExecuteChanged of the command
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) {
if (_PropertiesToWatch.Contains(e.PropertyName)) {
this.OnCanExecuteChanged();
}
}
/// <summary>
/// giving an expression that identify a propriety or a list of properties, return the property names obtained from the expression
/// Examples on selector usage
/// proprietà singola:
/// entity => entity.PropertyName
/// proprietà multiple
/// entity => new { entity.PropertyName1, entity.PropertyName2 }
/// </summary>
/// <param name="selector"></param>
/// <returns></returns>
protected List<string> RegisterPropertiesWatcher(Expression<Func<TViewModel, object>> selector) {
List<string> properties = new List<string>();
System.Linq.Expressions.LambdaExpression lambda = (System.Linq.Expressions.LambdaExpression)selector;
if (lambda.Body is System.Linq.Expressions.MemberExpression) {
System.Linq.Expressions.MemberExpression memberExpression = (System.Linq.Expressions.MemberExpression)(lambda.Body);
properties.Add(memberExpression.Member.Name);
}
else if (lambda.Body is System.Linq.Expressions.UnaryExpression) {
System.Linq.Expressions.UnaryExpression unaryExpression = (System.Linq.Expressions.UnaryExpression)(lambda.Body);
properties.Add(((System.Linq.Expressions.MemberExpression)(unaryExpression.Operand)).Member.Name);
}
else if (lambda.Body.NodeType == ExpressionType.New) {
NewExpression newExp = (NewExpression)lambda.Body;
foreach (var argument in newExp.Arguments) {
if (argument is System.Linq.Expressions.MemberExpression) {
System.Linq.Expressions.MemberExpression mExp = (System.Linq.Expressions.MemberExpression)argument;
properties.Add(mExp.Member.Name);
}
else {
throw new SyntaxErrorException("Syntax Error, selector has to be an expression that returns a new object containing a list of properties, e.g.: s => new { s.Property1, s.Property2 }");
}
}
}
else {
throw new SyntaxErrorException("Syntax Error, selector has to be an expression that returns a new object containing a list of properties, e.g.: s => new { s.Property1, s.Property2 }");
}
return properties;
}
}
note that my solution implies that this command has to be wired with the viewmodel that handle it and the viewmodel has to implement the INotifyPropertyChanged interface.
first two constructor are there so the command is backward compatible with DelegateCommand but the 3rd is the important one, that accepts a linq expression to specify which property to monitor
the usage is pretty simple and easy to understand, let me write it here with methods but of course you can create your handler methods. Suppose you have have a ViewModel called MyViewModel with two properties (PropertyX and PropertyY) that rise the propertychanged event, and somewhere in it you create an instance of SaveCommand, it would look like this:
this.SaveCommand = new MyDelegateCommand<MyViewModel>(this,
//execute
() => {
Console.Write("EXECUTED");
},
//can execute
() => {
Console.Write("Checking Validity");
return PropertyX!=null && PropertyY!=null && PropertyY.Length < 5;
},
//properties to watch
(p) => new { p.PropertyX, p.PropertyY }
);
maybe i'll create an article somewhere about this, but this snippet should be clear i hope
Fabio's answer works well. Here's a parameterized version for DelegateCommand<T>. (I've tightened up the code a little, too.)
public class DepedencyCommand<TViewModel, TArg> : DelegateCommand<TArg>
where TViewModel : INotifyPropertyChanged
{
private readonly List<string> _propertiesToWatch;
public DepedencyCommand(Action<TArg> executedMethod)
: base(executedMethod) { }
public DepedencyCommand(Action<TArg> executedMethod, Func<TArg, bool> canExecuteMethod)
: base(executedMethod, canExecuteMethod) { }
public DepedencyCommand(TViewModel viewModelInstance, Action<TArg> executedMethod, Func<TArg, bool> canExecuteMethod, Expression<Func<TViewModel, object>> propertiesToWatch)
: base(executedMethod, canExecuteMethod)
{
_propertiesToWatch = _RegisterPropertiesWatcher(propertiesToWatch);
viewModelInstance.PropertyChanged += PropertyChangedHandler;
}
/// <summary>
/// handler that, everytime a monitored property changes, calls the RaiseCanExecuteChanged of the command
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
if (_propertiesToWatch.Contains(e.PropertyName))
{
this.OnCanExecuteChanged();
}
}
/// <summary>
/// giving an expression that identify a propriety or a list of properties, return the property names obtained from the expression
/// Examples on selector usage
/// proprietà singola:
/// entity => entity.PropertyName
/// proprietà multiple
/// entity => new { entity.PropertyName1, entity.PropertyName2 }
/// </summary>
/// <param name="selector"></param>
/// <returns></returns>
private static List<string> _RegisterPropertiesWatcher(Expression<Func<TViewModel, object>> selector)
{
var properties = new List<string>();
LambdaExpression lambda = selector;
if (lambda.Body is MemberExpression)
{
var memberExpression = (MemberExpression)lambda.Body;
properties.Add(memberExpression.Member.Name);
}
else if (lambda.Body is UnaryExpression)
{
var unaryExpression = (UnaryExpression)lambda.Body;
properties.Add(((MemberExpression)unaryExpression.Operand).Member.Name);
}
else if (lambda.Body.NodeType == ExpressionType.New)
{
var newExp = (NewExpression)lambda.Body;
foreach (var argument in newExp.Arguments)
{
if (argument is MemberExpression)
{
MemberExpression mExp = (MemberExpression)argument;
properties.Add(mExp.Member.Name);
}
else
throw new SyntaxErrorException("Syntax Error, selector has to be an expression that returns a new object containing a list of properties, e.g.: s => new { s.Property1, s.Property2 }");
}
}
else
throw new SyntaxErrorException("Syntax Error, selector has to be an expression that returns a new object containing a list of properties, e.g.: s => new { s.Property1, s.Property2 }");
return properties;
}
}
There doesn't seem to be a better solution. I know what you mean, it seems inelegant but whatever lipstick you put on it, the onus is on the objects involved in the expression to notify the command.
If your condition is based purely on other notify properties you could add your own handler to PropertyChanged, that provides a bit of abstraction.
In this case, TimerState would be a VM property. Then you can a handler to your ViewModel.PropertyChanged event. You can then inspect the property name and update your CanExecute. This is still ugly, but at least you have all the garbage in one block.

Entity Framework 4 - Notification on Associations

I am facing a few difficulties with EF 4, foreign keys and INotifyPropertyChanged / the partial methods exposed for scalar properties.
I hope you can help me find the right way to do this.
Image I have a Customer entity with *..1 relationship with the Country entity.
Now, I'd obviously like to be able to do:
var customer = new Customer();
customer.Country = [...]
...but I don't necessarily need the CountryKey property.
I create a Association in EF with the correct cardinality in the .edmx designer. I choose not to "add foreign key properties" in the dialog.
This leaves me with a generated class without the partial OnCountryChanging and OnCountryChanged.
Next, I try to add the foreign key properties, and I now have a OnCountryKeyChanging and OnCountryKeyChanged.
However, the generated code looks like this:
/// <summary>
/// No Metadata Documentation available.
/// </summary>
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.Int64 CountryKey
{
get
{
return _CountryKey;
}
set
{
OnCountryKeyChanging(value);
ReportPropertyChanging("CountryKey");
_CountryKey = StructuralObject.SetValidValue(value);
ReportPropertyChanged("CountryKey");
OnCountryKeyChanged();
}
}
private global::System.Int64 _CountryKey;
partial void OnCountryKeyChanging(global::System.Int64 value);
partial void OnCountryKeyChanged();
As you can see from the generated code, the PropertyChanged notification occurs with "CountryKey" instead of "Country". This makes data binding in WPF difficult.
My question is: how do I get around this?
Do I wrap my object in a ViewModel, listen to property changes and strip the "Key" part?
Do I modify the T4 template?
Or is there a third option I just can't see yet?
I'd greatly appreciate any suggestions here, as I am experimenting with WPF / EF without wrapping each Model property in a ViewModel.
The 'best practices' approach is to decorate your model in a viewmodel, exposing the model properties as required. You can create a generic ViewModel with some nifty work with dynamicobject, using perhaps a property mapping etc.
public class DynamicViewModel : DynamicObject, INotifyPropertyChanged, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;
private static readonly Dictionary<Type, Tuple<Dictionary<string, PropertyInfo>, Dictionary<string, string>>> typeDictionary = new Dictionary<Type, Tuple<Dictionary<string, PropertyInfo>, Dictionary<string, string>>>();
private readonly Dictionary<string, object> additionalProperties = new Dictionary<string, object>();
private readonly object underlyingObject;
public object UnderlyingObject
{
get
{
return underlyingObject;
}
}
private readonly Type type;
/// <summary>
/// constructor which takes a model for which it will be extensing its perceived properties
/// </summary>
/// <param name="underlyingObject">the underlying object</param>
public DynamicViewModel(IBindableRfqViewModel underlyingObject) : this(underlyingObject, new Dictionary<string, string>())
{
}
/// <summary>
/// constructor which takes a model for which it will be extensing its perceived properties as well as a property map
/// </summary>
/// <param name="underlyingObject">the underlying object</param>
/// <param name="propertyMap">a string/string dictionary, where the key is a property on the underlying object, and the value is the name of the dynamic property to be used as a binding target</param>
public DynamicViewModel(IBindableRfqViewModel underlyingObject, Dictionary<string, string> propertyMap)
{
this.underlyingObject = underlyingObject;
if (underlyingObject is INotifyPropertyChanged)
{
((INotifyPropertyChanged)underlyingObject).PropertyChanged += OnUnderlyingPropertyChanged;
}
type = underlyingObject.GetType();
if (typeDictionary.ContainsKey(type))
{
return;
}
lock (typeDictionary)
{
if (typeDictionary.ContainsKey(type))
{
return;
}
var forwardPropertyMap = propertyMap;
var typeProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToDictionary(p => p.Name, p => p);
typeDictionary.Add(type, Tuple.Create(typeProperties,forwardPropertyMap));
}
}
private void OnUnderlyingPropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.OnPropertyChanged(e.PropertyName);
}
private bool TryGetProperty(string name, out object result)
{
try
{
var propertyData = typeDictionary[type];
var modelProperty = name;
if (propertyData.Item2.ContainsKey(name))
{
modelProperty = propertyData.Item2[name];
}
if (propertyData.Item1.ContainsKey(modelProperty))
{
result = propertyData.Item1[modelProperty].GetValue(underlyingObject, null);
return true;
}
if (additionalProperties.ContainsKey(name))
{
result = additionalProperties[name];
return true;
}
result = null;
return true;
}
catch (Exception ex)
{
result = null;
return false;
}
}
/// <summary>
/// <see cref="DynamicObject.TryGetMember" />
/// </summary>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return this.TryGetProperty(binder.Name, out result);
}
/// <summary>
/// <see cref="DynamicObject.TryGetIndex" />
/// </summary>
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
return this.TryGetProperty(indexes[0].ToString(), out result);
}
private bool TrySetProperty(string name, object value)
{
try
{
var propertyData = typeDictionary[type];
var modelProperty = name;
if (propertyData.Item2.ContainsKey(name))
{
modelProperty = propertyData.Item2[name];
}
if (propertyData.Item1.ContainsKey(modelProperty))
{
propertyData.Item1[modelProperty].SetValue(underlyingObject, value, null);
}
else
{
if (!additionalProperties.ContainsKey(name))
{
additionalProperties.Add(name, new object());
}
additionalProperties[name] = value;
}
this.OnPropertyChanged(name);
return true;
}
catch (Exception ex)
{
return false;
}
}
/// <summary>
/// <see cref="DynamicObject.TrySetMember" />
/// </summary>
public override bool TrySetMember(SetMemberBinder binder, object value)
{
return this.TrySetProperty(binder.Name, value);
}
/// <summary>
/// <see cref="DynamicObject.TrySetIndex" />
/// </summary>
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
{
return indexes.Length == 0 || this.TrySetProperty(indexes[0].ToString(), value);
}
private void OnPropertyChanged(string propName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
/// <summary>
/// IDisposable implementation
/// </summary>
public void Dispose()
{
if (underlyingObject is INotifyPropertyChanged)
{
((INotifyPropertyChanged)underlyingObject).PropertyChanged -= OnUnderlyingPropertyChanged;
}
if (underlyingObject is IDisposable)
{
((IDisposable)underlyingObject).Dispose();
}
}
}

Validating method arguments with Data Annotation attributes

The "Silverlight Business Application" template bundled with VS2010 / Silverlight 4 uses DataAnnotations on method arguments in its domain service class, which are invoked automagically:
public CreateUserStatus CreateUser(RegistrationData user,
[Required(ErrorMessageResourceName = "ValidationErrorRequiredField", ErrorMessageResourceType = typeof(ValidationErrorResources))]
[RegularExpression("^.*[^a-zA-Z0-9].*$", ErrorMessageResourceName = "ValidationErrorBadPasswordStrength", ErrorMessageResourceType = typeof(ValidationErrorResources))]
[StringLength(50, MinimumLength = 7, ErrorMessageResourceName = "ValidationErrorBadPasswordLength", ErrorMessageResourceType = typeof(ValidationErrorResources))]
string password)
{ /* do something */ }
If I need to implement this in my POCO class methods, how do I get the framework to invoke the validations OR how do I invoke the validation on all the arguments imperatively (using Validator or otherwise?).
We have approached it like this:
We have a ValidationProperty class that takes in a RegularExpression that will be used to validate the value (you could use whatever you wanted).
ValidationProperty.cs
public class ValidationProperty
{
#region Constructors
/// <summary>
/// Constructor for property with validation
/// </summary>
/// <param name="regularExpression"></param>
/// <param name="errorMessage"></param>
public ValidationProperty(string regularExpression, string errorMessage)
{
RegularExpression = regularExpression;
ErrorMessage = errorMessage;
IsValid = true;
}
#endregion
#region Properties
/// <summary>
/// Will be true if this property is currently valid
/// </summary>
public bool IsValid { get; private set; }
/// <summary>
/// The value of the Property.
/// </summary>
public object Value
{
get { return val; }
set
{
if (this.Validate(value))//if valid, set it
{
val = value;
}
else//not valid, throw exception
{
throw new ValidationException(ErrorMessage);
}
}
}
private object val;
/// <summary>
/// Holds the regular expression that will accept a vaild value
/// </summary>
public string RegularExpression { get; private set; }
/// <summary>
/// The error message that will be thrown if invalid
/// </summary>
public string ErrorMessage { get; private set; }
#endregion
#region Private Methods
private bool Validate(object myValue)
{
if (myValue != null)//Value has been set, validate it
{
this.IsValid = Regex.Match(myValue.ToString(), this.RegularExpression).Success;
}
else//still valid if it has not been set. Invalidation of items not set needs to be handled in the layer above this one.
{
this.IsValid = true;
}
return this.IsValid;
}
#endregion
}
Here's how we would create a Validation property. Notice how the public member is a string, but privately I am using a 'ValidationProperty.'
public string TaskNumber
{
get { return taskNumber.Value.ToString(); }
set
{
taskNumber.Value = value;
OnPropertyChanged("TaskNumber");
}
}
private ValidationProperty taskNumber;
Now, whenever the value is set, the business layer will validate that it's a valid value. If it's not, it will simply throw a new ValidationException (in the ValidationProperty class). In your xaml, you will need to set NotifyOnValidationError & ValidatesOnExceptions to true.
<TextBox Text="{Binding TaskNumber, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}"/>
With this approach, you would probably have a form for creating a new 'User' and each field would valitate each time they set it (I hope that makes sense).
This is the approach that we used to get the validation to be on the business layer. I'm not sure if this is exactly what you're looking for, but I hope it helps.

Resources