PowerDesigner how to get consistent entity colour on different diagrams - data-modeling

I have designed a large conceptual data model using PD 16.5. The model covers different data domains, for example Fund, Asset, Finance, etc. and for each set of entities within a domain I have coloured them differently. So all Fund entities are blue, all Asset entities are green. This works well and is a good visual cue when looking at the model.
One of the benefits of doing a model in PD is that once you create the entities, you can create multiple diagrams and just drag and drop the entities in to show different views, etc. But when I drag a Fund entity onto my new diagram, for example, the formatting I had in the other diagram is not brought across with the entity.
Is there a way of maintaining the formatting of the Entities between diagrams?
Thanks
M

I have a suggestion, which is not satisfying... with a script, to apply a color schema on request. This script can be implemented in a custom method, called from a custom menu attached to the Model, in an Extension.
For example, I just provided a silly FormatEntity sub, to pick a color depending on the Entity name...
But you could have an Extended Association on entities, pointing to the main Folder (Package, or Model) for each entity; extended attributes on the BaseFolder, which provides the graphical elements you want to set on each entity; and a custom handler associated with the Extended Association, which updates the entity symbol depending on the selected folder...
option explicit
sub FormatEntity(s)
dim o
set o = s.Object
' TODO deal with shortcut case
if not o is nothing and o.ClassKind <> cls_Shortcut then
dim name : name = o.Name
output "seeing " & name
if instr(name,"0")+instr(name,"3")+instr(name,"6")+instr(name,"9")<>0 then
output "setting "&name&" to red"
s.FillColor = &hFF ' red
elseif instr(name,"1")+instr(name,"4")+instr(name,"7")<>0 then
output "setting "&name&" to green"
s.FillColor = &hFF00 ' green
else
output "setting "&name&" to blue"
s.FillColor = &hFF0000 ' blue
end if
end if
end sub
sub ExploreOneDiagram(diag)
dim s
for each s in diag.Symbols
if s.ClassKind = cls_EntitySymbol then
FormatEntity s
end if
next
end sub
sub ExploreDiagrams(folder)
dim d
for each d in folder.AllGraphicalDiagrams
'output folder.name&"."&d.name
ExploreOneDiagram d
next
end sub
ExploreDiagrams ActiveModel
dim p
for each p in ActiveModel.Packages
ExploreDiagrams p
next

Related

JOINING TABLES PROPER SYNTAX

I attempting to read in data from a 2nd table to populate a few textboxes on my form.
The two tables involved: LOAD_INFO_TABLE and FUEL_TABLE
I am attempting to join them as such:
Dim taFuel As New IFTAFuelDataSetTableAdapters.Fuel_TableTableAdapter
taFuel.Adapter.SelectCommand = New SqlClient.SqlCommand("SELECT Load_Info_table.PETS_LOAD_NUMBER" & _
"From Load_Info_Table As l" & _
"INNER JOIN Fuel_Table AS f" & _
"ON l.PETS_LOAD_NUMBER = f.PETS_LOAD_NUMB" & _
"Where f.PETS_LOAD_NUMB ='" & tbPETSLoadNumber.ToString & "'", taFuel.Connection)
I'm assuming I also need to create a new dataset to work from with info from both the LOAD_INFO_TABLE and the FUEL_TABLE but unsure how to do that either.
My end goal is I want to be able to load data related to a specific load number which is present and spread out into both tables. Then utilizing the new dataset to populate information on my form for that load number, kind of a "catch-all" page with important data on one form as opposed to needing multiple forms and switching between them.
Specifically I am looking to populate a total fuel cost and gallons purchased onto the load info table. I want to do this to eliminate the need to place the exact same information into both tables. It is redundant.
Further clarification: I want to be able to input or step through the Loads or view a specific Load (2017001 as an example), and the load information screen brings in all data requested from the LOAD_TABLE and FUEL_TABLE. I also want to bring over data from the FUEL_TABLE and populate specific text boxes on the Load screen. JOINING is NOT a strong suit of mine yet I know this can be done but I just do not fully comprehend how to syntax the porting of the data into the form using inner join.
I have spent days watching videos and reading information but nothing assists me in my specific need, or I am not understanding it if it does. This is a last resort request.
The SQL query you are pasting together is invalid (no whitespace between "lines"). And you must use the table aliases in the SELECT clause.
It's much easier if you use VB's new multi-line string literals and parameters. Then your SQL queries can be tested verbatim in SSMS.
EG
Dim sql = "
SELECT l.PETS_LOAD_NUMBER
From Load_Info_Table As l
INNER JOIN Fuel_Table AS f
ON l.PETS_LOAD_NUMBER = f.PETS_LOAD_NUMB
Where f.PETS_LOAD_NUMB = #loadNumber
"
Dim cmd = New SqlClient.SqlCommand(sql, taFuel.Connection)
cmd.Parameters.AddWithValue("#loadNumber", tbPETSLoadNumber.ToString)
taFuel.Adapter.SelectCommand = New SqlClient.SqlCommand(sql, taFuel.Connection)

