Explicit Loading of child navigation properties with criteria - entity-framework

Using DBContext in EF5 - after filtering and partial loading based on criteria like a date range.
I'm trying to produce a complete graph or tree of objects - Persons->Events where the only Events that are included are within a date range. All this whilst preserving the standard change tracking that one gets with the following:
Dim Repository As Models.personRepository = New Models.personRepository
Private Sub LoadData()
Dim personViewModelViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("personViewModelViewSource"), System.Windows.Data.CollectionViewSource)
Repository.endDate = EndDate.SelectedDate
Repository.startDate = StartDate.SelectedDate
personViewModelViewSource.Source = Repository.collectionOfpersons
End Sub
A listbox and a datagrid are both bound as a proper datasource. The POCO template has been modified to put INotifyProperty events in the navigation property class, Events.
I've been fighting with this for days now and filtering whether on Lazy loading or Explicit loading does not function. After masses of blog/chaper reading, I'm aware of the rather unreal limitation relating to Include; instead I'm trying explicit loading. I'm using the DBContext book btw.
Being unable to bring back only a subset of the Event data from the database is 100% deal breaker as there is likely to be hundreds of thousands of Events per Person. It doesn't make sense to me or my boss that the Entity Framework doesn't make this functionality fairly obvious-are we missing something?
I've left in the commented code to try and illustrate some of the paths I've attempted. The class itself is a repository that this method belongs to. I'll edit this question further to clarify just how many routes I've tried as it's been a LOT. The View uses a repository layer and a ViewModel so the code behind on the XAML is rather minimal.
In advance for any help, thank you!
Public Overridable ReadOnly Property AllFiltered(startdate As Date, enddate As Date) As ObservableCollection(Of person) Implements IpersonRepository.AllFiltered
Get
Dim uow = New UnitOfWork
context = uow.Context
Dim personQuery = context.persons.Include(Function(p) p.events).AsQueryable.Where(Function(x) x.personID = 10).FirstOrDefault
'Dim eventQuery = From e In context.Notes
' Where e.eventDateTime >= startdate And e.eventDateTime <= enddate
' Select e
'Dim personQuery As person = From r In context.persons
' From e In eventQuery
' Where r.personID = e.personID
' Select r, e
Dim singleperson = personQuery
'For Each r As person In personQuery
' persons.Add(r)
'Next
' context.Entry(eventQuery).Collection()
' context.Entry(personQuery).Reference(personQuery).Load()
context.Entry(singleperson).Collection(Function(d) d.events).Query().Where(Function(x) x.eventDateTime > startdate And x.eventDateTime < enddate).Load()
Return context.persons.Local
End Get
End Property
Note: I'm using logging/exception handling via PostSharp rather than polluting the code.
Below are some of the errors I've generated with previous paths taken.
The entity type DbQuery`1 is not part of the model for the current context.
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parameter name: path
Unable to cast object of type 'System.Data.Entity.Infrastructure.DbQuery1[VB$AnonymousType_02
[Entity.Person,Entity.Notes]]' to type 'System.Collections.ObjectModel.ObservableCollection`1[Entity.Person]'.
UPDATE: Yet another route I've tried, still cannot get this to fly either:
Private Property _collectionOfPersons As ObservableCollection(Of Person)
Public ReadOnly Property collectionOfPersons As ObservableCollection(Of Person)
Get
For Each Person In context.Persons
_collectionOfPersons.Add(ReturnSinglePerson(startDate, endDate, Person.PersonID))
Next
Return _collectionOfPersons.Where(Function(x) x.events.Where(Function(e) e.eventDateTime > startDate And e.eventDateTime < endDate))
End Get
End Property
Public Overridable ReadOnly Property SinglePerson(startdate As Date, enddate As Date) As ObservableCollection(Of Person) Implements IPersonRepository.AllFiltered
Get
Dim PersonQuery = context.Persons.Include(Function(p) p.events).AsQueryable.Where(Function(x) x.PersonID = 10).Select(Function(x) x).FirstOrDefault
Dim Person = PersonQuery
context.Entry(Person).Collection(Function(d) d.events).Query().Where(Function(x) x.eventDateTime > startdate And x.eventDateTime < enddate).Load()
Return context.Persons.Local
End Get
End Property
Public Function ReturnSinglePerson(startdate As Date, enddate As Date, id As Integer)
Dim PersonQuery = context.Persons.Include(Function(p) p.events).AsQueryable.Where(Function(x) x.PersonID = id).Select(Function(x) x).FirstOrDefault
Dim Person = PersonQuery
Return Person
End Function
Another shot:
Public Overridable ReadOnly Property FilteredPersons(startdate As Date, enddate As Date) As ObservableCollection(Of Person) Implements IPersonRepository.AllFiltered
Get
context.Persons.Load()
Dim DateCriteria = Function(e) e.events.Where(Function(d) d.eventDateTime > startdate And d.eventDateTime < enddate)
Dim Res = New ObservableCollection(Of Person)
For Each Person In context.Persons.Local.Select(Function(x) x).Where(DateCriteria)
Res.Add(Person)
Next
Return Res
End Get
End Property
Gives:
Public member 'Where' on type 'ObservableCollection(Of DailyNotes)' not found.
Tantalisingly close, only I get lots of duplicate names on the listbox - but the navigation carries through and the Date criteria work.
<ExceptionAspect>
Public Overridable ReadOnly Property FilteredPersons(startdate As Date, enddate As Date) As ObservableCollection(Of Person) Implements IPersonRepository.AllFiltered
Get
context.Persons.Load()
Dim test = From r In context.Persons
From e In context.Notes
Where e.eventDateTime > startdate And e.eventDateTime < enddate
Join rr In context.Persons On e.PersonID Equals rr.PersonID
Select r, e
Dim Res = New ObservableCollection(Of Person)
For Each Person In test
Res.Add(Person.r)
Next
Return Res
End Get
End Property
Don't try this one :). It simply selects the child properties only.
Public ReadOnly Property collectionOfResidents As ObservableCollection(Of resident)
Get
For Each resident In context.residents
_collectionOfResidents.Add(ReturnSingleResident(startDate, endDate, resident.residentID))
Next
Return _collectionOfResidents.Select(Function(x) x.events.Where(Function(e) e.eventDateTime > startDate And e.eventDateTime < endDate))
End Get
End Property
I'm hoping that adding my other attempts to this question may prompt both other answers and help others see the circles they can get into when first tackling this!

