classic asp/vbscript class to track employees and performance metrics - class

I am trying to create a classic asp/vbscript class that will allow me to easily manage a small number of employees (30-40) along with some metrics associated with those employees, about 14 metrics each. I've done some tutorials online and can't quite get how I should proceed. What I have so far is below. It's not much, basically I think I can only add the employees to a dictionary in the class, but I don't know where to go from here.
class iagent
private di_agents
private ar_metrics
private pri_agent_counter
Public function add_agent(uuid)
di_agents.Add uuid, pri_agent_counter
pri_agent_counter=pri_agent_counter+1
end function
private sub Class_initialize
pri_agent_counter=1
dim ar_metrics(14, 5)
set di_agents = CreateObject("Scripting.Dictionary")
end sub
end class

The class you have is just a wrapper around a dictionary. Are you talking about creating a class that represents an employee?
Class Employee
Public Name
Public Age
Public Phone
'other properties
End Class
Then you can instantiate Employee like this and set your properties
Set e = New Employee
e.Name = "Some name"
You could then store your instances of Employee in a dictionary, perhaps paired with an ID:
Set d = CreateObject("Scripting.Dictionary")
Call d.Add(uuid, e)
However, you're better off using a database for this and using ASP/VBS to extract records... Unless this is just an exercise

Related

MVVM: list in viewmodel consists of entities but I don't think it's right to place logic in entity poco classes