Import Data from 3 MDBs and 1 CSV, use in DataGrid object

This is eventually to be run on a primitive Windows environment for a PLC, so I'm using Visual Basic .NET 4.0 in Studio 2013.
Anyways here's the challenge I expect to be fairly simple once get going: I've got 3 MDBs with 1 table each and 1 CSV file, which I need to import data from and provide various viewing/editing functionality.
These MDBs are essentially a list of parameters for various "program settings" run by the PLC, called "recipes". There are over 200 parameters, but for some reason they were limited to 100 per file, so they had to create 3 MDBs. But they're essentially representing one long list of parameters. So for instance, the PLC might execute 4 different formats, or recipes, each with a unique "IndexName" key. So in the first MDB, the table has 4 rows and 106 columns, and in the second one 4 rows and 110 columns.
Here's a screenshot of the table in Access
"Export Demo.zip" on this post
http://www.vbforums.com/showthread.php?719287-RESOLVED-mdb-database-to-csv-file
seemed rather promising, but it's in VB6 and it's a bit beyond me to adapt to my environment.
I can create one DataSource/DataSet per source MDB/CSV, but my objective is to populate a single DataGrid object with Data from all those sources. I'm currently looking into how I can consolidate the data into one DataTable.
Pulling a few bits and pieces from various sources together, I managed to get this working. Particularly useful was the DataTable.Merge function, took me a while to find that.
Not sure how useful this code might be for others, or if it goes against stackoverflow etiquette. I'm happy to fix accordingly, but I did want to share that I managed.
Obviously this isn't the complete code, but hopefully the most relevant bits. I'm happy to provide some more pieces if required. Code comments/improvements welcome, I'm just a beginner!
Sub LoadDataIntoDataGrid()
'Loads CSV Table, Transpose & Load 3 MDB tables, Capture each Table Params, & Merge all 3 into 1
'Adjusts the merged table according to the csv file
Dim CSVTable As DataTable, MDB0Table As DataTable, MDB1Table As DataTable, MDB2Table As DataTable, _
CRLTable As DataTable
Dim CSVRow As DataRow
Dim removeRows As New List(Of String)
'Populate CSV Table
CSVTable = ReadFromCSV("CSVFile.csv")
'Populate 3 MDB tables with transposed Data: Rows are parameters
MDB0Table = ReadFromMDB("First.mdb", "SELECT * from Table1")
MDB1Table = ReadFromMDB("Second.mdb", "SELECT * from Table2")
MDB2Table = ReadFromMDB("Third.mdb", "SELECT * from Table3")
'Put each Parameter name into MDBTableParams
For Each ParamName In MDB0Table.Rows
CRLForm.MDBTableParams(0).Add(ParamName(0))
Next
For Each ParamName In MDB1Table.Rows
CRLForm.MDBTableParams(1).Add(ParamName(0))
Next
For Each ParamName In MDB2Table.Rows
CRLForm.MDBTableParams(2).Add(ParamName(0))
Next
'Set Primary Keys
MDB0Table.PrimaryKey = New DataColumn() {MDB0Table.Columns("NamePar")}
MDB1Table.PrimaryKey = New DataColumn() {MDB1Table.Columns("NamePar")}
MDB2Table.PrimaryKey = New DataColumn() {MDB2Table.Columns("NamePar")}
CSVTable.PrimaryKey = New DataColumn() {CSVTable.Columns("NamePar")}
'Merge all 3 MDB Tables into 1
CRLTable = MDB0Table
CRLTable.Merge(MDB1Table)
CRLTable.Merge(MDB2Table)
'For each row in the merged table, either mark for removal or add the description from the CSV file
CRLTable.Columns.Add("Desc", GetType(String))
CRLTable.Columns("Desc").SetOrdinal(1) 'Make Description second column (after "NamePar")
For Each CRLRow In CRLTable.Rows
CSVRow = CSVTable.Rows.Find(CRLRow("NamePar"))
If CSVRow Is Nothing Then
removeRows.Add(CRLRow("NamePar")) 'tag rows to be removed due to missing in CSV
Else
CRLRow("Desc") = CSVRow("DESC_" & CRLForm.ComboBoxLanguage.Text)
End If
Next
'Remove Rows tagged for removal due to absence from CSV file
For Each row In removeRows
CRLTable.Rows.Remove(CRLTable.Rows.Find(row))
Next
'Set DataGridView data source as the cleaned merged table
CRLForm.DataGridView1.SelectionMode = DataGridViewSelectionMode.RowHeaderSelect ' Otherwise DataSource = CRLTable refuses to populate
CRLForm.DataGridView1.DataSource = CRLTable
CRLForm.DataGridView1.SelectionMode = DataGridViewSelectionMode.ColumnHeaderSelect
End Sub

