views:

47

answers:

2

We are enforcing all our domain objects to implement GetHashCode.

namespace Core
{
  [Serializable]
  public abstract class DomainObject
  {
    public abstract override int GetHashCode();
  }
}

namespace Entity.Domain
{
  [Serializable]
  [DataContract]
  public partial class IdCard : DomainObject
  {
    private System.Int32 _effDte;

    [DataMember]
    public virtual System.Int32 EffDte
    {
        get { return _effDte; }
        set { _effDte = value; }
    }

    public override int GetHashCode()
    {
        return EffDte.GetHashCode();
    }
  }
}

When we expose these domain objects through WCF, the following generated service is requiring a post-update modification to compile.

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.3053
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace IdCardManagerServiceReference {
using System.Runtime.Serialization;
using System;


[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="IdCard", Namespace="http://schemas.datacontract.org/2004/07/Entity.Domain")]
[System.SerializableAttribute()]
public partial class IdCard : Core.DomainObject, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged { 
    [System.NonSerializedAttribute()]
    private System.Runtime.Serialization.ExtensionDataObject extensionDataField;

    [System.Runtime.Serialization.OptionalFieldAttribute()]
    private int EffDteField;

    [global::System.ComponentModel.BrowsableAttribute(false)]
    public System.Runtime.Serialization.ExtensionDataObject ExtensionData {
        get {
            return this.extensionDataField;
        }
        set {
            this.extensionDataField = value;
        }
    }

    [System.Runtime.Serialization.DataMemberAttribute()]
    public int EffDte {
        get {
            return this.EffDteField;
        }
        set {
            if ((this.EffDteField.Equals(value) != true)) {
                this.EffDteField = value;
                this.RaisePropertyChanged("EffDte");
            }
        }
    }
}

Any ideas on how to keep the requirement for GetHashCode, but remove the requirement for any code on the client (as an update or as partial classes)?

A: 

Fix the namespace on your partial class. You might need to adjust the Attributes and inheritance.

namespace DCS2000.IDCardExclude.UI.IdCardManagerServiceReference
{
  [Serializable]
  [DataContract]
  public partial class IdCard : DomainObject
  {
    public override int GetHashCode()
    {
        return EffDte.GetHashCode();
    }
  }
}
Ray Henry
Regardless of the namespace, if you know what it will be in the generated class, you can create the partial class to implement GetHasCode() and it will not be overritten when you create your reference.
Ray Henry
@Ray this is a good solution for a client side resolution and may be the best. Any ideas on something that won't require partial class files?
Lucas B
+2  A: 

If you really require that all C# consumers of your WCF service use the same models with original code in tact then use the "Add Service Reference" tool's "Reuse types in referenced assemblies" feature. Be sure to break out your models / contracts / interfaces into a single assembly with no other implementation code in it that serves as a shared "definition" assembly. Mark this assembly as the one to reuse types from when the "Add Service Reference" tool generates your client proxy code.

Also, just a proactive warning: simplify things for yourself by having only one "official" C# service client implementation for your service. Don't add redundant service reference proxies generated by Visual Studio to every single project which requires connectivity with your service.

EDIT:

Speaking from recent personal experience, having designed a modular, service-enabled API, allow me to provide more general advice on the topic of modularity as it pertains to not only WCF services, but the overarching design.

In our system, we have done what I suggested above in creating a single "Definition" assembly which contains only objects marked as [DataContract]: domain objects, models, data contracts, whatever you like to refer to them as.

In this assembly is also a collection of repository interfaces which defines methods solely in terms of these domain objects. There are also strongly-typed identifier structs defined in this assembly which are used to hold identity values per each model since these models are database persisted and each has an identity column. Using structs which wrap ints in this way is preferable to using int itself since now we get compiler-assisted semantic analysis, i.e. Model1ID is not convertible to Model2ID as they semantically represent two different domains despite the fact that both domains are representable by the int type.

What drives the modularity is the definition of these repository interfaces. The WCF service implementation class simply implements all the required interfaces, similarly with the WCF service client implementation class, the database repository implementation class, the caching proxy implementation class, the method invocation logging implementation class, etc. All of the concrete implementation classes exist in other assemblies, i.e. not in the "Definition" assembly which contains the interfaces and models. These classes implement the interfaces and appear to the consumer code as the same.

The key is to keep your API consumer code agnostic of the specific implementation classes and only reference the interfaces. The models themselves are kept as simple data containers with no business logic implementation found within them. I believe this pattern is referred to as anemic, but to me "anemic" has negative connotations so I don't like to use that term when describing this design.

What you get is consumer code implementing business logic that doesn't care whether it is talking to a WCF service or directly to a database, or that caching is being implemented seamlessly, or that your method invocations are being logged, or whatever other proxy uses you can come up with.

In summary, design with interfaces and make your life easier for yourself. But only do this if you are confident in your ability to practice self-restraint. In our system which I designed, we have T4 templates which generate almost all of the boilerplate code that comes along with WCF services to the point where all we have to do manually is define the model, design the interface, and write the database access code. The WCF facade comes free with a simple right-click and "Run Custom Tool" on the T4 templates. I love it. :)

James Dunne
Eactly. VERY good approach, and it allows one to put simple logic into the objects (like front end validation).
TomTom
Modularity is always a good approach! I have more advice to give on this topic but I must constrain myself to the issue at hand for the sake of SO :)
James Dunne
If it is in reference to WCF modularity I believe it pertains.
Lucas B
I feel I may have slightly crossed the relevancy line, but unfortunately there are very few places on this Internet with which to share such general design advice.
James Dunne