Tfs laika uzskaite (d3) - Refactor!

Kāds laiciņš jau ir pagājis kopš iepriekšējā laika uzskaites moduļa apraksta emuāra raksta.

Šo pāris mēnešu laikā nebija gan laika, gan arī citu apstākļu, lai turpinātu iesākto darbu. Tomēr pēdējā laikā šis temats palika aktuāls un nolēmu pabeigt reiz iesākto un publicēt visus plānotos rakstus par laika uzskaites moduļa izveidi TFS serverī.

Diezgan liels laika sprīdis ir pagājis kopš tika izveidota pirmā versija un laiks ir apskatīties uz to vēlreiz.

Tātad visām nepieciešamajām operācijām mēs izveidojām speciālu WCF interfeisu un servisa implementāciju šo operāciju realizācijai. Tā kā servisa interfeiss bija pavisam vienkāršs - CRUD, nolēmu tomēr aplūkot kādas citas nu jau pa šo laiku iznākušas tehnoloģijas. ADO.NET Data Services nodrošina visas mums nepieciešamās operācijas un atvieglotu arī klienta kodu no servisa implementācijas un klienta puses proxy klases.

Tātad refactor saraksts:

  1. No LINQ to SQL pāriet uz Entity Framework (EF);
  2. No WCF servisa implementācijas pāriet uz ADO.NET Data Services (DS);
  3. Reorganizēt klienta puses (Visual Studio Team Explorer host) kodu, lai izmantotu jauno servisa piegādātāju;

 

Lai raksts nepārvērstos par pamācību šajās tehnoloģijās, tad EF un DS izveidi saīsināšu līdz key-point'iem.

 

 

Entity Framework

Izveidojam jaunu EF modeli, kas izskatās šādi:

 

 

Piezīme: LINQ to SQL versiju atsekošanas kolona (Timestamp [binary]) vairāk nav nepieciešama. Precīzu grāmatvedību tagad veic EF.

 

 

Data Services

Izveidojam DS instanci un neaizmirstam arī to pareizi nokonfigrēt.

 

public static void InitializeService(IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule("WorkLog", EntitySetRights.All);
config.SetEntitySetAccessRule("TimeUnitType", EntitySetRights.All);
config.SetEntitySetAccessRule("WorkLogType", EntitySetRights.All);
}

NB! Lai vieglāk būtu atsekot kļūdas izpildes režīmā, tad noteikti var ieslēgt:

 

config.UseVerboseErrors = true;

 

Kā arī pašam servisam pievienot sekojošu atribūtu:

 

[ServiceBehavior(IncludeExceptionDetailInFaults = true)]

 

Šīs rindiņas, protams, vēlams ražošanas kodā nekompilēt, ja vien tas nav vitāli nepieciešams. Jāpiemin, ka personīgi man nepatīk šāds deklaratīvs pieejas tiesību noteikšanas veids, bet tā ir DS īpatnība.

 

 

Klienta puses koda Refactor

Tas arī viss ar servera puses konfigurāciju un izstrādi: esam izveidojuši servisu, kas nodrošinās visas CRUD nepieciešamās operācijas. Tālāk varam nodarboties ar klienta puses refactor. Šim mērķim izveidosim speciāli projektu (DataAccessLayer), kas piedāvās mums DS pieeju no mums nepieciešamajiem klientiem (Visual Studio, TFS Web Access). Klases sauksies - DataAccessManager. Klase nodrošinās visas nepieciešamās operācijas iekšēji izmantojot reference uz izveidotajiem DS un tās struktūra ir šāda:

 

 

Kad esam izveidojuši klienta puses servisa piegādātāju, varam turpināt ar pašu UserControl, kas nodrošinās mums laika uzskaites ierakstu attēlošanu konkrētajam TFS uzdevumam (WorkItem). Lai kontrole korekti darbotos, mums ir nepieciešams realizēt IWorkItemControl interfeisu, kas atrodas Microsoft.TeamFoundation.WorkItemTracking.Controls.dll bibliotēkā. Šis interfeiss ir sadarbības modelis starp saimnieku (Visual Studio) un lietotāja kontroli.

 

 

