PDA

Դիտել ողջ տարբերակը : BatutConnector - SQL աղյուսակների կառավարման framework



Արամ
23.02.2015, 21:36
Այստեղ գրառումներս զուտ մասնագիտական են լինելու։
Նշեմ, որ framework-ը դեռ developing-ի փուլում է և այս օրագրում, կնշեմ ընթացքում ինձ հանդիպած խնդիրները, լուծումները և այլն։
Հետագայում, պլանավորում եմ, այս framewok-ի կոդը ազատ դնել համացանցում, ինչպես նաև սրա միջոցով կայք ստեղծել, որտեղ ծրագրավորողները կկարողանան կիսվել իրենց պրոյեկտներով, ինչպես ես հիմա անում եմ այստեղ։

Framework—ը գրում եմ C#-ով։ Իմ առաջին փորձն է էս լեզվով ինչ որ բան գրելու։ Ցուցադրված կոդերի մեջ թերություն գտնելուց, շատ շնորհակալ կլինեմ, եթե ինձ տեղեկացնեք։

Թե ինչու միտք ծագեց այսպիսի framework գրելու, չեմ կարող բացատրել․․․կարճ ասած ցանկացած ծրագրավորղի խելքը իրանը չի, երբ գործը հասնում ա հեծանիվ հորինելուն։

Հուսով, թե այստեղիս գրառումները, թե հետագայում framework—ը ինչ որ բանով օգտակար կլինի։

Արամ
23.02.2015, 22:35
Նախ ծանոթանանք սկզբնական արխիտեկտուռայի հետ՝
http://s7.postimg.org/85lm4rsff/new_png.png
Հիշենք այս "դիագռամանմանը": Հետագա 1-2 պոստերի ընթացքում սա մեզ պետք կգա։
Սկսենք sysInfSchema-ից։ Այս օբյեկտը նախատեսված է INFORMATION_SCHEMA-ից, որտեղ SQL Server-ում պահվում են ընդհանուր Database-ի մասին տեղեկությունները, կոնկրետ աղուսյակի, որը նշվել է sysMain-ը ժառանգած կլասի միջոցով(cMainTable փոփախական), սյուների արժեքները ստանալու համար։
Ինչպե՞ս ենք մենք ստանում կոնկրետ աղյուսակիի սյուների անունները։
Կոդը՝