You can use the Select clause for finer control than with Include
Something like this:
context
.Persons
.Where( ... some predicate on Person ... )
.Select( o => new
{
Person = o,
Events = o.Events.Where( ... some predicate on Event ... )
}
)
;
This will translate both predicates into SQL which execute on the database server.

Ok after a LOT of fiddling and misunderstanding of anonymous types this evening, I think I succeeded. Nicholas's answer just needed to be done in VB which took me a while - I've not used anonymous types before.
This is what appears to work fine in my repository layer:
<ExceptionAspect>
Public Overridable ReadOnly Property FilteredPersons(startdate As Date, enddate As Date) As ObservableCollection(Of Person) Implements IPersonRepository.AllFiltered
Get
context.Persons.Load()
Dim test = context.Persons.Where(Function(r) r.PersonActive).Select(Function(o) New With { _
.Person = o, _
.events = o.events.Where(Function(e) e.eventDateTime > startdate) _
})
Dim PersonList= New ObservableCollection(Of Person)
For Each t In test
PersonList.Add(t.person)
Next
Return PersonList
End Get
End Property
The crucial updating/saving in the wpf View is intact, I'm really happy and grateful to Nicholas for his help on here (and patience...re:cartesion product). So thank you. I hope this helps someone else!

Related

Using AutoBogus to set member property value range