Šim interfeisam ir pāris ģalvenie punkti, kuri ir interesanti:

  1. InvalidateDatasource(): šī metode tiek izsaukta, kad notiek kontroles ielādēšana vai notiek konteksta maiņa. Piemēram, ja mēs atveram vairākus uzdevumus (WorkItem) pēc kārtas, tad lietotāja kontroles instance ir viena, bet katru reizi tiek izsaukta šī metode, lai nodotu informāciju lietotāja kontrolei par to, ka ir mainījies konteksts;
  2. WorkItemDatasource {}: šī īpašība tiek izmantota, lai mainoties kontekstam (sk. iepriekšējo punktu) lietotāja kontrolei nodotu informāciju par uzdevumu, kurš tiek aplūkots pašreiz;
  3. FlushToDatasource(): šo metodi var izmantot, lai saglabātu visas veiktās izmaiņas laika uzskaites sistēmā. Lai saglabātu vienkāršību, šajā modulī visas operācijas uz datu bāzi tiek veiktas on-fly un speciāla kešatmiņa uz klienta netiek realizēta.

 

List()

Pēc sekmīgas kontroles izvietojuma uzzīmēšanas

 

 

varam ķerties klāt pie loģikas realizācijas. Pirmais, kas ir jānodoršina ir saraksta attēlošana ar laika ierakstiem konkrētajam uzdevumam. Šim nolūkam varam izmantot InvalidateDatasource() metodi.

Noignorējot visas UI lietas, kods izskatās apmēram šādi:

 

this.workLogBindingSource.DataSource = this.manager.GetList(this.WorkItemId);
this.workLogBindingSource.ResetBindings(true);

 

Savukārt DataAccessManager saraksta iegūšanas realizācija ir šāda:

 

public IEnumerable<WorkLogService.WorkLog> GetList(int workItemId)
{
var ctx = this.GetServiceContext();
// need to return list in case of DataSource usage -> ToEnumerable() will not bind
return ctx.WorkLog.Expand("TimeUnit").Expand("LogType").Where(w => w.WorkItemId == workItemId).ToList();
}

 

Pievēršiet uzmanību Expand() metodes izsaukumiem, tās ir nepieciešamas, lai atgrieztajā rezultātā WorkLog objektam jau būtu ielādēts gan laika mērvienības atbilstošais objekts, gan arī pavadītā laika tipam ("izstrāde", "atpūta", etc). Šie objekti tālāk tiek izmantoti izmaiņu atsekošanai un korektai datu attēlošanai.

 

Šeit ir jāpiemin, ja rezultātam, kas nāk no DS, atgrieztu AsEnumerable() metodes izsaukuma rezultātu, tad Windows Forms BindingSource objektam tas diezko nepatīk, jo tas vēl nesatur nevienu elementu, tāpēc šim nolūkam rezultātu konvertējam uz sarakstu.

DS servisa instanci iegūst ar šāda koda palīdzību:

 

private WorkLogContext GetServiceContext()
{
// assign credentials to take into account Windows Integrated Authentication on target site
var ctx = new WorkLogContext(Configuration.ServiceUri)
{
Credentials = CredentialCache.DefaultNetworkCredentials
};

return ctx;
}

 

Šeit jāpiemin, ka DS servisa realizācijas iegūšanai ir nepieciešams zināt servisa Uri adresi. Par cik no dažādām vidēm (Visual Studio un Web Access) System.Configuration.ConfigurationManager klases izmantošana būtu apgrūtināta, tad nolēmu ieviest nepieciešamo adresi Windows Registry, kur visiem tas būtu ērti pieejams. Šim nōlūkam tika izveidota speciāla klase, kas nolasa reģistra vērtības:

 

public static string ReadKeyValue(string key)
{
RegistryKey result = Registry.LocalMachine;
if (result == null)
{
throw new InvalidOperationException("Failed to open LOCAL_MACHINE key.");
}

var delimited = rootKey.Split('/');
foreach (var node in delimited)
{
result = result.OpenSubKey(node);
}

if (result == null)
{
throw new InvalidOperationException("Failed to open registry key '" + rootKey + "'");
}

string keyValue = result.GetValue(key) as string;
result.Close();

return keyValue;
}

 

Pēc klienta koda realizācijas un saraksta iegūšanas, varam izveidoto kontroli reģistrēt TFS serverī. Šim nolūkam ērtības labad būs pieciešams TFS 2008 Power Tools, lai veiktu uzdevuma definīcijas labojumus.

