1

I am passing information between a SQL database and a PLC using 3rd party OPC libraries.

There are essentially two transactions.

Information passed from the PLC to the SQL server is statically typed. Very specific data is captured by the PLC and passed to the SQL database.

Information passed from the SQL server to the PLC is dynamically typed and may be limited to a single property or hundreds.

ITransaction.cs

public interface ITransaction : INotifyPropertyChanged
{
    short Response { get; set; }

    bool Request { get; set; }

    void Reset();
}

BaseTransaction.cs

internal abstract class BaseTransaction<T> : IDisposable
    where T : class, INotifyPropertyChanged
{
    private T _opcClient;

    protected T OpcClient
    {
        get { return _opcClient; }
        set
        {
            if (_opcClient != value)
            {
                OnOpcClientChanging();
                _opcClient = value;
                OnOpcClientChanged();
            }
        }
    }

    protected abstract void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e);

    private void OnOpcClientChanged()
    {
        if (_opcClient != null)
        {
            _opcClient.PropertyChanged += OnOpcClientPropertyChanged;

            OpcManager = new OpcManager(_opcClient);
        }
    }

    private void OnOpcClientChanging()
    {
        if (_opcClient != null)
            _opcClient.PropertyChanged -= OnOpcClientPropertyChanged;
    }
}

StaticTransaction.cs

internal abstract class StaticTransaction<T> : BaseTransaction<T>
    where T : class, ITransaction, new()
{
    public StaticTransaction()
    {
        OpcClient = new T();
    }

    protected override void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case "Response":
                ProcessResponse(OpcClient.Response);
                break;
            case "Request":
                ProcessRequest(OpcClient.Request);
                break;
        }
    }
}

DynamicTransaction.cs

internal abstract class DynamicTransaction : BaseTransaction<ExpandoObject>
{
    protected new dynamic OpcClient
    {
        get { return base.OpcClient as dynamic; }
    }

    public DynamicTransaction()
    {
        dynamic opcClient = new ExpandoObject();

        opcClient.Request = false;
        opcClient.Response = 0;

        // Access database, use IDictionary interface to add properties to ExpandoObject.

        opcClient.Reset = new Action(Reset);

        base.OpcClient = opcClient;
    }

    protected override void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case "Response":
                ProcessResponse(OpcClient.Response);
                break;
            case "Request":
                ProcessRequest(OpcClient.Request);
                break;
        }
    }

    private void Reset()
    {
        // Use IDictionary interface to reset dynamic properties to defaults.

        OpcClient.Request = false;
        OpcClient.Response = 0;
    }
}

As shown both StaticTransaction and DynamicTransaction have identical implementations of OnOpcClientPropertyChanged among other methods not shown. I would like to bring OnOpcClientPropertyChanged and the other methods into the base class but am prevented from doing so because the base class is unaware of the Response and Request properties found in the OpcClient. Can I bring the interface ITransaction into the base class somehow and still accommodate the dynamic implementation?

Derrick Moeller
  • 4,808
  • 2
  • 22
  • 48

1 Answers1

2

You can subclass DynamicObject (which acts just like ExpandoObject) and make your own version that implements ITransaction. This lets you move the ITransaction constraint up to the base class.

BaseTransaction.cs

internal abstract class BaseTransaction<T> : IDisposable where T : class, ITransaction
{
    private T _opcClient;

    protected T OpcClient
    {
        get { return _opcClient; }
        set
        {
            if (_opcClient != value)
            {
                OnOpcClientChanging();
                _opcClient = value;
                OnOpcClientChanged();
            }
        }
    }


    private void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case "Response":
                ProcessResponse(OpcClient.Response);
                break;
            case "Request":
                ProcessRequest(OpcClient.Request);
                break;
        }
    }

    protected abstract void ProcessResponse(short opcClientResponse);

    protected abstract void ProcessRequest(bool opcClientRequest);

    private void OnOpcClientChanged()
    {
        if (_opcClient != null)
        {
            _opcClient.PropertyChanged += OnOpcClientPropertyChanged;

            OpcManager = new OpcManager(_opcClient);
        }
    }

    private void OnOpcClientChanging()
    {
        if (_opcClient != null)
            _opcClient.PropertyChanged -= OnOpcClientPropertyChanged;
    }
}

StaticTransaction.cs

internal abstract class StaticTransaction<T> : BaseTransaction<T>
where T : class, ITransaction, new()
{
    public StaticTransaction()
    {
        OpcClient = new T();
    }
}

