1.默認EF生成的鏈接字符串比較的長和怪異,若想使用普通的鏈接字符串來鏈接EF,則能夠經過建立分部類,並重寫一個構造函數,在構造函數中經過動態拼接EntityConnectionString獲得EF所需的鏈接字符串,具代實現代碼以下:html
public partial class DataEntities { private static ConcurrentDictionary<string, string> entityConnStrings = new ConcurrentDictionary<string, string>(); public DataEntities(string connName) : base(BuildEntityConnectionString(connName)) { } private static string BuildEntityConnectionString(string connName) { if (!entityConnStrings.ContainsKey(connName)) { var connStrSetting = System.Configuration.ConfigurationManager.ConnectionStrings[connName]; EntityConnectionStringBuilder entityConnStrBuilder = new EntityConnectionStringBuilder(); entityConnStrBuilder.Provider = connStrSetting.ProviderName; entityConnStrBuilder.ProviderConnectionString = EncryptUtility.DesDecrypt("XXXXX", connStrSetting.ConnectionString); entityConnStrBuilder.Metadata = "res://*/Data.csdl|res://*/Data.ssdl|res://*/Data.msl"; string entityConnString = entityConnStrBuilder.ToString(); entityConnStrings.AddOrUpdate(connName, entityConnString, (key, value) => entityConnString); } return entityConnStrings[connName]; } }
注意上面的類是一個分部類:partial,同時BuildEntityConnectionString方法是一個靜態方法,在BuildEntityConnectionString方法中ProviderConnectionString = EncryptUtility.DesDecrypt("XXXXX", connStrSetting.ConnectionString);是關鍵,我這裏是對config中的鏈接字符串 也都進行了加密,故此處我須要解密,若無這個需求能夠直接:ProviderConnectionString =connStrSetting.ConnectionString便可。後續實例化EF上下文對象時,請使用:DataEntities(string connName)這個構造涵數便可,DataEntities是具體的EF上下文對象,你們的EF上下文類名都可能不相同。sql
2.支持一個通用對象的XML序列化(即:一個類中有可變類型屬性成員,須要不一樣的序列結果及生成不一樣的序列元素名稱),具體實現代碼以下:數據庫
一個須要被序列化成XML的類:其中要求生成的XML元素detail必需有子元素,且子元素名稱及子元素內部屬性根據類型的不一樣而不一樣(即:detail元素下的子元素是可變的)服務器
[XmlRootAttribute("master")] public class DemoMaster<T> where T : class { [XmlElement("attr")] public string DemoAttr { get; set; } [XmlElement("detail")] public DemoDetail<T> DemoDetail { get; set; } //關鍵點在這裏,該屬性元素爲:detail,但其子元素根據T不一樣而不一樣 } public class DemoDetail<T> : IXmlSerializable where T : class { public T body { get; set; } public System.Xml.Schema.XmlSchema GetSchema() { return null; } public void ReadXml(System.Xml.XmlReader reader) { string bodyStr = reader.ReadInnerXml(); this.body = XmlHelper.XmlDeserialize<T>(bodyStr, Encoding.UTF8); } public void WriteXml(System.Xml.XmlWriter writer) { writer.WriteRaw(XmlHelper.XmlSerialize(this.body, Encoding.UTF8, true)); } } [XmlTypeAttribute("list-a", AnonymousType = false)] public class DemoDetailA { public string Apro1 { get; set; } public string Apro2 { get; set; } public string Apro3 { get; set; } } [XmlTypeAttribute("list-b", AnonymousType = false)] public class DemoDetailB { public string Bpro1 { get; set; } public string Bpro2 { get; set; } public string Bpro3 { get; set; } } [XmlTypeAttribute("list-c", AnonymousType = false)] public class DemoDetailC { public string Cpro1 { get; set; } public string Cpro2 { get; set; } public string Cpro3 { get; set; } }
注意上面代碼中,須要關注:DemoDetail屬性及DemoDetail<T>類,DemoDetail屬性僅是爲了生成detail元素節點,而子節點則由DemoDetail<T>類來進行生成,DemoDetail<T>是實現了IXmlSerializable接口,在XML序列化時,DemoDetail<T>類僅將body屬性對應的T類型實例內容進行序列化(WriteRaw),而反序列化時,則先反序列化body屬性對應的T類型實例,而後賦值給body屬性,這也是巧妙之處,DemoDetail<T>類自己並無真正參與到序列化中,故序列化的字符串也看不到DemoDetail<T>類相關的元素,DemoDetail<T>類僅僅是一個XML序列化格式生成的中介。序列化的XML結果以下:app
序列化代碼:ide
var demo1 = new DemoMaster<DemoDetailA>() { DemoAttr = "demo1", DemoDetail = new DemoDetail<DemoDetailA>() { body = new DemoDetailA() { Apro1 = "demoA1", Apro2 = "demoA2", Apro3 = "demoA3" } } }; var demo2 = new DemoMaster<DemoDetailB>() { DemoAttr = "demo2", DemoDetail = new DemoDetail<DemoDetailB>() { body = new DemoDetailB() { Bpro1 = "demoB1", Bpro2 = "demoB2", Bpro3 = "demoB3" } } }; var demo3 = new DemoMaster<DemoDetailC>() { DemoAttr = "demo3", DemoDetail = new DemoDetail<DemoDetailC>() { body = new DemoDetailC() { Cpro1 = "demoC1", Cpro2 = "demoC2", Cpro3 = "demoC3" } } }; textBox1.Text = XmlHelper.XmlSerialize(demo1, Encoding.UTF8); textBox1.Text += "\r\n" + XmlHelper.XmlSerialize(demo2, Encoding.UTF8); textBox1.Text += "\r\n" + XmlHelper.XmlSerialize(demo3, Encoding.UTF8);
序列化的XML:函數
<?xml version="1.0" encoding="utf-8"?> <master> <attr>demo1</attr> <detail><list-a> <Apro1>demoA1</Apro1> <Apro2>demoA2</Apro2> <Apro3>demoA3</Apro3> </list-a></detail> </master> <?xml version="1.0" encoding="utf-8"?> <master> <attr>demo2</attr> <detail><list-b> <Bpro1>demoB1</Bpro1> <Bpro2>demoB2</Bpro2> <Bpro3>demoB3</Bpro3> </list-b></detail> </master> <?xml version="1.0" encoding="utf-8"?> <master> <attr>demo3</attr> <detail><list-c> <Cpro1>demoC1</Cpro1> <Cpro2>demoC2</Cpro2> <Cpro3>demoC3</Cpro3> </list-c></detail> </master>
3.winform DataGridView 實現指定列採起密碼框模式顯示與編輯,以及列綁定到複合屬性(即:綁定到多層次屬性),具體實現代碼以下:ui
dataGridView1.CellFormatting += new DataGridViewCellFormattingEventHandler(dataGridView1_CellFormatting); dataGridView1.EditingControlShowing += new DataGridViewEditingControlShowingEventHandler(dataGridView1_EditingControlShowing); public string EvaluateValue(object obj, string property) { string retValue = string.Empty; string[] names = property.Split('.'); for (int i = 0; i < names.Count(); i++) { try { var prop = obj.GetType().GetProperty(names[i]); var result = prop.GetValue(obj, null); if (result != null) { obj = result; retValue = result.ToString(); } else { break; } } catch (Exception) { throw; } } return retValue; } private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { if (dataGridView1.Columns[e.ColumnIndex].DataPropertyName.Contains(".")) { e.Value = EvaluateValue(dataGridView1.Rows[e.RowIndex].DataBoundItem, dataGridView1.Columns[e.ColumnIndex].DataPropertyName); } if (dataGridView1.Columns[e.ColumnIndex].Name == "KeyCode") { if (e.Value != null && e.Value.ToString().Length > 0) { e.Value = new string('*', e.Value.ToString().Length); } } } private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) { int i = this.dataGridView1.CurrentCell.ColumnIndex; bool usePassword = false; if (dataGridView1.Columns[i].Name == "KeyCode") { usePassword = true; } TextBox txt = e.Control as TextBox; if (txt != null) { txt.UseSystemPasswordChar = usePassword; } } //示例:綁定的源數據類定義 public class DemoBindClass { public string Attr { get; set; } public string KeyCode { get; set; } public DemoDetailA Detail { get; set; } } public class DemoDetailA { public string Apro1 { get; set; } public string Apro2 { get; set; } public string Apro3 { get; set; } public DemoDetailB DetailChild { get; set; } } public class DemoDetailB { public string Bpro1 { get; set; } public string Bpro2 { get; set; } public string Bpro3 { get; set; } }
綁定到數據源:this
var demo = new[] { new DemoBindClass() { Attr = "demo", KeyCode="a123456789b", Detail = new DemoDetailA() { Apro1 = "demoA1", Apro2 = "demoA2", Apro3 = "demoA3", DetailChild = new DemoDetailB() { Bpro1 = "demoB1", Bpro2 = "demoB2", Bpro3 = "demoB3" } } } }; dataGridView1.AutoGenerateColumns = false; dataGridView1.DataSource = demo;
實現指定列採起密碼框模式顯示與編輯,以及列綁定到複合屬性均須要訂閱DataGridView的CellFormatting及EditingControlShowing事件,並在其中寫轉換當前Cell的Value,實現列綁定到複合屬性,關鍵點在:EvaluateValue方法,該方法邏輯很簡單,就是根據綁定的屬性層級(.分隔)層層遍歷獲取屬性的值,直到遍歷完或爲空時中止,最後獲得的結果便是綁定的屬性的值。最終實現的效果以下圖示:加密
4.利用BCP(sqlbulkcopy)來實現兩個不一樣數據庫之間進行數據差別傳輸(即:數據同步)
TransferBulkCopy做用:實現兩個不一樣數據庫之間進行數據差別傳輸,BuildInsertOrUpdateToDestTableSql做用:根據目的表及臨時表生成更新與插入記錄的SQL語句,以此實現:若同步的數據已存在,則更新,不存在,則插入。
/// <summary> /// 通用數據傳輸方法(採用SqlBulkCopy快速批量插入,而後再進行處理) /// </summary> /// <param name="sourceSelectSql"></param> /// <param name="sourceConn"></param> /// <param name="destTableName"></param> /// <param name="destConn"></param> /// <param name="colMapFunc"></param> /// <param name="lastSaveAction"></param> public void TransferBulkCopy(string sourceSelectSql, SqlConnection sourceConn, string destTableName, SqlConnection destConn, Func<DataTable, Dictionary<string, string>> colMapFunc, Func<string, DataTable, SqlConnection, SqlConnection, bool> lastSaveAction, bool closeConnection = true) { DataTable srcTable = new DataTable(); SqlDataAdapter srcAdapter = new SqlDataAdapter(sourceSelectSql, sourceConn); srcAdapter.AcceptChangesDuringUpdate = false; SqlCommandBuilder srcCmdBuilder = new SqlCommandBuilder(srcAdapter); srcAdapter.Fill(srcTable); if (srcTable != null && srcTable.Rows.Count > 0) { string tempDestTableName = "#temp_" + destTableName; ClsDatabase.gExecCommand(destConn, string.Format("select top 0 * into {0} from {1}", tempDestTableName, destTableName), false); List<string> mapDestColNameList = new List<string>(); using (SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(destConn)) { sqlBulkCopy.DestinationTableName = tempDestTableName; foreach (var map in colMapFunc(srcTable)) { sqlBulkCopy.ColumnMappings.Add(map.Key, map.Value); mapDestColNameList.Add(map.Value); } sqlBulkCopy.WriteToServer(srcTable); } srcTable.ExtendedProperties.Add(MapDestColNames_String, mapDestColNameList); bool needUpdate = lastSaveAction(tempDestTableName, srcTable, destConn, sourceConn); if (needUpdate) { if (srcTable.Columns.Contains("TranFlag")) { foreach (DataRow row in srcTable.Rows) { row["TranFlag"] = true; } } srcAdapter.Update(srcTable); } } if (closeConnection) { DisposeConnections(sourceConn, destConn); } }
/// <summary> /// 生成同步插入及更新目的表SQL語句 /// </summary> /// <param name="destTableName"></param> /// <param name="tempdestTableName"></param> /// <param name="pkWhereColNames"></param> /// <param name="mapDestColNames"></param> /// <param name="sqlType">0=生成INSERT與UPDATE;1=生成UPDATE語句;2=生成INSERT語句</param> /// <returns></returns> public string BuildInsertOrUpdateToDestTableSql(string destTableName, string tempdestTableName, string[] pkWhereColNames, object mapDestColNames, int sqlType = 0) { var mapDestColNameList = mapDestColNames as List<string>; string updateColNames = null; foreach (string col in mapDestColNameList) { if (!pkWhereColNames.Contains(col, StringComparer.OrdinalIgnoreCase)) { updateColNames += string.Format(",{0}=tnew.{0}", col); } } updateColNames = updateColNames.Substring(1); string insertColNames = string.Join(",", mapDestColNameList); string pkWhereSql = null; foreach (string col in pkWhereColNames) { pkWhereSql += string.Format(" and told.{0}=tnew.{0} ", col); } pkWhereSql = pkWhereSql.Trim().Substring(3); StringBuilder sqlBuilder = new StringBuilder(); if (sqlType == 0 || sqlType == 1) { sqlBuilder.AppendFormat("UPDATE {0} SET {1} FROM {0} told INNER JOIN {2} tnew ON {3} " + Environment.NewLine, destTableName, updateColNames, tempdestTableName, pkWhereSql); } if (sqlType == 0 || sqlType == 2) { sqlBuilder.AppendFormat("INSERT INTO {0}({1}) SELECT {1} FROM {2} tnew WHERE NOT EXISTS(SELECT 1 FROM {0} told WHERE {3}) " + Environment.NewLine, destTableName, insertColNames, tempdestTableName, pkWhereSql); } return sqlBuilder.ToString(); }
使用示例以下:
public void SendData_CustomerAuthorization() { try { SqlConnection obConnLMS1 = new SqlConnection(master.connLMSStr); SqlConnection obConnWEB1 = new SqlConnection(master.connWEBStr); string selectSql = @"SELECT TOP {0} Id,Phone,Mac,IsSet,LastLoginTime,PCName,TranFlag FROM TWEB_CustomerAuthorization WHERE TranFlag=0 ORDER BY Id "; selectSql = string.Format(selectSql, master.batchSize); master.TransferBulkCopy(selectSql, obConnWEB1, "TB_CustomerAuthorization", obConnLMS1, (stable) => { var colMaps = new Dictionary<string, string>(); foreach (DataColumn col in stable.Columns) { if (!col.ColumnName.Equals("TranFlag", StringComparison.OrdinalIgnoreCase)) { colMaps.Add(col.ColumnName, col.ColumnName); } } return colMaps; }, (tempTableName, stable, destConn, srcConn) => { StringBuilder saveSqlBuilder = new StringBuilder("begin tran" + Environment.NewLine); string IUSql = master.BuildInsertOrUpdateToDestTableSql("TB_CustomerAuthorization", tempTableName, new[] { "Id" }, stable.ExtendedProperties[master.MapDestColNames_String]); saveSqlBuilder.Append(IUSql); saveSqlBuilder.AppendLine("commit"); ClsDatabase.gExecCommand(destConn, saveSqlBuilder.ToString()); master.WriteMsg(master.lstSended, string.Format("上傳時間:{0:yyyy-MM-dd HH:mm}\t SendData_CustomerAuthorization \t Succeed:{1}", DateTime.Now, stable.Rows.Count)); return true; }); } catch (Exception ex) { master.WriteMsg(master.lstErrorInfo, DateTime.Now.ToString("yyyy-MM-dd HH:mm") + "\t" + "SendData_CustomerAuthorization" + "\t" + ex.Message.ToString()); } }
同步原理以下:
4.1.定義好查詢源服務器的須要同步的表(通常表中咱們定義一個用因而否同步的標識字段,如:TranFlag Bit類型,0表示新數據,未同步,1表示已同步);
4.2.查詢源服務器的須要同步的表的記錄(通常是TranFlag=0的記錄),利用SqlDataAdapter+SqlCommandBuilder 裝載Dataset,目的是後續能夠利用SqlDataAdapter直接生成更新命令並執行;
4.3.利用insert into從目的服務器的將被同步的表複製結構產生一個臨時表,表名通常是:#temp_目的服務器的將被同步表名 ,這樣臨時表與實體表的結構徹底一致;
4.4.實例化一個SqlBulkCopy,並創建源服務器的須要同步的表字段與目的臨時表字段的映射,而後執行跨服務器傳輸;
4.5.利用 BuildInsertOrUpdateToDestTableSql 方法 ,生成 目的服務器的將被同步的表 與 臨時表的插入與更新SQL語句(如今在同一個庫了,想怎麼用SQL語句都可)
4.6.爲確保一致性,故外層還需包裹事務SQL語句,若還需加入其它處理SQL,能夠加在begin tran ... commit代碼塊中便可,最後執行SQL語句:gExecCommand(ClsDatabase.gExecCommand是一個SQLDB HELPER 類的執行SQL命令的方法)
5.實現同一個WINDOWS SERVICE程序 COPY多份,而後經過更改自定義的服務ID(ServiceID)配置項來實現:同一個服務程序安裝成多個不一樣的WINDOWS服務進程:
5.1.建立一個WINDOWS服務項目,在ProjectInstaller設計器界面經過右鍵彈出菜單選擇安裝程序(serviceProcessInstaller一、serviceInstaller1)、並設置好ServiceName、DisplayName、Description、Account等,以下圖示:
5.2.在ProjectInstaller構造函數中增長從CONFIG文件中讀取自定義的服務ID(ServiceID)配置項的值,而後將ServiceID拼加到預設的ServiceName後面,以便實際根據ServiceID可以安裝成不一樣ServiceID後綴的服務進程,關鍵點在於改變ServiceName,另外一個關鍵點是從CONFIG文件中獲取ServiceID,因爲安裝時,傳統的方式沒法正常讀取到CONFIG,只能經過Assembly.GetExecutingAssembly().Location 來獲取當前執行的程序集的路徑再拼成CONFIG文件路徑,最後讀出ServiceID的值,示例代碼以下:
public partial class ProjectInstaller : System.Configuration.Install.Installer { public ProjectInstaller() { InitializeComponent(); string assyLocation = System.Reflection.Assembly.GetExecutingAssembly().Location; string assyCfgPath = assyLocation + ".config"; string installServiceLogPath = Path.Combine(Path.GetDirectoryName(assyLocation), "InstallServiceLog.log"); string serviceID = ConfigUtil.GetAppSettingValueForConfigPath("ServiceID", assyCfgPath); System.IO.File.AppendAllText(installServiceLogPath, string.Format("[{0:yyyy-MM-dd HH:mm:ss}] ServiceAssembly ConfigPath:{1};\r\n", DateTime.Now, assyCfgPath)); if (!string.IsNullOrWhiteSpace(serviceID)) { this.serviceInstaller1.DisplayName = "TestService_" + serviceID; this.serviceInstaller1.ServiceName = "TestService_" + serviceID; } System.IO.File.AppendAllText(installServiceLogPath, string.Format("[{0:yyyy-MM-dd HH:mm:ss}] ProjectInstaller.ProjectInstaller() ->ServiceID:{1},ServiceName:{2}; \r\n", DateTime.Now, serviceID, this.serviceInstaller1.ServiceName)); } }
5.3.在服務類的構造函數中一樣增長從CONFIG中讀取自定義的服務ID(ServiceID)配置項的值,而後將ServiceID拼加到預設的ServiceName後面(注意應與上述ProjectInstaller中指定的ServiceName相同),示例代碼以下:
public partial class TestService: ServiceBase { public TestService() { serviceID = ConfigUtil.GetAppSettingValue("ServiceID"); if (!string.IsNullOrWhiteSpace(serviceID)) { this.ServiceName = "TestService_" + serviceID; } } }
上述三步就完成了同一個服務程序安裝成多個不一樣的WINDOWS服務進程,這個仍是比較實用的哦!上述ConfigUtil是封裝的一個配置文件讀寫幫助類,以前文章有介紹,後面也會發佈一個更完整的ConfigUtil類。