LLBL Gen作為項目開發的ORM框架,選擇.NET Remoting作為分布式技術框架。一直也很想把ERP框架從.NET Remoting升級到WCF,只是關於方法重載的配置方法需要特殊處理。舉例說明如下
public interface IEmployeeManager { EmployeeEntity GetEmployee(System.Int32 Employeeid); EmployeeEntity GetEmployee(System.Int32 Employeeid, IPrefetchPath2 prefetchPath); EmployeeEntity GetEmployee(System.Int32 Employeeid, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList); }
IEmployeeManager接口用於對員工表Employee進行CRUD操作。上面三個重載方法是用來從數據庫中讀取員工信息
對.NET Remoting技術實現,只需要派生於MarshalByRefObject,實現IEmployeeManager接口的方法即可
public class EmployeeManager : MarshalByRefObject, IEmployeeManager { public EmployeeEntity GetEmployee(System.Int32 Employeeid) { return GetEmployee(Employeeid, null); } public EmployeeEntity GetEmployee(System.Int32 Employeeid, IPrefetchPath2 prefetchPath) { return GetEmployee(Employeeid, prefetchPath, null); } public EmployeeEntity GetEmployee(System.Int32 Employeeid, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList) { EmployeeEntity _Employee = new EmployeeEntity(Employeeid); using (DataAccessAdapterBase adapter = GetCompanyDataAccessAdapter()) { bool found = adapter.FetchEntity(_Employee, prefetchPath, null, fieldList); if (!found) throw new RecordNotFoundException("Invalid Employee"); } return _Employee; } }
這樣對於.NET Remoting是沒有問題的。然后對於WCF技術,它不支持方法重載overload,需要添加Name特性
public interface IEmployeeManager { [OperationContract(Name="Employeeid")] EmployeeEntity GetEmployee(System.Int32 Employeeid); [OperationContract(Name="EmployeeidPrefetchPath")] EmployeeEntity GetEmployee(System.Int32 Employeeid, IPrefetchPath2 prefetchPath); [OperationContract(Name="EmployeeidPrefetchPathFieldList")] EmployeeEntity GetEmployee(System.Int32 Employeeid, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList); }
每次加這么多參數很麻煩,於是就制造一個工具,專門用於生成Name的參數值
甚至可以把這個工具的代碼直接集成到Code Smith的模板中,這樣大大減少了工作量,提高工作效率。
回到主題,來看看LLBL Gen的.NET Remoting版本,這個例子被直接映射到了ERP框架中。
定義接口方法
public interface IService { int GetNumberOfCustomers(); void GetOrderStatistics(out decimal averageOrderPrice, out decimal highestOrderPrice,
out int numberOfOrders, out int orderIdWithHighestPrice); OrderEntity GetOrderAndCustomerWithHighestPrice(int orderId); CustomerEntity GetCustomerWithMostOrdersAndNumberOfOrders(out int numberOfOrders); EntityCollection<CustomerEntity> GetAllCustomers(); EntityCollection<CustomerEntity> GetAllCustomersFilteredOnProduct(int productId); EntityCollection<ProductEntity> GetProductsSortedBySortExpression(SortExpression sorter); bool RemoveOrder(OrderEntity toRemove); CustomerEntity GetCustomerWithFullOrders(string customerId); bool SaveCustomer(CustomerEntity toSave, bool recursive); CustomerEntity GetCustomer(string customerId); void SaveOrders(EntityCollection<OrderEntity> orders); }
從技術的角度來講,有二種類型的方法:單向的,傳入參數,執行動作,不返回值;雙工,傳入參數,返回值。
接口的實現代碼
public class Service : MarshalByRefObject, IService { public int GetNumberOfCustomers() { Console.WriteLine("GetNumberOfCustomers called"); using(DataAccessAdapter adapter = new DataAccessAdapter()) { // simply obtain the count of the customerid and return that. return (int)adapter.GetScalar(CustomerFields.CustomerId, AggregateFunction.CountRow); } } }
其它的代碼參考下載的例子程序,它的實現代碼可以用代碼生成器生成。不帶參數的DataAccessAdapter 表示從默認生成的配置文件中讀取連接字符串,內容如下
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="Northwind.ConnectionString.SQL Server (SqlClient)" value="data source=.\sqlexpress;initial
catalog=Northwind;integrated security=SSPI;persist security info=False;packet size=4096"/>
</appSettings>
</configuration>
服務器端的實現,把接口的實現代碼發布成服務
// register a channel and assign the service type to it. This opens the service to the outside world at TCP port 65100. TcpChannel channel = new TcpChannel(65100); ChannelServices.RegisterChannel(channel, true); Type serverType = Type.GetType("RemotingService.Service"); RemotingConfiguration.RegisterWellKnownServiceType(serverType, "theEndPoint", WellKnownObjectMode.Singleton); // switch on FastSerialization SerializationHelper.Optimization = SerializationOptimization.Fast;
客戶端實現代碼,讀取服務並調用它來實現數據庫的讀寫
// grab service object first. TcpChannel channel = new TcpChannel(); ChannelServices.RegisterChannel(channel, true); MarshalByRefObject o = (MarshalByRefObject)RemotingServices.Connect(typeof(IService), string.Format(
"tcp://{0}:65100/theEndPoint", SERVERNAME)); IService service = o as IService; if(service == null) { throw new Exception("Service couldn't be obtained. Aborting"); } MainForm.DalService = service; // switch on FastSerialization SerializationHelper.Optimization = SerializationOptimization.Fast;
之后的代碼中,都會應用MainForm.DalService來獲取客戶數據以及客戶的產品資料。
這個例子還有幾點可以討論的地方,請參考如下的思考
1 服務的調用代碼。如果有多個.NET Remoting服務 ,應該做成工廠模式,並且提高cache以減少內存的消耗。
工廠模式的參考代碼如下所示
IService service; if(!_cache.Contains(service)) service=CreateService(service) return service;
這樣實現的好處是,當這個接口被多個客戶段代碼調用時,服務實現只被創建了一次,節約內存。雖然.NET的垃圾回收器已經很智能了,但是這種帶緩存版本的寫法,比沒有緩存要有效率。
2 沒有使用配置文件,而是直接在代碼中創建TcpChannel的方式。在學.NET Remoting的時,也喜歡用各種書中推薦的方式,用配置文件而不是直接硬編碼到代碼中。在實踐中發現,這樣每次都需要把已經配置好的配置文件的片段拷貝到新項目的配置文件中,比較麻煩。一般來說,Channel和Port在部署到客戶服務器上是不會變動的。於是就改變做法,把Channel的創建直接寫到代碼中,Port留給配置文件來指定。這種方式不需要增加配置文件節,方便部署。
3 當方法需要返回多個值時,參考接口IService的GetOrderStatistics方法,這里使用的是out修飾參數。把值賦給傳入的參數,方法調用完成后,再獲取參數值。這里,推薦用結構struct設計成返回值,以應對增加參數的變化。
方法參數的傳入也一樣,當有多個參數要傳入到方法內部時,可以有struct把這些參數簡單封裝一下,作為一個整體傳入方法中。在未來有對這個方法進入維護時,也會相對容易一些。
文章中提到的例子程序,請到LLBL Gen官方網站下載,代碼名稱是Example_NorthwindRemotingExampleCS_Adapter。