I have the following classes:
Class Person
Property ID As String = Nothing
Property Firstname As String = ""
Property Lastname As String = ""
End Class
Class Account
Property AccountNumber As String = ""
Property Owners As New List(Of Person)
End Class
I wish to you utilize https://github.com/nickdodd79/AutoBogus to set a range of values from 1,000 to 10,000 for Person.ID when I instantiate an instance of the Account class like so:
Dim fk = AutoFaker.Create()
Dim acct = fk.Generate(Of Account)
Please how may I do this using AutoBogus?
The original design of AutoBogus was to generate small object graphs for unit tests. Generating the numbers you require could have a performance impact. However, if the above is what you are trying to generate and nothing more complex, then it could be small enough to succeed.
To use AutoBogus out the box you can do the following:
Dim acct = AutoFaker.Generate(Of Account, 1000)
The second parameter should be the number of accounts you want to create.
AutoBogus uses Bogus under the hood and then leverages Reflection to populate unset properties. If you do see any performance issues, you could use Bogus directly with the caveat that you need define a RuleFor for each property. The Bogus docs provide in-depth details on how to achieve this.
Nick.
Solution modified from answer provided by Bogus author bchavez at https://github.com/bchavez/Bogus/issues/394.
Sub Main
Dim personFaker = New AutoFaker(Of Person)
personFaker.RuleFor(Function(p) p.Firstname, Function(f) f.Name.FirstName)
.RuleFor(Function(p) p.Lastname, Function(f) f.Name.LastName)
.RuleFor(Function(p) p.ID, Function(f) f.Random.Int(1000,10000).ToString)
Dim accountFaker = New AutoFaker(Of Account)
accountFaker.RuleFor(Function(a) a.AccountNumber, Function(f) f.Random.Replace("###############"))
.RuleFor(Function(a) a.Owners, Function(f) New List(Of Person)(personFaker.GenerateBetween(1,5)))
accountFaker.Generate().Dump()
End Sub
Class Person
Property ID As String = Nothing
Property Firstname As String = ""
Property Lastname As String = ""
End Class
Class Account
Property AccountNumber As String = ""
Property Owners As New List(Of Person)
End Class

combining classes in vba?

I have a problem finding the right structure of my programme and I am trying to solve it using classes, without success.
I have 1 class for Patients, each Patient has the following:
Name
Operation
Surgeon
OperationDuration
Then each Surgeon should have a schedule for everyday.
Therefore, I am thinking using another class for daily schedule, which should have :
Day
TotalDuration
Something in my thinking doesn't look correct and I am struggling to understand what should I do.
1)Is my structure correct for what I want to do?
2)How can I check if a surgeon has a planned daily schedule and if he doesn't then add a patient to his schedule?
Any help would be much appreciated.
Thanks,
George
Class Patients
Private mstrName As String
Private mstrOperationDescription As String
Private mlngSurgeon As Long
Private mdblOpDuration As Double
Public Property Get Name() As String
Name = mstrName
End Property
Public Property Get OperationDescription() As String
OperationDescription = mstrOperationDescription
End Property
Public Property Get Surgeon() As Long
Surgeon = mlngSurgeon
End Property
Public Property Get OpDuration() As Double
OpDuration = mdblOpDuration
End Property
Class Schedule
Private mlngDay As Long
Private mdblTotalDuration As Double
Public Property Get Day() As Long
Day = mlngDay
End Property
Public Property Let Day(ByVal lDay As Long)
mlngDay = lDay
End Property
Public Property Get TotalDuration() As Double
TotalDuration = mdblTotalDuration
End Property
Public Property Let TotalDuration(ByVal dTotalDuration As Double)
mdblTotalDuration = dTotalDuration
End Property
Test Sub calculating the total duration but I am not able to list them according to days count
Public Sub Test()
Dim mydata As New clsData
Dim schedule1 As New clsSchedule
Dim schedule2 As New clsSchedule
Dim i As Integer
mydata.InputData
For i = 1 To mydata.PatientCount
If mydata.patient(i).Surgeon = 1 Then
schedule1.TotalDuration = schedule1.TotalDuration + mydata.patient(i).OpDuration
Else
schedule2.TotalDuration = schedule2.TotalDuration + mydata.patient(i).OpDuration
End If
Next i
MsgBox "Total Duration is: " & schedule1.TotalDuration
End Sub
One solution would be to have a Collection of Surgeon objects and a Collection of Patient objects. Each Patient has a Surgeon as a property, and each Surgeon has a Collection of Patients.
From this you can find out the total operation duration across all patients belonging to a Surgeon.
The trick is to know how the objects relate to each other - in this case each Patient has one Surgeon, and each Surgeon can have many Patients. This leads quite logically to the following structure:
Patient
Private mstrName As String
Private mstrOperationDescription As String
Private mobjSurgeon As cSurgeon
Private mdblOpDuration As Double
Property Let Name(txt As String)
mstrName = txt
End Property
Property Let OperationDescription(txt As String)
mstrOperationDescription = txt
End Property
Property Let Surgeon(objSurgeon As cSurgeon)
Set mobjSurgeon = objSurgeon
End Property
Property Let OpDuration(num As Double)
mdblOpDuration = num
End Property
Property Get OpDuration() As Double
OpDuration = mdblOpDuration
End Property
Surgeon
Private mstrName As String
Private mlngSurgeonId As Long
Private mcolPatients As Collection
Private Sub Class_Initialize()
Set mcolPatients = New Collection
End Sub
Property Let Name(txt As String)
mstrName = txt
End Property
Property Get Name() As String
Name = mstrName
End Property
Property Let IdNumber(num As Long)
mlngSurgeonId = num
End Property
Property Get IdNumber() As Long
IdNumber = mlngSurgeonId
End Property
Sub AddPatient(objPatient As cPatient)
mcolPatients.Add objPatient, objPatient.Name
End Sub
Function TotalHours() As Double
Dim objPatient As cPatient
For Each objPatient In mcolPatients
TotalHours = TotalHours + objPatient.OpDuration
Next objPatient
End Function
Test Routine
Sub CheckSurgeonHours()
Dim colSurgeons As Collection
Set colSurgeons = New Collection
Dim colPatients As Collection
Set colPatients = New Collection
'Populate Surgeon and Patient collections from input data
'This is the static data for each object, i.e. name, Id, operation type/duration
Dim objSurgeon As cSurgeon
For Each objSurgeon In colSurgeons
Dim objPatient As cPatient
For Each objPatient In colPatients
objPatient.Surgeon = objSurgeon
objSurgeon.AddPatient objPatient
Next objPatient
Debug.Print objSurgeon.TotalHours
Next objSurgeon
End Sub