I have a viewmodel and it exists out of entity framework classes (just plain classes). I Read on stackoverflow that it's okey to put an inotifypropertychanged in the model. So I did that in order not to have a lot of extra code.
So, now I have to perform an action when a property changes in one of my list items... the thing is, I can't react on this, in my viewmodel...
What do I do?
I have come across this issue once I started making larger projects with the MVVM pattern.
In the end I just shifted my INotifyPropertyChanged event calls to the ViewModel properties. I also decorated these properties with validation notification as that is where most of the validation that needs a user to react to will be held, any common elements can also be pulled out to a base ViewModel where relevant.
Another issue that you might come across as time goes by is where to put some of your processing logic. I used to have mine at the Model level, but then some of them required a bit closer interaction with the UI (validation reasons mainly), but if I put it in the ViewModel then I would have to have references to my repositories etc. In the end my ViewModels have access to another layer (a Service Layer) where this takes place. (A prime example for the use of this would be if you have a column in a database which is unique that you need to check when a user has created/modified data belonging to this column.)
Anyway, that is just a bit of extra information that I had to figure out shortly after the issue your currently having!
i fought with the best stategy for this for a while. I found that there are a number of different ways to do this.
First thing to note, if you add the property validation/notification directly to your model classes then the notifictaion and validation will happen when the class is created and for each object that gets created in observable collections, which in turn can cause performance issues or other challenges. So for that reason I moved my validation and notification into a "helper" partial class for the model.
So the model gets created through the Entity framework, lets say for an object called Job. I create public partial class called job as well. Here is an example (in vb, I can convert to C# if you need)
Partial Public Class job
Inherits ValidationBase
#Region "CONSTRUCTORS"
Public Sub New()
''default values
Me.FTC_Type = 4
Me.dtCreated = Now
Me.dtUpdated = Now
HasChanges = False
End Sub
Public Sub New(bValidate As Boolean)
PropertyValitaion(bValidate)
''default values
Me.FTC_Type = 4
Me.dtCreated = Now
Me.dtUpdated = Now
HasChanges = False
End Sub
Public ReadOnly Property DisplayPath
Get
Return "W" + idJob.ToString + ": " + chrTitle + " - " + client.chrCompany
End Get
End Property
Public ReadOnly Property SearchPath
Get
Return "W" + idJob.ToString + " " + chrTitle + " " + client.chrCompany + " " + chrContact
End Get
End Property
#End Region
#Region "VALIDATION FUNCTIONS"
Public Overrides Function Validate(validationContext As ComponentModel.DataAnnotations.ValidationContext) As IEnumerable(Of ComponentModel.DataAnnotations.ValidationResult)
Return MyBase.Validate(validationContext)
PropertyValitaion(True)
End Function
Public Sub PropertyValitaion(bAllProperties As Boolean, Optional sProperty As String = "")
'initialize validation helper
If bAllProperties OrElse sProperty = "chrTitle" Then
If String.IsNullOrEmpty(chrTitle) Then
AddError("chrTitle", "You must enter a Job Title")
Else
RemoveError("chrTitle")
End If
End If
End Sub
#End Region
End Class
So you can see that I can create other readonly properties (Like display title etc) and create my own custom validation that gets called when I want. THis validation uses the IDataErrorInfo for getting tied back into the UI. I have two constructors, one the calls the property validation and one that does not. That way I can control when it happens.
I created a ValidationBase class that my Objects partial class inhertis from. THis saves me from having to manually implement IdataErrorInfo and INotifyPropertyCHnaged in every partial class for every object.
Here is my validation base:
Imports System.ComponentModel
Imports System.Collections.Concurrent
Imports System.ComponentModel.DataAnnotations
Imports System.ComponentModel.DataAnnotations.Schema
Public Class ValidationBase
Implements IValidatableObject, IDataErrorInfo, INotifyPropertyChanged
#Region "DECLARATIONS"
Protected _propertyErrors As New Dictionary(Of String, String)
Protected _validationResults As New List(Of ValidationResult)
Public ReadOnly Property HasErrors() As Boolean
Get
Return (_propertyErrors.Count + _validationResults.Count) > 0
End Get
End Property
#End Region
#Region "IValidatableObject IMPLEMENTATION"
Public Overridable Function Validate(validationContext As ValidationContext) As IEnumerable(Of ValidationResult) Implements IValidatableObject.Validate
Return Nothing
End Function
#End Region
#Region "iDataError OBJECTS"
'Returns an error message
'In this case it is a general message, which is
'returned if the list contains elements of errors
Public ReadOnly Property [Error] As String Implements System.ComponentModel.IDataErrorInfo.Error
Get
If _propertyErrors.Count > 0 Then
Return "Object data is invalid"
Else
Return Nothing
End If
End Get
End Property
Default Public ReadOnly Property Item(ByVal columnName As String) As String Implements System.ComponentModel.IDataErrorInfo.Item
Get
If _propertyErrors.ContainsKey(columnName) Then
Return _propertyErrors(columnName).ToString
Else
Return Nothing
End If
End Get
End Property
#End Region
#Region "IDataError FUNCTIONS"
'Adds an error to the collection, if not already present
'with the same key
Protected Sub AddError(ByVal columnName As String, ByVal msg As String)
If Not _propertyErrors.ContainsKey(columnName) Then
_propertyErrors.Add(columnName, msg)
OnPropertyChanged(columnName)
End If
End Sub
'Removes an error from the collection, if present
Protected Sub RemoveError(ByVal columnName As String)
If _propertyErrors.ContainsKey(columnName) Then
_propertyErrors.Remove(columnName)
OnPropertyChanged(columnName)
End If
End Sub
Public Sub ClearErrors()
_propertyErrors.Clear()
End Sub
#End Region
#Region "INotifyPropertyChanged IMPLEMENTATION"
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Public Overridable Sub OnPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
#End Region
End Class
SO i can't say this is the totally right way because there does not seem to be a clear best pratcie defined out there. But this works for me and I hope it can help you.

DbSet.Find method ridiculously slow compared to .SingleOrDefault on ID

I have the following code (Database is SQL Server Compact 4.0):
Dim competitor=context.Competitors.Find(id)
When I profile this the Find method takes 300+ms to retrieve the competitor from a table of just 60 records.
When I change the code to:
Dim competitor=context.Competitors.SingleOrDefault(function(c) c.ID=id)
Then the competitor is found in just 3 ms.
The Competitor class:
Public Class Competitor
Implements IEquatable(Of Competitor)
Public Sub New()
CompetitionSubscriptions = New List(Of CompetitionSubscription)
OpponentMeetings = New List(Of Meeting)
GUID = GUID.NewGuid
End Sub
Public Sub New(name As String)
Me.New()
Me.Name = name
End Sub
'ID'
Public Property ID As Long
Public Property GUID As Guid
'NATIVE PROPERTIES'
Public Property Name As String
'NAVIGATION PROPERTIES'
Public Overridable Property CompetitionSubscriptions As ICollection(Of CompetitionSubscription)
Public Overridable Property OpponentMeetings As ICollection(Of Meeting)
End Class
I defined the many to many relations for CompetitionSubscriptions and OpponentMeetings using the fluent API.
The ID property of the Competitor class is a Long which is translated by Code First to an Identity column with a primary key in the datatable (SQL Server Compact 4.0)
What is going on here??
Find calls DetectChanges internally, SingleOrDefault (or generally any query) doesn't. DetectChanges is an expensive operation, so that's the reason why Find is slower (but it might become faster if the entity is already loaded into the context because Find would not run a query but just return the loaded entity).
If you want to use Find for a lot of entities - in a loop for example - you can disable automatic change detection like so (can't write it in VB, so a C# example):
try
{
context.Configuration.AutoDetectChangesEnabled = false;
foreach (var id in someIdCollection)
{
var competitor = context.Competitors.Find(id);
// ...
}
}
finally
{
context.Configuration.AutoDetectChangesEnabled = true;
}
Now, Find won't call DetectChanges with every call and it should be as fast as SingleOrDefault (and faster if the entity is already attached to the context).
Automatic change detection is a complex and somewhat mysterious subject. A great detailed discussion can be found in this four-part series:
(Link to part 1, the links to parts 2, 3 and 4 are at the beginning of that article)
http://blog.oneunicorn.com/2012/03/10/secrets-of-detectchanges-part-1-what-does-detectchanges-do/

Entity Framework: Many-to-Many

I have the following scenario: A Doctor can have multiple Companions. A Companion can also have multiple Doctors. Here are my classses (minus the context):
Public Class Doctor
Public Property DoctorId As Integer
Public Property Regeration As Integer
Public Property Name As String
Public Property Actor As String
Public Property Companions As List(Of Companion)
End Class
Public Class Companion
Public Property CompanionId As Integer
Public Property Name As String
Public Property Actor As String
Public Property Doctors As List(Of Doctor)
End Class
Public Class DoctorViewModel
Public Property DoctorId As Integer
Public Property Actor As String
Public Property Companions As List(Of CompanionViewModel)
End Class
Public Class CompanionViewModel
Public Property Actor As String
End Class
I'm trying to fetch a singular Doctor with a list of companions who have travelled with him. This I can do quite simply, but I'm trying to shape the query to get only a few columns and not the entire entity. He is my bungled query - the X's are what I can't figure out.
Dim query = From d In context.Doctors
From c In context.Companions
Select New DoctorViewModel With {
.Actor = d.Actor,
.DoctorId = d.DoctorId,
.Companions = XXXXXXX}
EDIT: If I query:
(From d In Tardis.Doctors
Where d.Actor = "Tom Baker"
Select New DoctorViewModel With {.Actor = d.Actor, .Companions = d.Companions.Select(Function(c) New CompanionViewModel With {.Actor = c.Actor})}).SingleOrDefault
I get:
"Unable to cast the type 'System.Collections.Generic.IEnumerable1' to
type 'System.Collections.Generic.List1'. LINQ to Entities only
supports casting Entity Data Model primitive types."
Would it be considered nasty to ditch the ViewModel classes in the query and just get the stuff as an anonymous type, then pass this to a constuctor in a ViewModel (my models have a whack of functions that are needed) and fill the class like this?
It probably would be not only okay but much more readable (aka maintainable) as well. Especially since the query wouldn't return an anonymous type, it would return an IQueryable
Solved! I've spent days on this! Trick was to change the List inside the Doctor ViewModel to IEnumerable(Of CompanionViewModel)

Question about ASP.NET MVC 2 Custom ViewModels

In my project I have my Linq To SQL dbml file, A Repository Layer for each DB Table and a Service Layer for each Repository.
In my service I have some MetaData for Validation as well as I extend each (table) Class to add some custom information to the object (you'll see this in the code below).
My question is, Should I consider building a Custom ViewModal for each (table) Class instead of using the extended Class in the service layer?
Below is an example of what I have now.
Repository
Namespace Domain
#Region "Interface"
Public Interface IUserRepository
Sub AddUser(ByVal openid As OpenID)
Function GetUsers() As IQueryable(Of User)
Sub UpdateUser(ByVal user As User)
Sub SubmitChanges()
End Interface
#End Region
#Region "Repository"
Public Class UserRepository : Implements IUserRepository
Private dc As MyDatabaseDataContext
Public Sub New()
dc = New MyDatabaseDataContext
End Sub
Public Sub AddUser(ByVal openid As OpenID) Implements IUserRepository.AddUser
Dim user As New User
user.MemberSince = DateTime.Now
openid.User = user
dc.OpenIDs.InsertOnSubmit(openid)
End Sub
Public Function GetUsers() As IQueryable(Of User) Implements IUserRepository.GetUsers
Dim users = (From u In dc.Users
Select u)
Return users.AsQueryable
End Function
Public Sub UpdateUser(ByVal user As User) Implements IUserRepository.UpdateUser
Dim _user = (From u In dc.Users
Where u.ID = user.ID
Select u).Single
With _user
.About = user.About
.BirthDate = user.BirthDate
.Email = user.Email
.isClosed = user.isClosed
.isProfileComplete = user.isProfileComplete
.RegionID = user.RegionID
.Reputation = user.Reputation
.UserName = user.UserName
.WebSite = user.WebSite
End With
End Sub
Public Sub SubmitChanges() Implements IUserRepository.SubmitChanges
dc.SubmitChanges()
End Sub
End Class
#End Region
End Namespace
Service
Imports System.ComponentModel.DataAnnotations
Namespace Domain
#Region "Validation"
<MetadataType(GetType(UserMetaData))> _
Partial Public Class User
Public Property UserRegion As String
Public Property LastSeen As DateTime
Public ReadOnly Property Slug(ByVal user As User) As String
Get
Return Replace(user.UserName, " ", "-")
End Get
End Property
End Class
''' <summary>
''' Validation for all User data.
''' </summary>
''' <remarks>All validation is done at the Service Layer</remarks>
Public Class UserMetaData
<DisplayName("name")> _
<Required(ErrorMessage:="Username is required.")> _
<StringLength(30, ErrorMessage:="Username cannot exceed 30 characters.")> _
<RegularExpression("^\w{3,30}$", ErrorMessage:="Not a valid username.")> _
Public Property UserName As String
<DisplayName("email")> _
<StringLength(50, ErrorMessage:="Email Address cannot exceed 50 characters.")> _
<RegularExpression("^([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})$", ErrorMessage:="Not a valid email address.")> _
Public Property Email As String
<DisplayName("website")> _
<StringLength(256, ErrorMessage:="Web Address cannot exceed 256 characters.")> _
<RegularExpression("^http(s?)\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?$", ErrorMessage:="Not a valid website address.")> _
Public Property WebSite As String
<DisplayName("about")> _
<StringLength(2000, ErrorMessage:="Profile cannot exceed 2000 characters.")> _
Public Property About As String
<DisplayName("region")> _
<Required(ErrorMessage:="Region is required.")> _
Public Property UserRegion As Integer
<DisplayName("birthdate")> _
<DisplayFormat(ApplyFormatInEditMode:=True, ConvertEmptyStringToNull:=True, DataFormatString:="{0:MM/dd/yyyy}")> _
Public Property BirthDate As DateTime
End Class
#End Region
#Region "Interface"
Public Interface IUserService
Sub AddUser(ByVal claimedidentifier As String, ByVal notes As String)
Function GetAllUsers() As IList(Of User)
Function GetUserByID(ByVal id As Integer) As User
Sub UpdateUser(ByVal user As User)
Sub SubmitChanges()
End Interface
#End Region
#Region "Service"
Public Class UserService : Implements IUserService
Private _UserRepository As IUserRepository
Public Sub New(ByVal UserRepository As IUserRepository)
_UserRepository = UserRepository
End Sub
Public Sub AddUser(ByVal claimedidentifier As String, ByVal notes As String) Implements IUserService.AddUser
Dim openid As New OpenID
openid.ClaimedIdentifier = claimedidentifier
openid.UserNotes = notes
_UserRepository.AddUser(openid)
End Sub
Public Function GetAllUsers() As System.Collections.Generic.IList(Of User) Implements IUserService.GetAllUsers
Return _UserRepository.GetUsers().Where(Function(u) (Not u.isClosed)).ToList
End Function
Public Function GetUserByID(ByVal id As Integer) As User Implements IUserService.GetUserByID
Return _UserRepository.GetUsers().Where(Function(u) (Not u.isClosed And u.ID = id)).SingleOrDefault
End Function
Public Sub UpdateUser(ByVal user As User) Implements IUserService.UpdateUser
_UserRepository.UpdateUser(user)
End Sub
Public Sub SubmitChanges() Implements IUserService.SubmitChanges
_UserRepository.SubmitChanges()
End Sub
End Class
#End Region
End Namespace
And currently in my Controller I send Modal Data to my View like this
Dim user As Domain.User = UserService.GetUserByID(id)
Return View(user)
Now one thing I have run into is the need send the user object to the Slug property whenever I need to use the Slug
Dim user As Domain.User = UserService.GetUserByID(id)
user.Slug = user.Slug(user) ''# this seems like a bit of a pain in the ass
Return View(user)
So because of this, is it better for me to create a custom ViewModal for each (table) Class and simply do the following
Dim user As Domain.UserViewModal = New Domain.UserViewModal(UserService.GetUserByID(id))
''# The UserViewModal will automatically do all the work to create the
''# Slug as well as other pertinent information
Return View(user)
It seems to me like the separation is a good thing, but still requires a butt load of time to build. Just wondering about the trade off benefit or if there is a better way to accomplish the same thing?
You're likely going to find that your model classes don't always correspond 1:1 with views. For this reason alone it makes sense to create ViewModel objects and use them to interact with your views as a viewmodel object can be a composite of various model information.
Doing so has the added benefit of ensuring that your viewmodel objects are specifically suited to the user interface and may contain screen-specific lists or other properties that have no business in a normal model object. This allows you to realize benefits conversely as well because your viewmodel objects need not be cluttered/bloated with anything except their purpose in life. (Linq to SQL objects must track state and accomplish a whole host of things completely unrelated to the UI.)
While the separation is good practice it can be a "pain in the butt" as you say. To make things easier in transferring information between your class instances look into Automapper which does just that very nicely and allows you to eliminate countless lines of tedious but necessary code.
Happy coding!

Entity Framework - strange issue with multiple foreign keys mapped to the same table

I am using EF (Framework 3.5 SP1) and have a simple two table demo set up:
Applicants
applicant-id int
applicant-pref-lang-coorepondence int (FK to CodeLanguages)
applicant-pref-lang-exam int (FK to CodeLanguages)
applicant-pref-lang-interview int (FK to CodeLanguages)
CodeLanguages
code-lang-id int
code-lang-desc varchar
A CodeLanguage entry can have 0, 1, * Applicants
Each language ref in Applicants must have 1 (and only one) CodeLanguage ref.
Problem:
When I bring back an Applicant entity (via WCF web service to my WPF-based client), if all three language references (coorespondence, exam, and interview) are all the same, lets say 1000 (english), and then I modify one of them, for example to 1001 (french), then all THREE will be changed to 1001 (french).
Here's the weird part: If all three references are different (lets say coorespondence=english, exam=french, and interview=spanish) and I change one of them - then it behaves as expected and only the one I changed is affected - the others are remain in their original state.
I have spent most of today trying various things such as dropping and recreating associations in the EDMX, recreating the EDMX datamodel - even creating a new database. None of this worked - I'm beginning to thing the issue is with EF and not my code.
Any ideas? Thanks.
An update on the final outcome of this issue. After some very quick and helpful advice from the EF team at Microsoft it was determined that this is expected behaviour from EF 3.5 SP1:
"When you query within the service layer for the Applicant where all languages are the same you end up with two objects, one Applicant with all three navigation properties pointing to the same CodeLanguage object.WCF then re-creates this same graph on the client meaning that the three breakpoints you set are indeed looking at the same property on the same object"
Microsoft provided the basis for my ultimate solution which is this:
First: Create a Partial Class for the Applicants data object and create three properties which reference the three language code_ids:
Partial Public Class Applicants
Private _intPrefCoorespLanguage As Integer = 0
Private _intPrefInterviewLanguage As Integer = 0
Private _intPrefExamLanguage As Integer = 0
<System.Runtime.Serialization.DataMemberAttribute()> _
Public Property MyPrefCoorespLanguageCodeId() As Integer
Get
Return (_intPrefCoorespLanguage)
End Get
Set(ByVal value As Integer)
_intPrefCoorespLanguage = value
End Set
End Property
<System.Runtime.Serialization.DataMemberAttribute()> _
Public Property MyPrefInterviewLanguageCodeId() As Integer
Get
Return (_intPrefInterviewLanguage)
End Get
Set(ByVal value As Integer)
_intPrefInterviewLanguage = value
End Set
End Property
<System.Runtime.Serialization.DataMemberAttribute()> _
Public Property MyPrefExamLanguageCodeId() As Integer
Get
Return (_intPrefExamLanguage)
End Get
Set(ByVal value As Integer)
_intPrefExamLanguage = value
End Set
End Property
<OnSerializing()> _
Private Sub PopulateClientProperties(ByVal sc As StreamingContext)
Me.MyPrefCoorespLanguageCodeId = Me.PrefCoorespLanguage.code_lang_id
Me.MyPrefInterviewLanguageCodeId = Me.PrefInterviewLanguage.code_lang_id
Me.MyPrefExamLanguageCodeId = Me.PrefExamLanguage.code_lang_id
End Sub
End Class
Second: Recompile and refresh the client's service reference. Use the three language code_id properties to bind to controls in xaml
Third: In the server-side update run the following to update the applciant and its language foreign keys:
myContext = New HR2009Entities
'Get original Applicant and feed in changes from detatched updated Applicant object
Dim OrigApp = (From a In myContext.Applicants Where a.applicant_id = pobjUpdatedApplicant.applicant_id Select a).First
'Apply preferred language foreign key refs
OrigApp.PrefCoorespLanguageReference.EntityKey = _
New EntityKey("HR2009Entities.CodeLanguages", "code_lang_id",pobjUpdatedApplicant.MyPrefCoorespLanguageCodeId)
OrigApp.PrefInterviewLanguageReference.EntityKey = _
New EntityKey("HR2009Entities.CodeLanguages", "code_lang_id", pobjUpdatedApplicant.MyPrefInterviewLanguageCodeId)
OrigApplicant.PrefExamLanguageReference.EntityKey = _
New EntityKey("HR2009Entities.CodeLanguages", "code_lang_id", pobjUpdatedApplicant.MyPrefExamLanguageCodeId)
'Apply Applicant table native-field changes
myContext.ApplyPropertyChanges(OrigApp.EntityKey.EntitySetName, pobjUpdatedApplicant)
'Save to database
myContext.SaveChanges()
myContext.Dispose()
Well you are right this does sound very wierd.
I tried to repro your problem based on what you've explained, but couldn't.
If you have a small repro though I will look into this.
If you want you can email me (alexj) # microsoft.com.
Alex James
Program Manager Entity Framework Team