static public List<String> GetTableColumns(string tableName) {
List<String> retVal;
String tmpTableName = tableName.ToUpper();
if (!TableColumnsCache.ContainsKey(tmpTableName)) { // If table not found in cache
retVal = new List<String>();
string tmpQueryString = "SELECT name FROM sys.columns WHERE object_id = OBJECT_ID(@TableName)";
SqlCommand tmpSqlCommand = new SqlCommand(tmpQueryString, sysDataBaseConnect.SQLConnection);
tmpSqlCommand.Parameters.AddWithValue("@TableName", tableName);
SqlDataReader columnsReaded = sysDataBaseConnect.executeQuery(tmpSqlCommand);
if (columnsReaded.HasRows) {
while (columnsReaded.Read()) {
retVal.Add(columnsReaded["name"].ToString());
}
TableColumnsCache.Add(tmpTableName, retVal); // Add to columns catch
}
else {
LastErrorMsg = "Table Not Found";
}
columnsReaded.Close();
}
else {
retVal = TableColumnsCache[tmpTableName];
}
return retVal;


Եթե 1 անգամ սեսիայի ընթացքում աղյուսակի սյուների անուները ստացել ենք, ապա էլ կարիք չկա նորից ստանալու, դրա համար ստանալուց հետ պահում ենք TableColumnsCache օբյեկտի մեջ, որը հայտարարված է այսպես՝ Dictionary<string, List<string>>, հետագայում, եթե ուրիշ օբյեկտ այդ թեյբլի աղյուսակները ուզի, իրեն միանգամից կվերադարձնենք Cache-ից, ոչ թե նորից հարցում կկազմենք դրանք ստանալու համար։ (sysInfSchema օբյեկտը static է)

Այս ֆունկցաին կանչվում է sysMain-ի Init() - ֆունկցիաից, որի միջոցով էլ ստեղծվում են մեր հիմնական 2 dictionary-ների key-երը կոդը՝

cTableColumnsList = sysInfSchema.GetTableColumns(this.cMainTable);
if (cTableColumnsList.Any()) {
foreach (string colName in cTableColumnsList) { // Loop through Columns list
this.cPhysicalRec.Add(colName, "");
}
this.cCurrentRec = new Dictionary<string, string>(this.cPhysicalRec);
retVal = true;
}


Ինչպես երևում է Diagram—ից մեր sysMain օբյեկտը ունի 2 Dictionary օբյեկտներ՝

protected Dictionary<string, string> cPhysicalRec; // Physical Rec ('column' => 'value' )
protected Dictionary<string, string> cCurrentRec; // Current Record ('column' => 'value' )

cPhysicalRec-ի մեջ, երբ կանչվում է SetId(string uniqueID) ֆունկցիան (որի մասին հետո կխոսենք) համապատասխանաբար լցվում են table-ի աղյուսակների արժեքները, հետո
նույն արժեքները copy են լինում cCurrentRec-Ի մեջ, որի հետ էլ հետագայում աշխատում ենք՝
SetValue, GetValue, SetRecord...սրանից մասին հետո։

Արամ
23.02.2015, 23:44
sysMain class-ը
http://s13.postimg.org/42urrurrb/diagram_2_sysmain.png
Ինպես տեսնում ենք դիագռամից, sysMain-ը ունի հետևյալ ֆունկցիաները՝
int SetID(string uniqueID)
վերադարձնում է
(սրանք կոնստանտներ են հայտարարված sysConstant կլասում)
edtNone-եթե ինչ որ խնդիր է առաջացել (0)
edtExist-եթե տողը գոյություն ունեղող ա (1)
edtNew-եթե տողը նոր է (2)
bool SetValue(string columnName, string value)
false - եթե validation-ը չի անցել (սրա մասին մի քիչ հետո)
true - եթե ամեն ինչ անցել է
string GetValue(string columnName)
վերադարձնում է սյունի արժեքը, եթե չկա ապա դատարկ
bool Delete()
bool Write()
սխալ վերադարձնելու դեպքում սխալի մասին ավելի ինֆորմացիա կարող ենք ստանալ
public int LastErrorNum
public string LastErrorMsg
փոփոխականների միջոցով

Հիմա ամեն ֆունկցիայի մասին ավելի մանրամասն
1.SetID
Կոդը՝

// Set Records to current row
virtual public int SetId(string uniqueId) {
int retVal = sysConstant.edtNone;
this.EditState = sysConstant.edtNone;
retVal = this.FindId(uniqueId); // from sysMainView
if (retVal != sysConstant.edtNone) {
if (this.cSecurityAccess > sysConstant.securityView) {
this.Clear(); // For be sure that memmory is clear
// No need to add ID to physical file
if (this.SetValue(cTableUniqueCol, uniqueId)) { // Add to current Rec
retVal = sysConstant.edtNew;
this.EditState = sysConstant.edtNew;
}
}
}
return retVal;
}

Վերցնում ենք ստացված արժեքը, փոխանցում FindID—ին, որը հայտարարված է sysMainView կլասում։
Այնտեղ էդ Id-ով select ենք կազմում Main աղուսյակի վրա, կոդի մաս՝

string tmpQueryString = "SELECT * FROM @MainTable WHERE @TableUniqCol = @uniqueID;";
SqlCommand tmpSqlCommand = new SqlCommand(tmpQueryString, sysDataBaseConnect.SQLConnection);
tmpSqlCommand.Parameters.AddWithValue("@uniqueID", uniqueId);
tmpSqlCommand.Parameters.AddWithValue("@MainTable", cMainTable);
tmpSqlCommand.Parameters.AddWithValue("@TableUniqCol", cTableUniqueCol);
SqlDataReader resultRec = sysDataBaseConnect.executeQuery(tmpSqlCommand);
if (ReadFromTableToRec(resultRec)) {
retVal = sysConstant.edtExist;
}

Եթե գոյություն ունի, արժեքները լցնում ենք cCurrentRec-ի և cPhysicalRec-ի մեջ։
Եթե գոյություն չունի, ապա տողը նոր է, և միայն ավելացնում ենք ID-ն cCurrentRec-ում և թողնում, որպեսզի իր հետ աշխատեն։

2. SetValue
Կոդը՝

virtual public bool SetValue(string colName, string value) {
bool retVal = false;
this.ClearErrors(); // Need to Clear Errors
value = value.Trim(); // Strip off Spaces
if (this.cCurrentRec.ContainsKey(colName)) {
// validation !!!
int tmpRetVal = sysConstant.retValidationNone;
if (!cSkipValidation) {
tmpRetVal = this.ExecuteValidationFunction(colName, value);
}
if (tmpRetVal == sysConstant.retValidationSuccess || tmpRetVal == sysConstant.retValidationNone) {
this.cCurrentRec[colName] = value;
retVal = true;
}
}
else {
this.LastErrorNum = sysConstant.REC_ERROR;
this.LastErrorMsg = sysConstant.REC_COLUMN_INVALID;
}
return retVal;
}
ExecuteValidationFunction - ֆունկցաի համար առանձին պոստ կտարամդրենք, իսկ հիմա ինչպես տեսնում ենք, մուտքագրված արժեքը վերցնում ենք և
cCurrentRec dictionary-ի մեջ մուտքագրված սյունի համար ավելացնում։
Այս ֆունկցիաի նման է SetValueNoValidation-ը որ առանց validation է setValue անում։

3․ GetValue
Էլ մի միանգամից փակի էջը, էս ֆունկցիան կարճ ա

virtual public string GetValue(string colName) {
this.ClearErrors(); // Need to Clear Errors
string retVal = "";
if (this.cCurrentRec.ContainsKey(colName)) {
retVal = this.cCurrentRec[colName];
}
else {
this.LastErrorNum = sysConstant.REC_ERROR;
this.LastErrorMsg = sysConstant.REC_COLUMN_INVALID;
}
return retVal;
}
Ինձ թվում է պարզ է կարիք չկա մանրամասնելու։
Ունենք նաև GetOldValue ֆունկցիան, որը cCurrentRec-ի փոխարեն օգտագործում է cPhysicalRec-ը։

4. Delete
Delete-ի մի կտոր՝
bool preDeleteRetVal = this.PreDeleteRec(uniqueId);
if (preDeleteRetVal) {
string tmpQueryString = "DELETE FROM @MainTable WHERE @TableUniqCol = @uniqueID;";
SqlCommand tmpSqlCommand = new SqlCommand(tmpQueryString, sysDataBaseConnect.SQLConnection);
tmpSqlCommand.Parameters.AddWithValue("@uniqueID", uniqueId);
tmpSqlCommand.Parameters.AddWithValue("@MainTable", cMainTable);
tmpSqlCommand.Parameters.AddWithValue("@TableUniqCol", cTableUniqueCol);
int rowsAffected = sysDataBaseConnect.executeNonQuery(tmpSqlCommand);
if (rowsAffected == 0) {
retVal = false;
this.LastErrorNum = sysConstant.SQL_ERROR;
this.LastErrorMsg = sysDataBaseConnect.LastErrorMsg;
}
else {
bool postDeleteRetVal = this.PostDeleteRec(uniqueId);
}
}


Այստեղ հետաքրքիր է այն, որ delete-ից հետո և առաջ կանչում ենք, postDeleteRec, preDeleteRec ֆունկցիաները, որոնք հայտարարված են virtual և թույլ կտան այս կլասը ժառանգողին առանց delete-ի հիմնական կոդին կպնելու ինչ որ լոգիկաներ ավելացնել։

Write-ը առանձին թեմա է ու իմ կարծիքով արժանի է, որ իր մասին հետո մի քիչ երկար խոսենք։

Chuk
24.02.2015, 00:38
Թեման «Օրագրեր» բաժնից եկավ «Ծրագրավորում» բաժին: Այ հիմա որ չալարենք կարդանք, կարող ենք նաև քննարկել, հարցեր տալ և այլն :)