EF6 Database First making Stored Procedure Async

What's the right way to run a EF6 stored procedure (database-first) in async mode?
I read about ToListAsync() but I don't see that available on stored procedure.
Also not sure if there is a different way to call the stored procedure when the actual call returns (#1) an OUT param or a (#2) list of items:
Case #1
using (DBContext db = new DBContext())
{
ObjectParameter result = new ObjectParameter("Result",
typeof(global::System.Boolean));
db.Login("email#email.com", "password", result);
}
Case #2
using (DBContext db = new DBContext())
{
var result = db.Contact_GetList("New York");
}
Thanks for the help
As per this workitem you would need to use SqlQueryAsync. Feel free to upvote the work item on the EF Codeplex site.
To map stored procedures and start using it with out writing any initial code, this is how I did it.
create a new model with a new connection string that will generate the connection string automatically in the web.config file where the connection string is at (if you use a current connection string it may no work when you test the function for the SP on the model browser).
map your table and the stored procedures (you can test the stored procedures in the model browser).
create classes that represents the attributes retrieved by each stored procedure e.g if your stored procedure returns three columns A,B,C, then the class must also have these three columns as attribute with the [key()] on top of the column that is going to be the PK
now create your controller with the class created and a new DbContext
then copy the information in the data context generated for the model and pasted in the new context that you generate when creating the controller.
when you want to use the store procedures they will be ready on the db.context because you paste their code on you new db-context that you create when the controller was crated.
NOTE: I hope this is not confusing but I can use the stored procedures with out typing any code, please ask me if you need sample code or screen shots, your new db-context will not over write after you created
This is the stored procedure I map
'--------------------------------------------------------------------------
' <auto-generated>
' This code was generated from a template.
'
' Manual changes to this file may cause unexpected behavior in your application.
' Manual changes to this file will be overwritten if the code is regenerated.
' </auto-generated>
'-----------------------------------------------------------------------
Imports System
Imports System.Collections.Generic
Partial Public Class phone_CurrentConferences_Result
Public Property AppointmentID As Integer
Public Property AppTitle As String
Public Property DateTime As Nullable(Of Date)
Public Property [Date] As String
Public Property Time As String
Public Property Company As String
Public Property Contact As String
Public Property Phone As String
Public Property Office As String
Public Property Lead_Director As String
Public Property TBD As Nullable(Of Boolean)
Public Property conference As String
End Class
This is the same model with a primary key
Imports System
Imports System.Collections.Generic
Imports System.ComponentModel.DataAnnotations
Public Class Conferences
[Key]
Public Property AppointmentID As Integer
Public Property AppTitle As String
Public Property DateTime As Nullable(Of Date)
Public Property [Date] As String
Public Property Time As String
Public Property Company As String
Public Property Contact As String
Public Property Phone As String
Public Property Office As String
Public Property Lead_Director As String
Public Property TBD As Nullable(Of Boolean)
Public Property conference As String
End Class
This is the context generated by the EF
'--------------------------------------------------------------------------
' <auto-generated>
' This code was generated from a template.
'
' Manual changes to this file may cause unexpected behavior in your application.
' Manual changes to this file will be overwritten if the code is regenerated.
' </auto-generated>
'--------------------------------------------------------------------------
Imports System
Imports System.Data.Entity
Imports System.Data.Entity.Infrastructure
Imports System.Data.Entity.Core.Objects
Imports System.Linq
Partial Public Class DayMasterEntities
Inherits DbContext
Public Sub New()
MyBase.New("name=DayMasterEntities")
End Sub
Protected Overrides Sub OnModelCreating(modelBuilder As DbModelBuilder)
Throw New UnintentionalCodeFirstException()
End Sub
Public Overridable Function phone_CurrentConferences(number As String, [date] As Nullable(Of Date)) As ObjectResult(Of phone_CurrentConferences_Result)
Dim numberParameter As ObjectParameter = If(number IsNot Nothing, New ObjectParameter("number", number), New ObjectParameter("number", GetType(String)))
Dim dateParameter As ObjectParameter = If([date].HasValue, New ObjectParameter("date", [date]), New ObjectParameter("date", GetType(Date)))
Return DirectCast(Me, IObjectContextAdapter).ObjectContext.ExecuteFunction(Of phone_CurrentConferences_Result)("phone_CurrentConferences", numberParameter, dateParameter)
End Function
End Class
SO, when I create the controller I use the model with the <KEY()> and I create my own context that will look like this
Imports System.Data.Entity
Imports System.Data.Entity.Infrastructure
Imports System.Data.Entity.Core.Objects
Namespace Models
Public Class DayMasterContext
Inherits DbContext
' You can add custom code to this file. Changes will not be overwritten.
'
' If you want Entity Framework to drop and regenerate your database
' automatically whenever you change your model schema, please use data migrations.
' For more information refer to the documentation:
' http://msdn.microsoft.com/en-us/data/jj591621.aspx
Public Sub New()
MyBase.New("name=DayMasterEntities")
End Sub
Protected Overrides Sub OnModelCreating(modelBuilder As DbModelBuilder)
Throw New UnintentionalCodeFirstException()
End Sub
Public Property Conferences As System.Data.Entity.DbSet(Of Conferences)
End Class
End Namespace
Then I copy the info in the context generated by the EF to my context
Imports System.Data.Entity
Imports System.Data.Entity.Infrastructure
Imports System.Data.Entity.Core.Objects
Namespace Models
Public Class DayMasterContext
Inherits DbContext
' You can add custom code to this file. Changes will not be overwritten.
'
' If you want Entity Framework to drop and regenerate your database
' automatically whenever you change your model schema, please use data migrations.
' For more information refer to the documentation:
' http://msdn.microsoft.com/en-us/data/jj591621.aspx
Public Sub New()
MyBase.New("name=DayMasterEntities")
End Sub
Protected Overrides Sub OnModelCreating(modelBuilder As DbModelBuilder)
Throw New UnintentionalCodeFirstException()
End Sub
Public Overridable Function phone_CurrentConferences(number As String, [date] As Nullable(Of Date)) As ObjectResult(Of phone_CurrentConferences_Result)
Dim numberParameter As ObjectParameter = If(number IsNot Nothing, New ObjectParameter("number", number), New ObjectParameter("number", GetType(String)))
Dim dateParameter As ObjectParameter = If([date].HasValue, New ObjectParameter("date", [date]), New ObjectParameter("date", GetType(Date)))
Return DirectCast(Me, IObjectContextAdapter).ObjectContext.ExecuteFunction(Of phone_CurrentConferences_Result)("phone_CurrentConferences", numberParameter, dateParameter)
End Function
Public Property Conferences As System.Data.Entity.DbSet(Of Conferences)
End Class
End Namespace
So, now you can use this context to query
entConferences(number As String, [date] As Nullable(Of Date)) As ObjectResult(Of phone_CurrentConferences_Result)
or to get a DBSet(of conferences)
Here is a controller I have created with this technique
Look where I call my stored procedure
Dim conferences = db.phone_CurrentConferences(phoneNumber, currentDate)
Imports System.Data
Imports System.Data.Entity
Imports System.Data.Entity.Infrastructure
Imports System.Linq
Imports System.Net
Imports System.Net.Http
Imports System.Web.Http
Imports System.Web.Http.Description
Imports BIWEBAPI
Imports BIWEBAPI.Models
Namespace Controllers.DayMasterControllers
Public Class ConferencesController
Inherits System.Web.Http.ApiController
Private db As New DayMasterContext
' GET: api/Conferences
Function GetConferences() As IQueryable(Of Conferences)
Return db.Conferences
End Function
' GET: api/Conferences/3053742500
''' <summary>
''' Use to get the current conferences selected by date
''' </summary>
''' <param name="id">phone number and date separated by coma ",""</param>
''' <returns>conferences by date</returns>
''' <remarks></remarks>
<ResponseType(GetType(Conferences))>
Function GetConferences(ByVal id As String) As List(Of Conferences)
Dim conferencelist = New List(Of Conferences)
Dim dateAndPhoneNumber = Split(id, ",")
Dim currentDate = ""
Dim phoneNumber = dateAndPhoneNumber(0)
If dateAndPhoneNumber.Length > 1 Then
currentDate = DateTime.Parse(dateAndPhoneNumber(1))
Else : currentDate = DateTime.Today
End If
Dim conferences = db.phone_CurrentConferences(phoneNumber, currentDate)
For Each conferenceInQuery As Object In conferences
Dim conference = New Conferences()
conference.AppointmentID = conferenceInQuery.AppointmentID
conference.AppTitle = conferenceInQuery.AppTitle
conference.DateTime = conferenceInQuery.DateTime
conference.[Date] = conferenceInQuery.[Date]
conference.Time = conferenceInQuery.Time
conference.Company = conferenceInQuery.Company
conference.Contact = conferenceInQuery.Contact
conference.Phone = conferenceInQuery.Phone
conference.Office = conferenceInQuery.Office
conference.Lead_Director = conferenceInQuery.Lead_Director
conference.TBD = conferenceInQuery.TBD
conference.conference = conferenceInQuery.conference
conferencelist.Add(conference)
Next
Return conferencelist
End Function
' PUT: api/Conferences/5
<ResponseType(GetType(Void))>
Function PutConferences(ByVal id As Integer, ByVal conferences As Conferences) As IHttpActionResult
If Not ModelState.IsValid Then
Return BadRequest(ModelState)
End If
If Not id = conferences.AppointmentID Then
Return BadRequest()
End If
db.Entry(conferences).State = EntityState.Modified
Try
db.SaveChanges()
Catch ex As DbUpdateConcurrencyException
If Not (ConferencesExists(id)) Then
Return NotFound()
Else
Throw
End If
End Try
Return StatusCode(HttpStatusCode.NoContent)
End Function
' POST: api/Conferences
<ResponseType(GetType(Conferences))>
Function PostConferences(ByVal conferences As Conferences) As IHttpActionResult
If Not ModelState.IsValid Then
Return BadRequest(ModelState)
End If
db.Conferences.Add(conferences)
db.SaveChanges()
Return CreatedAtRoute("DefaultApi", New With {.id = conferences.AppointmentID}, conferences)
End Function
' DELETE: api/Conferences/5
<ResponseType(GetType(Conferences))>
Function DeleteConferences(ByVal id As Integer) As IHttpActionResult
Dim conferences As Conferences = db.Conferences.Find(id)
If IsNothing(conferences) Then
Return NotFound()
End If
db.Conferences.Remove(conferences)
db.SaveChanges()
Return Ok(conferences)
End Function
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If (disposing) Then
db.Dispose()
End If
MyBase.Dispose(disposing)
End Sub
Private Function ConferencesExists(ByVal id As Integer) As Boolean
Return db.Conferences.Count(Function(e) e.AppointmentID = id) > 0
End Function
End Class
End Namespace

entity framework retrieve data as list(of string)

I have the following code to select all counties given a stateid from a counties table.
Public Shared Function GetCountiesfromState(statename As String) As List(Of String)
Dim context As New Model.teckEntities()
Dim query = From c In context.counties Where c.stateId = 7 Select c
Return query.ToList()
End Function
I get any error that query is returning a list of model. any thoughts on where the error lies?
If there is a Name (or Title) field on the County entity, it should be as simple as:
Public Shared Function GetCountiesfromState(statename As String) As List(Of String)
Dim context As New Model.teckEntities()
' Here is the difference:
Dim query = From c In context.counties Where c.stateId = 7 Select c.Name
Return query.ToList()
End Function
In your code above you were selecting the c which, is a County entity, not necessarily a string property. By selecting c.Name (or c.Title) instead, you'll be building a list of strings instead of a list of county entities.
Cheers.

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.