How to determine if an Entity with relationship properties has changes - entity-framework

Dim myEmployee as Employee = myObjectContext.Employee.Where("it.EmployeeID = 1").First()
The following line will cause e.EntityState to equal EntityState.Modified :
myEmployee.Name = "John"
However, changing a property that is a relationship will leave e.EntityState = EntityState.Unchanged. For example:
myEmployee.Department = myObjectContext.Department.Where("it.DepartmentName = 'Accounting'").First()
How can I tell if myEmployee has changes? I need to know so I can log changes made to the Employee record for auditing purposes.

There is a way to get the state of a relationship, but it is not as easy to obtain as the state of an entity.
ObjectContext.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState state)
returns IEnumerable<ObjectStateEntry> with entries for both, entities and relationships (there is IsRelationship property on ObjectStateEntry so you can determinate if it's relationship or entity).
I tested out with with your example when relationship is changed the way you do
myEmployee.Department = myObjectContext.Department.Where("it.DepartmentName = 'Accounting'").First()
and I find out by calling GetObjectStateEntries for each possible EntityState that one ObjectStateEntry is added with state Added:
myObjectContext.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added)
Now, you can peek at the current values of the state entry to see if they match the ends of the relationship (not nice). However, it's a bit complicated and I'm not sure if it's going to meet your needs in every case.

i was having a similar issue when i was trying to validate in Entity framework:
After researching a little bit i found a solution:
(see im posting the whole validation solution)
Interface for validation:
Interface IValidatable
Function Validate(Optional ByVal guardando As Boolean = False) As List(Of ApplicationException)
End Interface
Handling the SavingChanges event in a partial class:
Partial Class FacturacionEntities
Private Sub FacturacionEntities_SavingChanges(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.SavingChanges
Dim objects As New List(Of System.Data.Objects.ObjectStateEntry)
objects.AddRange(Me.ObjectStateManager.GetObjectStateEntries(EntityState.Added))
objects.AddRange(Me.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
Dim errors As New List(Of ApplicationException)
For Each obj In objects
If obj.IsRelationship Then
Dim fro = DirectCast(obj.CurrentValues(1), EntityKey)
Dim k As New EntityKey("FacturacionEntities." & fro.EntitySetName, fro.EntityKeyValues(0).Key, fro.EntityKeyValues(0).Value)
errors.AddRange(DirectCast(Contexto.Facturacion.GetObjectByKey(k), IValidatable).Validate())
Else
errors.AddRange(DirectCast(obj.Entity, IValidatable).Validate)
End If
Next
If errors.Count > 0 Then
Dim err_list As String = ""
For Each s In errors
err_list = err_list & s.Message & vbCrLf
Next
Throw New ApplicationException(err_list)
End If
End Sub
End Class
Please note than "Contexto.Facturacion" is an instance of the Entities class generated by Entity framework engine.

Related

Class of a Class

I'm getting just killed trying to make a class of a class. I have shopped around the site and seen several examples but maybe because its 1:43 I am having a hard time understanding them.
I was successfully able to use a class to automate a huge data entry project at work. I created a class called catDist which is the category distribution of types of agricultural products a company could manufacture or sell.
catDist contains six properties:
Private selfWorth As String
Private Q1 As Double
Private Q2 as Double
Private Q3 as Double
Private Q4 As Double
Private activated As Boolean
They all have the standard get and let codes.
There are 48 possible categories. I have a module that creates 48 instances of them with 48 different values for selfWorth (e.g "Cottonseed", or "maize" etc), and sets Q1 through Q4 as 0 . The module originally worked with a Userform that I could type in the values and hit enter. If it saw that I had entered a value inside a particular textbox (yes there were 48X4 textboxes) it would set activated to true and changes the relevant Q's to the values I entered.
WHAT I WANT TO DO NOW.
It was a great success. Now what I want to do is create a class called "Distributor". Each distributor class would have 4 collections have catDist objects. I can create the distributor class. I can create the catDist class. But for the love of God I can not figure out a way to set the corresponding distributor catDist property to the catDist value I used in the Set method.
Sub testRegist()
Dim registrant As testRegistrant
Set registrant = New testRegistrant
registrant.registNum = "Z000123"
'MsgBox (registrant.registNum)
Dim cowMilk As testcatDist
Set cowMilk = New testcatDist
cowMilk.selfWorth = "Cow Milk"
cowMilk.distribution = 4.6
registrant.testCat = cowMilk
Debug.Print registrant.testCat.selfWorth
End Sub
catDist Class
Private pselfWorth As String
Private pdistribution As Double
Public Property Get selfWorth() As String
selfWorth = pselfWorth
End Property
Public Property Let selfWorth(name As String)
pselfWorth = name
End Property
Public Property Get distribution() As Double
distribution = pdistribution
End Property
Public Property Let distribution(dist As Double)
pdistribution = dist
End Property
Registrant a.k.a distributor class
Private pRegistNum As String
Private pCatDist As testcatDist
Public Property Get registNum() As String
registNum = pRegistNum
End Property
Public Property Let registNum(registration As String)
pRegistNum = registration
End Property
Public Property Get testCat() As testcatDist
testCat = pCatDist
End Property
Public Property Let testCat(cat As testcatDist)
Set pCatDist = New testcatDist
pCatDist = cat
End Property
The only problem I see is that you are using Let instead of Set. In VBA you use Set when assigning to objects.
When you write registrant.testCat = cowMilk (in your Sub), testCat = pCatDist (in the getter of testRegistrant.testCat) and pCatDist = cat (in the setter of testRegistrant.testCat) you are implicitly using Let (it's as if you had written Let registrant.testCat = cowMilk) instead of (explicitly) using Set.
So, if you write Set registrant.testCat = cowMilk in your test Sub, Set testCat = pCatDist in the getter and Set pCatDist = cat in the setter you should be good to go.
Also, in the same setter, the initialization of pCatDist isn't needed since you are passing cat to it in the next line.
And, as #GSerg (thank you) says, the signature of your setter should be Public Property Set testCat(cat as testcatDist) instead of Public Property Let.

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.

Listen to property change through Entity Framework AssociationChanged event

I can successfully listen to changes on Entity Framework child EntityCollection if the action is add or delete but cannot find the way to listen to changes if the child class property value was updated.
More specifically, in the below, how can I access the property name that was changed on the child ("Employee") class to run some business logic on the parent ("Company") class?
Public Sub New()
AddHandler Me.employees.AssociationChanged, AddressOf employees_AssociationChanged
End Sub
Private Sub employees_AssociationChanged(ByVal sender As Object, ByVal e As CollectionChangeEventArgs)
Dim act As CollectionChangeAction = e.Action
Dim employeeOnOtherEnd As employee = CType(e.Element, employee)
If Not employeeOnOtherEnd Is Nothing Then
If act = CollectionChangeAction.Add Then
'logic when new employee added
ElseIf act = CollectionChangeAction.Remove Then
'logic when new employee was deleted
End If
'I want to run some business logic here if some employee property value was updated... How to do that?
End If
End Sub
I have INotifyPropertyChanged but I don't want to place any code inside employee class to affect the company class directly. Instead I want to catch the change in company class and run the logic there. I'd like to see the Visual Basic example for this.

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 - 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