Արամ
25.02.2015, 00:42
Write-Ֆունկցիան՝
http://s18.postimg.org/7tk31ydx5/diagram_3_sysmain_write_1.png
Մի փոքր պարզաբանեմ՝
Սկսում ենք հերթով համեմատել ըստ սյուների հին արժեքը և նոր արժեքը, եթե տարբեր են ապա մեր կազմած հարցման մեջ ավելացնում ենք
դրա SET-ը։
Եթե աղյուսակի մեջ մեր SetID-ի արած ID-ն կար, այսինքն այդ տողը գոյություն ուներ ապա հարցումը կազմվում է, որպես UPDATE, եթե ոչ ապա INSERT:
Նրբություն, քանի որ այդ արժեքները գալիս են user-ից հնարավոր Injection-ից խուսափելու համար հարկավոր է, որպեսզի արժեքները որպես պարամետր փոխանցենք հարցմանը։ Դրա համար երբ արդեն իմանում ենք տարբերվող սյուների քանակը(N), մեր հարցմանը ավելացնում ենք param1,param2,...paramN, և զուգահեռաբար զանգված ենք ստեղծում, որի էլեմենտների ինդեկսները համընկնում են param1, param2, param3-ի․․․․ հետ։
Կոդի հատված՝

foreach (KeyValuePair<string, string> entry in this.cCurrentRec) {
if (entry.Value != this.cPhysicalRec[entry.Key]) {
tmpExistQueryValuesAssig += entry.Key + " = @param" + tmpCountOfCols.ToString() + ",";
tmpValues[tmpCountOfCols] = entry.Value;
tmpCountOfCols++;
}
}
Իսկ վերջում, երբ արդեն պարզ է թե հարցումը INSERT INTO է, թե UPDATE, ավելացնում ենք արժեքները՝
Կոդի հատված՝