Using Array of Defaults in VBA to Populate Excel WBS with Outline

I've got a WBS (Work Break Down Structure), with multiple rows (top-level of a group outline), and each top-level row is an activity. Directly under the activity are the roles involved.
Based on the value of the activity in the top level ("plan", for example), the cells in the level below are populated, according to their values in a related table on another sheet ("defaults" tab).
Currently, the rows under the activity (that correspond to roles) are doing an ugly index/match lookup, which multiplied by 25 roles, can grind the spreadsheet to a halt.
What I think will solve this issue is taking the Role Defaults table, putting it in a persistent array, and using the values in the array over and over, as the user puts in the top-level activities. I just can't figure out how to make the array persistent (so the VBA doesn't repopulate it ever time a user changes a cell). If the values in the Role Defaults table changes, I can handle that with a worksheet OnChange, so that's not an issue.
Row 3 "Activity 1" is what the Activity Rows look like with the group outline collapsed.
Rows 4-9 are what the Activity Rows look like with the group outline expanded, showing the underlying roles.
For each of the roles, this is the table on another tab that's used to look up the value that should be in the corresponding Activity/Role cell on the WBS tab.
I'm a proponent of using Dictionary objects whenever the need for lookups arise. In my solution below, I use nested dictionaries to return a combination of Top-Level and Activity. (Note: I tried to understand your business need as best as I could, but I'm sure I didn't nail it. I also assumed some knowledge of VBA above a beginner's level. If you have follow up questions, please ask and we'll try and help).
First, create a new module to hold the globally available Dictionary. This cannot be a Worksheet module. (In the VBE, go to Insert --> Module). At the very top of the module, before creating a subroutine, declare a publicly available Dictionary
Public oDictWbs As Object
We only want one instance of this dictionary, so I like to use a Singleton like pattern which returns a Dictionary if already created, and if not, create and return a new one. (Note: I factored out the routine that returns a new dictionary into RefreshWBS so that it can be used to create a new dictionary based on your business rules. So, for example, in the Default worksheet OnChange event, you can call RefreshWBS [code reuse is always fun]).
Private Function GetWBS() As Object
If Not oDictWbs Is Nothing Then
Set GetWBS = oDictWbs
Exit Function
End If
Set GetWBS = RefreshWBS()
End Function
Private Function RefreshWBS()
Dim sDefault As Worksheet
Dim rTopLevels As Range
Dim rActivities As Range
Dim rIterator As Range
Dim rInnerIter As Range
Set oDictWbs = Nothing
'Both variables below establish the range that stores the fixed info (the default worksheet)
'Instead of hard coding in the range, create your own logic based on your needs and rules
Set sDefault = Sheets("Default")
Set rTopLevels = sDefault.Range("B1:C1")
Set rActivities = sDefault.Range("A3:A4")
Set oDictWbs = CreateObject("Scripting.Dictionary")
For Each rIterator In rTopLevels
If Not oDictWbs.exists(rIterator.Value) Then
Set oDictWbs(rIterator.Value) = CreateObject("Scripting.Dictionary")
End If
For Each rInnerIter In rActivities
If Not oDictWbs(rIterator.Value).exists(rInnerIter.Value) Then
oDictWbs(rIterator.Value)(rInnerIter.Value) = sDefault.Cells(rInnerIter.Row, rIterator.Column)
End If
Next rInnerIter
Next rIterator
Set RefreshWBS = oDictWbs
End Function
Finally, we create a function that can be accessed from within the Worksheet itself, allowing the user to access information in the WBS Dictionary. You can enter into an Excel cell a function like =GetWbsActivityTime(B1, A4) presuming that cell B1 contains the top-level descriptor and A4 describes the activity. So long as that value is in the dictionary, it will return the value associated with it.
Function GetWbsActivityTime(sTopLevel As String, sActivity As String) As Variant
Dim oDict As Object
Set oDict = GetWBS()
If Not oDict.exists(sTopLevel) Then
GetWbsActivityTime = CVErr(xlErrRef)
Exit Function
End If
If Not oDict(sTopLevel).exists(sActivity) Then
GetWbsActivityTime = CVErr(xlErrRef)
Exit Function
End If
GetWbsActivityTime = oDict(sTopLevel)(sActivity)
End Function
I know it's a lot to absorb, so review it and let me know of any questions or quirks with which I can help. Also, if I totally missed the point of the exercise, let me know and I'll see if we can salvage parts of the solution.

