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

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

Related

classic asp/vbscript class to track employees and performance metrics

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

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/

Code First Entity Framework

We are using Miscrosoft's code first entity framework (4.1) to map to an existing database. We want to be able to change the datatypes and values of some properties that map one to one with a table. For instance, there is a column on the table that determines if a record is current. It is an integer column, and has values 1 or 2. We don't want to change the database as there are many different applications fetching data from that colum, but it would be nice for our code to have the class that maps to that table have a bool property that is IsActive, which returns true if the table column is 1 and false otherwise. Is there a way to configure the EnityFrame work so that we can define this mapping directly without having two properties on the actual class, one for the integer column (mapped to the database) and one boolean property computed from the other? Can I map the boolean property directly to the integer column?
Simple answer is no. EF is totally stupid in this area and it is completely missing simple type mapping.
That means that you cannot change type of scalar properties and your class indeed has to work with that int property using values 1 and 2 to define your IsActive.
The workaround can be:
public class YourClass
{
public int IsActiveValue { get; set; }
[NotMapped]
public bool IsActive
{
get { return IsActiveValue == 2; }
set { IsActiveValue = value ? 2 : 1; }
}
}
This workaround has some disadvantages
You must have two properties and IsActvieValue must be visible to context
You cannot use IsActive in linq-to-entities queries

Having difficulty seeing how to work with many-to-many relationships in MVC2/EF4

I've written about this a few times, but am still having difficulty seeing how to work with many-to-many relationships in MVC2 with EF4, specifically when it comes to Create and Edit functionality. I think part of my problem is that I decided to create my database tables in such a way that the pivot tables aren't visible in the model itself.
My tables, once again:
Games:
int GameID (primary key, auto-incr)
string GameTitle
string ReviewTitle
int Score
int ReviewContentID (foreign key from Content - News, Articles, and Game reviews all have similar content requirements)
int GenreID (foreign key from Genres)
Platforms:
int PlatformID (primary key, auto-incr)
string Name
GamePlatform (not visible in model):
int GameID (foreign key from Games)
int PlatformID (foreign key from Platforms)
When I create a new review, I really just want to add entries to the GamePlatform pivot table, as I'm just trying to link the game I reviewed to the already existing platforms. Dealing with it in an OOP level is confusing me as I keep thinking I'm adding to Platforms, when all I really want to do is link Games' id to various Platform ids.
So, I don't want to create a new Platform from the incoming HTTP-Post data. I just want to be able to take the form data and create a new Game, new review Content, and link the new Game to existing Platforms based on selected checkboxes.
I understand how to perform the first two tasks. It's the linking I can't seem to grasp.
Apologies for continuing to harp on this, but it's really the one thing holding me back from making significant progress.
Assuming you have correctly mapped this in entity framework and your Game object has a Platforms collection then assigning existing Platforms to a Game should be as simple as passing the IDs of those platforms to your Game add/edit action.
In your form you can use a series of checkboxes with a value attribute of PlatformID and a single common name, 'platformids' for example. Note, the Html.CheckBox() HtmlHelper doesn't have a parameter for 'value', so you'll have to specify it by way of the htmlAttributes object. MVC's default model binder will automatically group your collection of 'platformid' values in the form into a single typed IEnumerable by adding a matching parameter to the receiving action.
Here's some code to get you started:
// games controller
public action AddGame(Game newGame, int[] platformIds) {
Platforms[] platforms;
if(platFormIds != null && platformIds.Any()) {
platforms = ObjectContext.Platforms.Where(ExpressionExtensions.BuildOrExpression<Platform, int>(p => p.PlatformID, platformIds)).ToList();
}
if(ModelState.IsValid()) {
game.Platforms.AddRange(platforms);
ObjectContext.AddToGames(game);
ObjectContext.SaveChanges();
}
}
// helper class
public static Expression<Func<TElement, bool>> BuildOrExpression<TElement, TValue>(Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values) {
if (valueSelector == null) throw new ArgumentNullException("valueSelector");
if (values == null) throw new ArgumentNullException("values");
ParameterExpression p = valueSelector.Parameters.Single();
if (!values.Any())
return e => false;
IEnumerable<Expression> equals = values.Select(value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
Expression body = equals.Aggregate(Expression.Or);
return Expression.Lambda<Func<TElement, bool>>(body, p);
}
Note: The BuildOrExpression() above is just a nice way to create the SQL equivalent of SELECT * FROM TABLE WHERE ID IN(1,2,3,4,5,...).

How to determine if an Entity with relationship properties has changes

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.