for (int i = 0; i < tmpCountOfCols; i++) {
tmpSqlCommand.Parameters.AddWithValue("@param" + i.ToString(), tmpValues[i]);
}

Հաջորդ գրառումը կլինի սյուների validation—ների կազմակերպան մասին, որից հետո կամփոփենք ունեցածը, որպեսզի շարժվենք առաջ։

Արամ
25.02.2015, 19:58
Ինչպես խոսք էի տվել, սյուների Validation:

Գաղտնիք չէ, որ DataBase-ի հետ աշխատելու ընթացքում, երբ սյուներին արժեքներ ենք տալիս, պետք է ինչ որ բան ստուգենք, համեմատենք, ինչ որ պայմանների դեպքում նոր թույլ տանք։ Օրինակ ունենք 2 սյուն` StartingDate, EndingDate, երբ StartingDate-ը 02/25/2015 է, մենք պետք է թույլ չտանք, որպեսզի EndingDate-ը լինի 02/24/2015, ասյինքն ավելի շուտ։

Քանի որ ինտերֆեյսեր ենք տրամադրում, այսինքն մենք կոնկրետ օբյեկտի մեջ դեռ չգիտենք, թե ի՞նչ թեյբլի հետ ենք աշխատելու, ի՞նչ սյուններ է ունենալու այդ թեյբլը, ինչ տիպի օբյեկտ է մեզ ժառանգել և այլն․․․դրա համար որոշեցի, sysMain-ում պահենք ժառանգած օբյեկտի տիպը, որը ժառանգված օբյեկտը կփոխանցի։
Այսպես, կոդ՝

this.cInhertedClassType = typeof(this);
Հիմա հարց կարջանա, թե ինչի համար է այն մեզ պետք․․․պարզաբանեմ․․․
երկար մտածելուց հետո որոշեցի, որ ամեն սյունի validation-ի համար պատասխան է տալու համապատասխան ֆունկցիան, օրինակ՝

bool Validate_ColumnName(string value)
Այս ֆունկցաին գրելու է ժառանգված կլասում, ինչպե՞ս մենք իմանաք այս ֆունկցիա-ի մասին վերևի կլասից, մեզ կօգնի reflection-ը։
Այսինքն, երբ մենք SetValue ենք անում ինչ որ սյունի արժեք, մենք այդ մուտք եղած արժեքը փոխանցում ենք այ էս ֆունկցիաին, կոդի հատված՝

MethodInfo methodInfo = this.cInhertedClassType.GetMethod(tmpFunctionName, BindingFlags.NonPublic | BindingFlags.Instance);
if (methodInfo != null) {
bool tmpRetBool = (bool)methodInfo.Invoke(this, new object[] { arg_value });
if(tmpRetBool) {
retVal = sysConstant.retValidationSuccess;
} else {
retVal = sysConstant.retValidationFail;
}
}
Սրանով օգտվելով տիպ-ից կարողանում ենք պարզել արդյոք մեր գրած ֆունկցաին գոյություն ունի, ինչպե՞ս ենք իմանում ֆունկցիաի անունը, շատ պարզ՝

string tmpFunctionName = sysConstant.VALIDATION_FUNCTION_PREFIX + colName;
VALIDATION_FUNCTION_PREFIX -ը դիֆայն է արված որպես "Validate_":
Այսինքն արդյունքում մենք կունենաք, օրինակ Validate_StartingDate:

Եթե չենք գտնում նման ֆունկցիա, ուրեմն մենք առանց validation կարող ենք արժեքը հանգիստ սրտով տեղադրել։

Հուսով եմ հետաքրքիր էր: :)

Արամ
06.03.2015, 23:40
Այսոր վերջապես ահագին բան վերջացրեցի, եկեք տեսնենք թե ինչպես կարող ենք օգտագործել մեր framework-ը և ի՞նչ կարող ենք անել։
Նախ և առաջ պետք է ժառանգենք մեր հիմնական կլասից և տանք մեր աղուսյակի ու օբյեկտի տվյալները։ Կոդ՝

namespace Batut {
class TestPost : Main {
public TestPost() {
this.m_InhertedClassType = typeof(TestPost);
this.m_MainTable = "Post";
this.m_TableUniqueCol = "PostID";
this.m_SecurityAccess = Constant.securityWrite;
this.Init();
}
private bool Validate_LongPost(string value) {
return false;
}

}
}
Մանրամասն ամեն տողը՝
this.m_InhertedClassType = typeof(TestPost); - եթե հիշում եք սա մեզ անհրաժեշտ էր որպեսզի պարզեինք թե կոնկրետ մեր օբյեկտը validation-ի function-ներ ունի թե չէ
this.m_MainTable = "Post"; - աղուսյակի անունը
this.m_TableUniqueCol = "PostID"; - աղուսյակի կլաստերեդ ինդեքսը
this.m_SecurityAccess = Constant.securityWrite; - եսիմ խի եմ արել, հետագայում կպարզեմ ։Դ
this.Init(); - init—ը մեր տված արժեքների հիման վրա կլցնի մեր currentRec, physicalRec dictionary—ները

հիմա եկեք ստեղծենք մեր օբյեկտը և նոր տող ավելացնենք՝


TestPost model = new TestPost(); // ստեղծում ենք մեր օբյեկտը
int retVal = model.SetId("1"); // Ավելացնում ենք նոր տող 1 ID-ով
if (retVal == Constant.edtNew) { // ստուգում ենք, որ տողը նոր լինի
model.SetValue("ShortPost","karch");
model.SetValue("LongPost","erkar"); // այս արժեքը չի ավելանա, որովհետև վերևում վալիդեյշն ենք գրել, որը false է վերադարձնում
model.Write();
}



Շատ ուրախ կլինեմ, որ ներկայացրածս կոդերի մեջ թերացումներս ցույց տաք։ (առաջին փորձս է C#-ով ինչ որ բան գրելու)
Նաև ուրախ կլինեմ, եթե ինչ որ հարցեր լինեն, խոսենք, քննարկենք, ինչ որ նոր բաններ ավելացնենք։
Թույալտրվում ա ))

Lusina
07.03.2015, 01:07
Շատ ուրախ կլինեմ, որ ներկայացրածս կոդերի մեջ թերացումներս ցույց տաք։ (առաջին փորձս է C#-ով ինչ որ բան գրելու)
Նաև ուրախ կլինեմ, եթե ինչ որ հարցեր լինեն, խոսենք, քննարկենք, ինչ որ նոր բաններ ավելացնենք։
Թույալտրվում ա ))

Նկար (https://qph.is.quoracdn.net/main-qimg-e0c9dafb319150b6c6d9816047ed9eae)

Արի կլինի տեղափոխվի ձախ ճամբար էլի :oy

Հ.Գ. Ներող, չդիմացա, "Թույալտրվում ա "-ն չլիներ, չէի գրի :))

Արամ
07.03.2015, 17:24
Նկար (https://qph.is.quoracdn.net/main-qimg-e0c9dafb319150b6c6d9816047ed9eae)

Արի կլինի տեղափոխվի ձախ ճամբար էլի :oy

Հ.Գ. Ներող, չդիմացա, "Թույալտրվում ա "-ն չլիներ, չէի գրի :))
Հետագայում, երբ որ կոդը դնեմ համացանցում, կփոխեմ։

Արամ
08.03.2015, 15:40
Ձեր արձագանքերը այդքան էլ հուսադրող չեն, բայց դե այսօր մի քիչ բզբզացի, կոդերը գցեցի github, կարող եք նայել, ուսումնասիրել, ուղղել և այլն․․
Հա՛, թույլատրվում ա ))

https://github.com/AramKocharyan/BatutConnector