Search database with textbox

First of all I am working at VB 2012.
I have problem with searching my database. It goes so slow, actually filling the ListView is what bothers me.
I have a text box with TextChange event. Its instant search. So when I'm starting to write in that text box it's starts to filter the database and filling the data in the ListView.
This is the code in text box and the Load procedure
Private Sub txtID_TextChanged(sender As Object, e As EventArgs) Handles txtID.TextChanged
Load("SELECT * FROM table WHERE id LIKE '" & txtID.Text & "%'")
End Sub
Private Sub Load(ByVal strQ As String)
List.Items.Clear()
cmd = New SqlClient.SqlCommand(strQ, con)
dr = cmd.ExecuteReader()
If dr.HasRows = True Then
While dr.Read
Dim X As ListViewItem
X = List.Items.Add(dr(0))
X.SubItems.Add(dr(2))
X.SubItems.Add(dr(3))
X.SubItems.Add(dr(4))
X.SubItems.Add(dr(1))
X.SubItems.Add(dr(5))
End While
End If
End Sub
So, every time I hit a letter it calls the load procedure.
And I have so much data and it goes so slow. Can you help me somehow ? Is there any solution ?
I don't know how you're ever going to possibly speed that up. Connecting and querying the database is a lot of overhead, especially compared to the speed of pressing a key or typing a word. There's just not a way to do this without severely affecting the user who's typing.
What I suggest instead is that you wait for the user to tell you they're done typing before you bother making a search. If you're trying to do fancy auto-completion stuff you're going to need to cache the data a lot closer to the app than the database.
You need to create a class to hold the search results, like this:
Public Class SearchResult
Private _propID As String
Public Property ID() As String
Get
Return _propID
End Get
Set
_propID = Value
End Set
End Property
Private _propName As String
Public Property PropName() As String
Get
Return _propName
End Get
Set
_propName = Value
End Set
End Property
...
End Class
Now you query the database to get all the results to display in the list view, storing it in a List(Of SearchResult), like this:
Private Function Load(ByVal strQ As String) As List(Of SearchResult)
Dim ListOfResults = New List(Of SearchResult)
cmd = New SqlClient.SqlCommand(strQ, con)
dr = cmd.ExecuteReader()
If dr.HasRows = True Then
While dr.Read
Dim X As New SearchResult()
X.PropID = dr(0)
X.PropName = dr(1)
...
End While
End If
End Sub
You can call this code like this:
Dim AllSearchResults = Load("SELECT * FROM table WHERE id LIKE '" & txtID.Text & "%'")
Now when you want to do a search you can apply the following LINQ against your cached list of everything (AllSearchResults), like this:
Public Function DoSearch(searchText As String) As List(Of SearchResult)
Return From s In AllSearchResults Where s.PropID.Contains(searchText) Select c
End Function
Finally, you can call this LINQ filtering on each key press by the user, like this:
Private Sub txtID_TextChanged(sender As Object, e As EventArgs) Handles txtID.TextChanged
DoSearch(txtID.Text)
End Sub
One way to cut the overhead down by a fair degree to to wait to issue the query until the length of the text entered is 3 (or better yet 5) characters. It is very, very unlikely that all customers (or whatever these are) starting with 'S' is going to be meaningful or helpful to anyone except the one person on rare occasions looking for "Sab....". People HAVE to be typing in the 2nd and 3rd char before the first query is complete and displayed!
To try to make a really bad idea less bad, I'd look into a way to issue the query ONCE (on the first character if I really, really has to), then filter those results down on subsequent keystrokes. (ala Aaron's "caching the data a lot closer to the app").
The next thing is Select *. I dunno whats in the table, but do you REALLY need every column? This appears to be some sort of pick list, do you really need 6 fields to provide the user the information needed to make a selection? If there are more than 6 columns in the table, immediately narrow the query to the 6 used in the listview. Once they make their choice you can go back and get exactly what you need by ID or whatever.
I'd personally use a faster control but thats subjective.
ALL databases eventually acquire dormant data. Customers (or whatever) that were one time shoppers and never return - do they need to be in the list? If there is a column somewhere for lastorderdate or lastupdateddate construct your query to pick those active in the last XX months (and if not, see if you can add one because the issue is not going to get better as the database gets larger!). Then a checkbox for the user to widen the range as needed, like "See All" or something. Users are not likely to balk at the idea if it speeds things up the other 80% of the time.
...those are just off the top of my head.