DynamicTransactionObject.cs

internal class DynamicTransactionObject : DynamicObject, ITransaction, IDictionary<string, object>
{

    private readonly Dictionary<string, object> _data = new Dictionary<string, object>();

    public DynamicTransactionObject()
    {
        //Set initial default values for the two properties to populate the entries in the dictionary.
        _data[nameof(Response)] = default(short);
        _data[nameof(Request)] = default(bool);
    }

    public short Response
    {
        get
        {
            return (short)_data[nameof(Response)];
        }
        set
        {
            if (Response.Equals(value))
                return;

            _data[nameof(Response)] = value;
            OnPropertyChanged();
        }
    }

    public bool Request
    {
        get
        {
            return (bool)_data[nameof(Request)];
        }
        set
        {
            if (Request.Equals(value))
                return;

            _data[nameof(Request)] = value;
            OnPropertyChanged();
        }
    }

    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return _data.Keys;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {

        return _data.TryGetValue(binder.Name, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        object oldValue;
        _data.TryGetValue(binder.Name, out oldValue)
        _data[binder.Name] = value;
        if(!Object.Equals(oldValue, value)
           OnPropertyChanged(binder.Name);
        return true;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #region IDictionary<string,object> members
    IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)_data).GetEnumerator();
    }

    void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
    {
        ((ICollection<KeyValuePair<string, object>>)_data).Add(item);
    }

    void ICollection<KeyValuePair<string, object>>.Clear()
    {
        _data.Clear();
    }

    bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
    {
        return ((ICollection<KeyValuePair<string, object>>)_data).Contains(item);
    }

    void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
    {
        ((ICollection<KeyValuePair<string, object>>)_data).CopyTo(array, arrayIndex);
    }

    bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
    {
        return ((ICollection<KeyValuePair<string, object>>)_data).Remove(item);
    }

    int ICollection<KeyValuePair<string, object>>.Count
    {
        get { return _data.Count; }
    }

    bool ICollection<KeyValuePair<string, object>>.IsReadOnly
    {
        get { return ((ICollection<KeyValuePair<string, object>>)_data).IsReadOnly; }
    }

    bool IDictionary<string, object>.ContainsKey(string key)
    {
        return _data.ContainsKey(key);
    }

    void IDictionary<string, object>.Add(string key, object value)
    {
        _data.Add(key, value);
    }

    bool IDictionary<string, object>.Remove(string key)
    {
        return _data.Remove(key);
    }

    bool IDictionary<string, object>.TryGetValue(string key, out object value)
    {
        return _data.TryGetValue(key, out value);
    }

    object IDictionary<string, object>.this[string key]
    {
        get { return _data[key]; }
        set { _data[key] = value; }
    }

    ICollection<string> IDictionary<string, object>.Keys
    {
        get { return _data.Keys; }
    }

    ICollection<object> IDictionary<string, object>.Values
    {
        get { return _data.Values; }
    }
#endregion
}

DynamicTransaction.cs

internal abstract class DynamicTransaction : BaseTransaction<DynamicTransactionObject>
{
    protected new dynamic OpcClient
    {
        get { return base.OpcClient as dynamic; }
    }

    public DynamicTransaction()
    {
        var opcClient = new DynamicTransactionObject();

        // Access database, use IDictionary<string,object> interface to add properties to DynamicObject.

        base.OpcClient = opcClient;
    }
}
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • This looks promising, I'm still working through some of the details. My actual use case is somewhat more complicated. ITransaction defines a method as well as the two properties. I also need to fire the PropertyChanged event for all properties, not just Response and Request. – Derrick Moeller Dec 29 '16 at 15:26
  • I updated `DynamicTransactionObject` to now fire on all properties instead of just the two from the interface (see the updated `TrySetMember`). For the method you can just add it to the class just like you added the properties. – Scott Chamberlain Dec 29 '16 at 15:40
  • This seems close, I'm assuming I need to call OnPropertyChanged from the explicit IDictionary implementation? I'm guessing TrySetMember is used by the dynamic keyword? – Derrick Moeller Dec 29 '16 at 17:59
  • Yes, TrySetMember is used by the dynamic calls. You would need to add similar calls to the relevant IDictionary calls like `Add`, `this[key]`, `Remove` and similar. – Scott Chamberlain Dec 29 '16 at 18:35