I have an ObservableCollection of class Customers that I fill from a database query. Because of how the tables are joined, I sometimes get one off entries (Bill has a shipping address that is different than his mailing address) that show up in the data twice.
Each customer row has an ID as unique primary key, and this is what I use to pull up more customer information when someone selects a row from the bound ListView.
In the WinForms version of this program, I would search the ListView for the CustomerID, and if it was found I would avoid inserting it a second time.
ObservableCollection doesn't seem to have the ability to easily tell me if a CustomerID already exists in one of the class instances of the collection, so I'm wondering what the best way to handle this is.
The ideas I've had so far:
' Not sure how to make this work, since the CustomerID and Name would be the same, but the city, state, zip might not be.'
t = new classCustomer(CustomerID, CustomerName, City, State, Zip)
if not sr.contains(t) then
sr.Add(t)
end if
Possibly figure out how to create an ObservableDictionary, but so far all the examples are in C#, and it may take me a while to port it over to VB.net
Anyone know of a better implementation?
You simply need to tell .NET what defines a person, in this case the ID.
Simply override Equals on your customer object, then your collection will now be able to know if 2 customers are equivalent:
Public Class Person
Private id As Integer
Public Sub New(ByVal id As Integer)
Me.id = id
End Sub
Public Overrides Function Equals(ByVal obj As Object) As Boolean
Return (TypeOf (obj) Is Person) And (DirectCast(obj, Person)).id = Me.id
End Function
Public Overrides Function GetHashCode() As Integer
Return Me.id.GetHashCode()
End Function
End Class
Sub Main()
Dim observable As New ObservableCollection(Of Person)()
observable.Add(New Person(1))
Dim duplicate As New Person(1)
If Not observable.Contains(duplicate) Then
observable.Add(duplicate) ' never gets hit because of .Equals override
End If
End Sub
without the override it does not know how to tell if they are equivalent or not.
Related
I have a winforms applications that has an ms sql server backend. In my database i have lookup tables for like status, and other tables where the data rarely changes. In my application, several forms might use the same lookup tables (Some have a lot of data in them). Instead of loading/filling the data each time the form is open, is there a way to cache the data from the database that can be accessed from multiple forms. I did some searching, but couldnt find the best solution. There is caching, dictionaries, etc. What is the best solution and can you point me to the documentation that discusses it and may even have an example.
Edit:
In my original post I failed to mention that I have a strongly typed dataset and use tableadapters. I want to preload my lookup tables when my application starts, and then have these dataset tables be used throughout the application, on multiple forms without having to fill them on every form.
I have tried creating a class:
Public Class dsglobal
Public Shared EML_StaffingDataSet As EML_StaffingDataSet
Public Shared Sub populateDS()
EML_StaffingDataSet = New EML_StaffingDataSet
End Sub
Public Shared Sub loadskills()
Dim ta As New EML_StaffingDataSetTableAdapters.TSTAFFSKILLTableAdapter
ta.Fill(EML_StaffingDataSet.TSTAFFSKILL)
End Sub
End Class
I run this on a background worker when my application is starting up. So it loads the dataset table. On fill, I can see the datatable has data in it. When I open a form, i want to use the dataset table, but it seems to clear the data out. Not sure if my approach is correct or where my error is.
Edit2:
I have also tried this per comments, but not sure I am doing it correctly. If I am doing it correctly, then how do I use that as a datasource at design time, can i only do that programmatically?
Public Module lookupdata
Private EML_StaffingDataSet As EML_StaffingDataSet
Private skillvalues As List(Of skill)
Public ReadOnly Property skill As List(Of skill)
Get
If skillvalues Is Nothing Then
getskillvalues()
End If
Return skillvalues
End Get
End Property
Private Sub getskillvalues()
skillvalues = New List(Of skill)
EML_StaffingDataSet = New EML_StaffingDataSet
Dim ta As New EML_StaffingDataSetTableAdapters.TSTAFFSKILLTableAdapter
ta.Fill(EML_StaffingDataSet.TSTAFFSKILL)
For Each row As DataRow In EML_StaffingDataSet.TSTAFFSKILL
Dim skill As New skill
skill.skill_id = row("skill_id")
skill.skill_desc = row("skill_desc")
skill.skill_open_ind = row("skill_open_ind")
skillvalues.Add(skill)
Next
End Sub
End Module
Public Class skill
Public Property skill_id As Integer
Public Property skill_desc As String
Public Property skill_open_ind As Boolean
End Class
You might want to consider a lazy loading pattern, like this:
Public Module LookupData
Private statusValues As List(Of LookupValue)
Public Readonly Property Statuses As List(Of LookupValue)
Get
If statusValues Is Nothing Then
GetStatusValues()
End If
Return statusValues
End Get
End Property
Private Sub GetStatusValues()
statusValues = New List(Of LookupValue)
Dim sql = "select key, value from StatusTable"
'TODO: Read the items from the database here, adding them to the list.
End Sub
End Module
Public Class LookupValue
Public Property Key As String
Public Property Value As String
End Class
The idea is that you've got a single instance of LookupData (a Module in VB, there can be only one). Lookup data has a series of Properties, each of which returns a list of values from the database. If the data has already been loaded, it just returns what it has cached. If the data has not been loaded, then the first time it is referenced it retrieves it from the database.
You can consume it elsewhere in your code as follows:
Dim myStatuses = LookupData.Statuses
I have built a workflow designer that allows to enter a list of email addresses.Each email in the list needs to be an InArgument(Of String) so they can be individually edited/added using variables.
On my Activity, I have a property that is declared like so:
Public Property [To] As ObservableCollection(Of InArgument(Of String))
My designer is wired up and populating this collection properly.
However during the execution, I do not know how to get the run-time value for each InArgument that was added.
When we are executing the workflow and iterating for each InArgument added to the list, I attempted to get the value as shown below but that fails:
For Each toAddress As InArgument(Of String) In Me.To.ToList()
Dim emailToAddress As String = toAddress.Get(_Context)
Next
The error we get is “The argument of type '<type>' cannot be used. Make sure that it is declared on an activity” and type is a string in my case...
The error we get sort of make sense because we haven’t declared a property on the activity since it was added dynamically to the list and therefore cannot get the value using the syntax shown below:
The_Property_Name.Get(_Context)
Can someone help? I can't seem to find anything. Should I be doing a different approach?
I figured it out so I will answer my own question! All we need to do is explicitly add the collection items to the metadata by overriding CacheMetadata() method on the activity. This then makes it available to the workflow context.
First we add them to the context:
Protected Overrides Sub CacheMetadata(ByVal metadata As CodeActivityMetadata)
MyBase.CacheMetadata(metadata)
Dim i As Integer = 0
For Each item As InArgument(Of String) In Me.To
Dim runTimeArg As RuntimeArgument = New RuntimeArgument("TO" & i.ToString(), item.ArgumentType, item.Direction, False)
metadata.Bind(item, runTimeArg)
metadata.AddArgument(runTimeArg)
i = i + 1
Next
End Sub
Then when executing, we get the values like this
For Each item As InArgument(Of String) In Me.To
Dim email As String = _Context.GetValue(item)
Next
I'm working on a project with EF5, Vb.net, AJAX (Javascript), ASMX-Webservices and HTML.
My question is if i have (example classes):
Public Class Company
Public Property CompanyId As Integer
Public Property Name As String
Public Overridable Property Companytype As Companytype
End Class
and the Class:
Public Class Companytype
Public Property CompanytypeId As Integer
Public Property Name As String
-> Public Overridable Property Companies As List(Of Company)
End Class
do I need the -> marked line?
I'm afraid but I really don't know which advance this line brings to me.
Actually I can read all the Companies of a Companytype like this:
Public Shared Function PostCompanyTypeCompanies() As List(Of Company)
Dim db As New Context
Dim x As Integer = 1
Dim y = (From c In db.Companies Where c.CompanyType.CompanyTypeId = x Select New With _
{c.Name, _
c.CompanyType})
Dim z As List(Of Company) = y.AsEnumerable() _
.Select(Function(t) New Company With _
{.Name = t.Name, _
.CompanyType = t.CompanyType}).ToList()
Return z
End Function
This with 'x' is just an example, I can just pass the CompanytypeId to the function.
The problem with the lists is, I always get a circular reference when I want to get the Companytypes for a new Company and I can't access the companytype of a Company like:
Company.Companytype.Name
When I do it without the list everything works fine, because i can store the whole Companytpe to the Company.
I tried the other possibility with setting the Getter of the Child & Parent Properties to Protected then the problem was logically also that I couldn't access the variable as I described a 3 lines above.
So the important question is: Is this -> List Property mandatory?
Thanks for you help.
do I need the -> marked line? NO. It is redundant information (with no real use), which potentially, might provoke consistency errors. Example:
Dim company1 As New Company()
Dim listCompanies As New List(Of Company)()
Dim companiesType1 As New Companytype()
listCompanies.Add(company1)
With companiesType1
.CompanytypeId = 1
.Name = "Type 1"
.Companies = listCompanies
End With
With company1
.CompanyId = 1
.Name = "1"
.Companytype = companiesType1
End With
The code above defines Company1 and the associated type (companiesType1... although we have the problem of "first the egg or the chicken", what gives a first idea of why the chosen approach is erroneous). If you create a new company of the same type:
Dim company2 As New Company()
With company2
.CompanyId = 2
.Name = "2"
.Companytype = companiesType1
End With
You would have to redefine companiesType1 (update its Companies property), to keep the consistency. But, as far as this is redundant information, Companytype would "do the job" independently upon this fact:
If (company2.Companytype Is company1.Companytype) Then
'Both companies have the same type
End If
The aforementioned condition will always be true: either if companiesType1 contains the right information (was updated with company2) or not.
If you need to have a list of all the companies belonging to certain type, you should better rely on a different class storing all the values (e.g., allTheCompanies).
I originally posted this as a LINQ query - got it working and then realised I had a problem. You can't use LINQ queries to select/filter and order the items in a CollectionViewSource (why oh why didn't I check this first, why oh why isn't this possible?).
So, I am now trying to work out how to sort a filtered CollectionViewSource.
My CollectionViewSource is bound to an ObservableCollection(Of MediaItems). MediaItems contains a child/nested List(Of AdvertOptions).
The parent ObservableCollection(Of MediaItems) - class is structured as follows:
MediaItems
.ID (int)
.Src (string)
.Advert (bool)
.AdOptions As List(Of AdvertOptions)
.Counter (int)
AdvertOptions class consists of:
.Age (int)
.Gender (int)
.Priority (int)
I am filtering out any MediaItems that don't meet the following criteria:
MediaItems.Advert = true
AdOptions.Age = x (parameter within triggered function called to perform the filter/sort)
AdOptions.Gender = y (parameter within triggered function called to perform the filter/sort)
After the CollectionViewSource has been filtered, I need to sort the items based on two sort orders so the resulting CollectionViewSource items can be navigated through in my applicaiton using the CollectionViewSource navigation methods (MoveCurrentToX etc).
The sort order I need to apply is:
AdOptions.Priority (in descending order)
By Counter (in ascending order)
The way I am filtering is by using these functions:
Public Shared Sub FilterByAdvertisement(ByVal Item As Object, ByVal e As FilterEventArgs)
Dim MediaObjectItem As MediaObject = TryCast(e.Item, MediaObject)
If Not MediaObjectItem.IsAdvertisingMedia = True Then
e.Accepted = False
End If
End Sub
Public Shared Sub FilterByAvertisementOption(ByVal Item As Object, ByVal e As FilterEventArgs)
Dim MediaObjectItem As MediaObject = TryCast(e.Item, MediaObject)
Dim Items = From m In MediaObjectItem.AdOptions Select m Where m.Age = Current.Age And m.Gender = Current.Gender
If Items.Count = 0 Then
e.Accepted = False
End If
End Sub
Just for reference, I am adding the filter as follows:
Public AdvertisingDataView As CollectionViewSource
AddHandler AppLocal.AdvertisingDataView.Filter, AddressOf FilterByAdvertisement
AddHandler AppLocal.AdvertisingDataView.Filter, AddressOf FilterByAdvertisementOption
I now need to work out how to sort the filtered items. The problem is, CollectionViewSource seems to have limited support for sorting. I can easily sort the Counter using:
AdvertisingDataView.SortDescriptions.Add(New SortDescription("Counter", ListSortDirection.Ascending))
But that's my secondary sort - I want to Sort first by AdOptions.Priority (requires sub-selecting the right item), then by Counter.
I was wondering if creating Groups would help but can't work out if this will provide the sorting capability I'm looking for.
I have looked at the possibility of converting to a ListCollectionView instead of CollectionViewSource, then using a CustomSort but I can't work out how I could implement this and if it would also offer the capability I'm looking for given my primary sort is a value within a nested list.
Can anyone contribute to help me achieve my outcome?
Ben
You can achieve multiple levels of sorting on the default view of your CollectionViewSource.
There are essentially 3 types of views automatically generated by WPF, all deriving from the CollectionView base class:
ListCollectionView -> Created when the collection implements IList.
BindingListCollectionView -> Created when the collection implements IBindingList.
EnumerableCollectionView -> Created when the collection implements nothing but IEnumerable.
You can always add to multiple SortDescriptor to the SortCollection of your default view like this -
ListCollectionView lcv =
(ListCollectionView)CollectionViewSource.GetDefaultView(myCollection);
lcv.SortDescriptions.Add(new SortDescription(…));
Refer to these links for further refernce -
http://bea.stollnitz.com/blog/?p=387
http://bea.stollnitz.com/blog/index.php?s=collectionviewsource
I don't know how to write it in VB, but i can show you in c# how it's made:
YourListView = CollectionViewSource.GetDefaultView(tempListView
.OrderBy(x => x.FirstSorting)
.ThenBy(y => y.SecondSorting));
Look how to do the same in VB, and you will fix this.
You have a link right here - http://linqsamples.com/linq-to-objects/ordering/ThenBy-lambda-csharp
Good luck!
I have been working thru this WPF example and am trying to hook it up to my database using Entity Framework but am confused on how to do this. Can someone please offer some guidance on how this would be done?
The code has the following in CustomerRepository.cs
static List<Customer> LoadCustomers(string customerDataFile)
{
// In a real application, the data would come from an external source,
// but for this demo let's keep things simple and use a resource file.
using (Stream stream = GetResourceStream(customerDataFile))
using (XmlReader xmlRdr = new XmlTextReader(stream))
return
(from customerElem in XDocument.Load(xmlRdr).Element("customers").Elements("customer")
select Customer.CreateCustomer(
(double)customerElem.Attribute("totalSales"),
(string)customerElem.Attribute("firstName"),
(string)customerElem.Attribute("lastName"),
(bool)customerElem.Attribute("isCompany"),
(string)customerElem.Attribute("email")
)).ToList();
}
which is where I assume the hook to the database would happen but not sure how. I can create the Model.edmx file to connect to the database but not sure how to physically get the list of customers from the database.
Also, this example uses a List of Customers but most examples I have gone through use ObservableCollection for this type of data. Is one preferable over the other and why?
TIA,
Brian Enderle
My MVVM/EF projects typically load entities directly into the ViewModels or Into light collections in the view models. I don't create any kind of custom repository to sit between them.
Generally my ViewModels do one of two things,
Retrieves data on instancing
Takes an entity as a constructor argument.
When I retrieve data on instance, I generally use a background worker class, which queries the context, puts the results in a list, and passes the list out. The Work Completed method then puts the entities into viewmodels and puts the ViewModels in a ObservableCollection.
Similar to this:
Private WithEvents GetProjectsBackgroundWorker As BackgroundWorker
Private _Projects As New ObservableCollection(Of ProjectViewModel)
Public Sub New()
GetProjectsBackgroundWorker = New BackgroundWorker
GetProjectsBackgroundWorker.RunWorkerAsync()
End Sub
Public Property Projects() As ObservableCollection(Of ProjectViewModel)
Get
Return _Projects
End Get
Set(ByVal value As ObservableCollection(Of ProjectViewModel))
_Projects = value
End Set
End Property
#Region " GetProjects"
Private Sub GetProjectsBackgroundWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles GetProjectsBackgroundWorker.DoWork
Dim results = From proj As Project In Context.Projects Where proj.flgActive = True And proj.flgReview = True Select proj
Dim ProjList As New List(Of Project)(results)
e.Result = ProjList
End Sub
Private Sub GetProjectsBackgroundWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles GetProjectsBackgroundWorker.RunWorkerCompleted
If e.Error Is Nothing Then
For Each p As Project In e.Result
Projects.Add(New ProjectViewModel(p, Context))
Next
Else
End If
End Sub
#End Region
In ViewModels that take an entity as an argument, they often take a context as argument, especially if something is a short time-span operation. Otherwise I detach entities from the context in the even something goes hay-wire with the database or the connection is lost or something.
To answer your second question, ObservableCollections are Enumerable collections that have collection change notification implemented. The collection will notify the UI when a new item is added/removed/moved. Typically any time I have entities that are going to be viewed or displayed in the UI, I host them in an Observable Collection. Otherwise I use something simpler, a List normally.