Refactor to n-tier

I am a self taught vb6 programmer who uses DAO. Below is an example of a typical piece of code that I could churn out:
Sub cmdMultiplier_Click() 'Button on form, user interface '
dim Rec1 as recordset
dim strSQL as string
strSQL = "select * from tblCustomers where ID = " & CurrentCustomerID 'inline SQL '
set rec1 = GlobalDataBase.openrecordset(strSQL) ' Data access '
if rec1.bof <> true or rec1.eof <> true then
if rec1.fields("Category").value = 1 then
PriceMultiplier = 0.9 ' Business Logic '
else
priceMultiplier = 1
end if
end if
End Sub
Please pretend that the above is the entire source code of a CRUD application.
I know this design is bad, everything is mixed up together. Ideally it should have three distinct layers, user interface, business logic
and data access. I sort-of get why this is desirable but I don't know how it's done and I suspect
that's why I don't fully get why such a separation is good.
I think I'd be a lot further down the road if someone could refactor the above ridiculously
trivial example into 3 tiers.
a trivial example, yes, but with all the basic elements - they just belong in 3 different classes (see below). The main reason for this is the "separation of concerns" principle, i.e. the GUI is only concerned with GUI things, the Biz Logic layer is only concerned with the business rules, and the data-access layer is only concerned with data representations. This allows each layer to be maintained independently and reused across applications:
'in Form class - button handler
Sub cmdMultiplier_Click()
PriceMultiplier = ComputePriceMultiplier(CurrentCustomerId)
End Sub
'in Biz Logic class
Function ComputePriceMultiplier(custId as Integer) as Double
Dim cust as Customer = GetCustomer(custId)
if cust.Category = 1 then 'please ignore magic number, real code uses enums
return 0.9
end if
return 1
End Function
'in Data Access Layer class
Function GetCustomer(custId as Integer) as Customer
Dim cust as Customer = New Customer 'all fields/properties to default values
Dim strSQL as String = "select * from tblCustomers where ID = " & custId
set rec1 = GlobalDataBase.openrecordset(strSQL) ' Data access '
if rec1.bof <> true or rec1.eof <> true then
cust.SetPropertiesFromRecord(rec1)
end if
return cust
End Function
[a 'real' application would cache the current customer, have constants or stored procedures for the customer query, etc.; ignored for brevity]
Contrast this with your original everything-in-the-button-handler example (which is appallingly common in VB code because it is so easy to do it that way) - if you needed the price-multiplier rule in another application, you'd have to copy, paste, and edit the code into that application's button-handler. Now there would be two places to maintain the same business rule, and two places where the same customer query was executed.
Typically you will have your UI code responding to the events raised by the user, in this case the Button Click.
After that it really depends on how your program is designed, the most basic design would be to reference a Customer instance and it would contain a multiplier property.
Your customer object is populated from data in your DAL.
Validation for UI would go in UI layer, business validation rules could go into your business object, and then your DAL is your persistence layer.
Here is a very basic pseudo-code example:
btnClick
Dim Cust as New Customer(ID)
multplr = Cust.DiscountMultiplier
End Click
Class Customer
Sub New(ID)
Data = DAL.GetCustomerData(ID)
Me.Name = Data("Name")
Me.Address = Data("Address")
Me.DiscountMultiplier = Data("DiscountMultiplier")
End Sub
Property ID
Property Name
Property Address
Property DiscountMultiplier
Return _discountMultiplier
End
End Class
Class DAL
Function GetCustomerData(ID)
SQL = "Paramaterized SQL"
Return Data
End Function
End Class
Knowing how to refactor is a good thing. From now you will know how to separate layers.
However, I think your time will be better spend to upgrade the tools you are using at the same time. Do you have consider to do it with VB.Net ?
A way to do it will preserving your existing code base is to code the Data layer and BR in VB.Net. Then to expose the BR through COM Interface (this is a check box option in the project). You can then use the new BR from your current interface.
Once all BR and DAL done, you will be a step away to a complete new platform.
What is the purpose of the button?
My first steps would be:
extract the part accessing the database. (warning: air code ahead)
function getCustomer(CurrentCustomerID as Long)
strSQL = "select * from tblCustomers where ID = " & CurrentCustomerID
set rec1 = GlobalDataBase.openrecordset(strSQL)
result = 1
if rec1.recordcount >0 then
getCustomer = rec1
else
getCustomer = false
endif
end function
compose the business logic function:
function getCustomerDiscount(customerID as Long)
customer = getCustomer(customerID)
res = 1
if customer then
if customer("category")=1) then
res = .9
endif
endif
getcustomerdiscount = res
end function
then, change the button:
Sub cmdMultiplier_Click()
pricemultiplier = getcustomerdiscount(currentcustomerid)
end sub

Resources