Tātad, lai reģistrētu jauno lietotāja kontroli kādam no uzdevumu tipiem, ir nepieciešams spert sekojošus soļus:

  • Iegūt kāda uzdevuma definīcijas failu (wit.xml). To var izdarīt ar pieminētajiem Power Tools
  • iegūtajā nepieciešamajā uzdevuma definīcijas failā ir nepieciešams reģistrēt lietotāja kontroli. Ērtāku reģistrāciju var veikt ar Power Tools iekļauto WorkItem Template Editor

 

 

  • Pēc ūzdevuma definīcijas rediģēšanas, tā ir jānovieto atpakaļ TFS serverī.
  • Lietotāja kontroles bibliotēkas ir jāiekopē All Users Application Data folderī, kurā Visual Studio meklē reģistrēto kontroli. Par šo tēmu tiks runāts "Client Deployment" raksta daļā.

 

Uzdevuma definīcijas faila iegūšanai un izmaiņu publicēšanai atpakaļ TFS serverī var izmantot witexport.exe un witimport.exe komandrindas rīkus, kas nāk līdzi TFS servera instalācijai.

Pēc sekmīgas jaunās lietotāja kontroles reģistrācijas Task uzdevuma tipā, varām pārbaudīt kontroles darbību dzīvē:

 

 

 

C/U/D()

Saraksta attēlošana darbojas lieliski. Tālāk varam realizēt pārējās operācijas, kas nepieciešams - Create, Update, Delete.

Laika ieraksta pievienošanai nevajadzētu būt sarežģītākai par šo kodu:

 

var item = DataLayer.WorkLogService.WorkLog.New();
Edit editForm = new Edit(this.manager, item);
DialogResult result = editForm.ShowDialog(this);

if (result == DialogResult.OK)
{
// save worklog
this.manager.Save(item);
((IWorkItemControl)this).InvalidateDatasource();
}

 

Tas pats attiecas uz rediģēšanu un dzēšanu:

 

private void EditWorkLog()
{
var entry = this.CurrentWorkLogEntry;
var copy = entry.Clone();

Edit editForm = new Edit(this.manager, copy);
DialogResult result = editForm.ShowDialog(this);

if (result == DialogResult.OK)
{
// save worklog
this.manager.Save(copy);
((IWorkItemControl)this).InvalidateDatasource();
}
}

 

private void DeleteWorkLog()
{
DialogResult result = MessageBox.Show("Delete?",
"WorkLog",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2);

if (result == DialogResult.Yes)
{
this.manager.Delete(this.CurrentWorkLogEntry);
((IWorkItemControl)this).InvalidateDatasource();
}
}

 

Tomēr, lai realizētu šāda veida rediģēšanas operācijas, ir nedaudz jāpierod pie DS uzvedības un asociāciju pārvaldības (relationship management).

Tātad, DataAccessManager klases Save() operācija ir realizēta sekojoši:

 

public void Save(WorkLogService.WorkLog item)
{
if (item.TimeUnit == null)
{
throw new InvalidOperationException("TimeUnit association is not set.");
}

if (item.LogType == null)
{
throw new InvalidOperationException("LogType association is not set.");
}

var ctx = this.GetServiceContext();

// update default values
item.WorkLogged = DateTime.Now;
item.UserName = (this.Username ?? this.ExtractUsernameFromDomain());

if (item.WorkLogId.Equals(Guid.Empty))
{
// insert
item.WorkLogId = Guid.NewGuid();
ctx.AddToWorkLog(item);
}
else
{
// update
ctx.AttachTo("WorkLog", item);
ctx.UpdateObject(item);
}

// refresh associations
ctx.SetLink(item, "TimeUnit", this.GetTimeUnit(item.TimeUnit.TimeUnitTypeId, ctx));
ctx.SetLink(item, "LogType", this.GetLogType(item.LogType.WorkLogTypeId, ctx));

ctx.SaveChanges(SaveChangesOptions.Batch);
}

 

Galvenā uzmanība ir jāpievērš koda apgabaliem, kas darbojas insert un update situācijās. Insert gadījumā izmantojam AddTo() metodi, bet Update gadījumā ir jāizmanto AttachTo() metodi, lai pievienotu nepieciešamo objektu DS kontekstam jeb DS klienta proxy klasei, kura veic visas grāmatvedības operācijas. Vēl būtu jāpievērš uzmanība tam, kādā veidā tiek pārvaldītas saites starp objektiem. Lai arī izmaiņas skar tikai, piemēram, laika ieraksta aprakstu, nākas refreshot visas saites, kuras attiecīgais objekts satur un refreshot nākas ar identiskiem objektiem, kuri iegūti no tās pašas DS klienta proxy klases ar kuras palīdzību tiks veikta nepieciešamā operācija. Šis liekas nedaudz odd piegājiens, bet ticiem man, izmēģinājos N-tos variantus šajā koda apgabalā - šis bija vienīgais, kas strādāja.

Delete() operācija ir nedaudz vienkāršana:

 

public void Delete(WorkLogService.WorkLog workLog)
{
var ctx = this.GetServiceContext();
ctx.AttachTo("WorkLog", workLog);
ctx.DeleteObject(workLog);
ctx.SaveChanges();
}

 

Ja jūs vēl lasāt šo rakstu, tad pēc tam, kad norealizēta loģika, varam ķerties pie vizuālās daļas :)

 

 

Par cik ģenerētie DS proxy klases objekti nerealizē IEditableObject interfeisu, kuru savukārt var izmantot Windows Forms BindingSource objekts, lai atceltu veiktās izmaiņas rediģēšanas logā, un šāda paša veida operācijas nepieciešams veikt arī Web vidē, tad nepieciešama būs klonēšanas operācija atsevišķam laika ierakstam. Tas ir panākams ar daļējās klases īpašībām:

 

public partial class WorkLog
{
public static WorkLog New()
{
var result = new WorkLog();
result.WorkLogDateTime = DateTime.Now;
return result;
}

public WorkLog Clone()
{
var result = new WorkLog
{
WorkLogId = this.WorkLogId,
WorkLogDateTime = this.WorkLogDateTime,
WorkItemId = this.WorkItemId,
UserName = this.UserName,
WorkDone = this.WorkDone,
TimeUnit = this.TimeUnit,
Description = this.Description,
LogType = this.LogType,
WorkLogged = this.WorkLogged
};
return result;
}
}

 

Klonēšana nepieciešama, lai varētu pilnvērtīgi izmantot Windows Forms DataBinding iespējas, jo mainot sabindota lauku vērtību kādā no kontrolēm (piemēram, TextBox), mainās arī tās vērtība pašā objekta instancē. No rediģēšanas operācijas redzams, kur tas tiek izmantots:

 

var entry = this.CurrentWorkLogEntry;
var copy = entry.Clone();

Edit editForm = new Edit(this.manager, copy);

 

Ērtākai un ātrākai lietotāja kontroles testēšanai noteikti ir vērts arī izveidot savu Windows Client host aplikāciju, kas simulē Visual Studio uzvedību, jo pārbaude no Visual Studio var aizņemt daudz vairāk laika nekā nepieciešams.

Nākamā raksta daļa tiks veltīta Web Access pieejas interfeisa realizācija šim laika uzskaites modulim.

 

Lai visa šī rakstu sērija nebūtu tikai tukšu salmu kulšana - beigās tiks pievienots arī strādājošs izejas kods visām bibliotēkām, kas tika izveidotas šajā rakstu sērijā :))

 

 

 

Tfs laika uzskaites sērijas:

 

 

 

Cerams, ka noderēs!

Published Monday, October 20, 2008 11:37 PM by valdis.iljuconoks

Comments

# Jautajums

Sorry, par spamu.

Vai tev gadījumā nav zināms kā ar EF un Linq to Entities var izveidot GridView (viss ir no koda puses), kur viena no kolonnām būs Lookups. (Respektīvi - tā būs no TemplateField mantota klase ar kontroli DrowDown.)

Piemēram, ir trīs tabulas Orders-OrderDetails-Products un man vajaga GridView-ā attēlot visus Products un vienā kolonnā Lookup uz Orders tabulu (piemērma, Orders Name).

Wednesday, October 22, 2008 2:17 PM by dropdownis

# re: Tfs laika uzskaite (d3) - Refactor!

ļoti iespējams, ka jātaisa būs subselect, vismaz tā uz doto momentu man izskatās. ja tas nav ļoti steidzami, tad tuvākā laikā izveidotu smilškasti tavam piemēram, lai noskaidrotu precīzu vaicājumu.

Wednesday, October 22, 2008 2:54 PM by valdis.iljuconoks

# Hello

Great – I should definitely pronounce, impressed with your web site. I had no trouble navigating through all the tabs as well as related info ended up being truly simple to do to access. I recently found what I hoped for before you know it in the least. Quite unusual. Is likely to appreciate it for those who add forums or anything, website theme . a tones way for your customer to communicate. Nice task..  <a href="topautoinsurancerates.net/">auto insurance rates</a>

Sunday, October 23, 2011 8:29 AM by car insurance rates

# regodToVBlUoZEt

hFPA5X Muchos Gracias for your blog post.

Friday, March 23, 2012 6:01 AM by buy google plus

Leave a Comment

(obligāts) 
(obligāts) 
(brīvizvēles)
(obligāts)