Updated ComplexObject values AfterSaveEntities: missing from server to client - breeze

I am calculating some values in the AfterSaveEntities method on the server using Breeze EFContextProvider. I am doing this for several different entity types.
After I calculate the new values, I save them to the datastore (using a new datacontext as specified in other SO questions).
I also update the entities in the saveMap with these new scalar values. These values are just integer properties that make up a ComplexObject that is a property of the entity being saved.
Everything works great. I can see just before my SaveChanges API function returns to client, that the properties are my ComplexObject are all correct.
Now, for most of my entity types, when the saveResult hits the client, everything is as expected. EXCEPT...for one of my entity types.
For this problem entity, the properties of the ComplexObject are not getting updated. If I look at the client-side complexAspect.originalValues property of the ComplexObject, the originalValues are the values I'm expecting to be the actual values the object...the same values that it had just before it left the server in the SaveChanges result.
I'm wondering if the answer is with adjusting entityInfo.OriginalValuesMap in the AfterSaveEntities function. But it didn't seem to make a different. I wonder if working with Complex Objects requires a different syntax when adjusting the OriginalValuesMap.
EDIT: I discovered that this unexpected behavior happens only when I create a new EntityInfo and add it to the SaveMap Dictionary in AfterSaveEntities.
The new entities that I create via CreateEntityInfo(entity, Breeze.ContexProvider.EntityState.Unchanged) are added to the saveMap with proper Type as the key.
Ironically, these extra entities all have the properties on their complex objects show up fine on client side. But as soon as I add more entities to the saveMap is when my original entity (that is already in the saveMap) starts messing up.
I hope I made my problem clear enough.

Here's my solution for anyone else with this problem...
The problem goes back to having 2 datacontexts...I needed a 2nd one to load related entities to do calculations for business logic.
I was adding entities from my 2nd context back into the SaveMap...I fixed this by instead loading those entities from the original breeze context and saving THOSE into the SaveMap.
Protected Overrides Sub AfterSaveEntities(saveMap As Dictionary(Of Type, List(Of EntityInfo)), keyMappings As List(Of KeyMapping))
Dim context2 = New Data.Db(EntityConnection)
context2.Configuration.ProxyCreationEnabled = False
context2.Configuration.LazyLoadingEnabled = False
Dim newEntitiesMap As New Dictionary(Of Type, EntityInfo)
For Each entType In saveMap
Select Case entType.Key.Name
Case "Registration"
For Each ent In entType.Value
Dim registration = DirectCast(ent.Entity, Registration)
registration.Stats = GenerateRegistrationStats(context2, registration.Id)
Next
Case "ConferenceRoom"
For Each ent In entType.Value
Dim conferenceRoom = DirectCast(ent.Entity, ConferenceRoom)
conferenceRoom.Stats = GenerateConferenceRoomStats(context2, conferenceRoom.Id)
Next
Case "Reservation"
For Each ent In entType.Value
Dim reservation = DirectCast(ent.Entity, Reservation)
reservation.Stats = GenerateReservationStats(context2, reservation.Id)
''Update ConferenceRoom stats
Dim confRoom = Me.Context.ConferenceRooms.Find(reservation.ConferenceRoomId)
confRoom.Stats = GenerateConferenceRoomStats(context2, confRoom.Id)
AddToSaveMap(newEntitiesMap, confRoom, GetType(ConferenceRoom))
''Update Registration stats
Dim registration = Me.Context.Registrations.Find(reservation.RegistrationId)
registration.Stats = GenerateRegistrationStats(context2, registration.Id)
AddToSaveMap(newEntitiesMap, registration, GetType(Registration))
Next
Case "Registree" 'update
For Each ent In entType.Value
Dim registree = DirectCast(ent.Entity, Registree)
Dim registration = Me.Context.Registrations.Find(registree.RegistrationId)
registration.Stats = GenerateRegistrationStats(context2, registration.Id)
AddToSaveMap(newEntitiesMap, registration, GetType(Registration))
Next
End Select
Next
context2.SaveChanges()
For Each ent In newEntitiesMap
If saveMap.ContainsKey(ent.Key) Then
'add to existing list
saveMap(ent.Key).Add(ent.Value)
Else
Dim list As New List(Of EntityInfo) From {ent.Value}
saveMap.Add(ent.Key, list)
End If
Next
End Sub
Private Sub AddToSaveMap(AddToSaveMap As Dictionary(Of Type, EntityInfo), entity As Object, classType As Type)
Dim entInfo = Me.CreateEntityInfo(entity, Breeze.ContextProvider.EntityState.Unchanged)
AddToSaveMap.Add(classType, entInfo)
End Sub
Private Function GenerateConferenceRoomStats(context As Data.Db, conferenceRoomId As Integer) As ConfRoomStats
Dim confRoom = context.ConferenceRooms.Where(Function(x) x.Id = conferenceRoomId) _
.Include(Function(x) x.Reservations.Select(Function(r) r.Occupants)).FirstOrDefault
confRoom.Stats = confRoom.GenerateStats
Return confRoom.Stats
End Function
Private Function GenerateReservationStats(context As Data.Db, reservationId As Integer) As ReservationStats
Dim reservation = context.Reservations.Where(Function(x) x.Id = reservationId) _
.Include(Function(x) x.Occupants).FirstOrDefault
reservation.Stats = reservation.GenerateStats
Return reservation.Stats
End Function
Private Function GenerateRegistrationStats(context As Data.Db, registrationId As Integer) As RegStats
Dim registration = context.Registrations.Where(Function(x) x.Id = registrationId) _
.Include(Function(x) x.Registrees.Select(Function(r) r.Person)) _
.Include(Function(x) x.Estimates) _
.Include(Function(x) x.Reservations.Select(Function(r) r.ConferenceRoom)) _
.Include(Function(x) x.Reservations.Select(Function(r) r.Occupants)).FirstOrDefault
registration.Stats = registration.GenerateStats
Return registration.Stats
End Function

Related

Entity Framework 6 - Passing Table Valued Parameter to Table Valued Function, returning IQueryable

I want to call a Table Valued Function from entity framework, and have it return an IQueryable. The code I have is:
// Defines table-valued function, which must return IQueryable<T>.
[Function(FunctionType.TableValuedFunction, nameof(ufnGetContactInformation), Schema = dbo)]
public IQueryable<ContactInformation> ufnGetContactInformation(
[Parameter(DbType = "int", Name = "PersonID")]int? personId)
{
ObjectParameter personIdParameter = personId.HasValue
? new ObjectParameter("PersonID", personId)
: new ObjectParameter("PersonID", typeof(int));
return this.ObjectContext().CreateQuery<ContactInformation>(
$"[{nameof(this.ufnGetContactInformation)}](#{nameof(personId)})", personIdParameter);
}
However, I also want to be able to pass into the TVF a Table Valued Parameter (so I can pass a list of Id's into the TVF). I've found code that can do this using a DataTable which references the user-defined type I have in the db, eg:
var dt = new DataTable();
dt.Columns.Add("Id");
dt.Rows.Add(1);
dt.Rows.Add(2);
dt.Rows.Add(3);
var idList = new SqlParameter("idList", SqlDbType.Structured);
warnings.Value = dt;
warnings.TypeName = "dbo.udt_idList";
entities.ExecuteStoredProcedure("usp_TableValuedFunc", idList);
The problem is, the second code uses SQLParameter, and the first code uses ObjectParameter. It appears that ObjectParameter won't allow for user-defined-types to be supplied, and any code EF uses that accepts SQLParameter won't allow me to return an IQueryable from a TVF. Any ideas, am I missing something?

How can I create multiple new instances of a model and save them to the database?

I'm using the following code to attempt to import a CSV file. However it is just saving the last object of Fact rather than saving each of the objects that were built.
Do While Not sr.EndOfStream
Dim aFact as Fact
Dim mArray = sr.ReadLine().Split(",")
aFact.Name = mArray(0)
aFact.Value = mArray(1)
db.Facts.Add(aFact)
End
db.SaveChanges()
Just use a list where you save the object
Dim factList As New List(Of Fact) ' add the list
Do While Not sr.EndOfStream
Dim aFact as Fact
Dim mArray = sr.ReadLine().Split(",")
aFact.Name = mArray(0)
aFact.Value = mArray(1)
factList.Add(aFact) ' put fact object in list
End

Creating a List (of String) giving error" Object reference not set to an instance of an object. "

MVC 3 razor VB.NET project. I have resorted to manual building a list for a drop down box so I can insure certain values are available in the select list and also to control what the first item is in the list. The below is my code snippet for the part that is giving me problems..
Dim _courses1 As Integer = db.courses.ToList.Where(Function(r) r.course_day = "Tuesday").Count
Dim _classes1 As List(Of cours) = db.courses.ToList
Dim classRef1 As List(Of String)
If Not _selectedClass0 = "--" Then
classRef1.Add("--")
Else
classRef1.Add(_selectedClass0)
End If
For i As Integer = 0 To _courses1 - 1
For Each item In _classes1.Where(Function(f) f.course_day = "Tuesday")
Dim _item As cours = item
classRef1.Add(_item.course_ref)
Next
Next
ViewBag.tue1 = classRef1
The _selectedClass0 is just a string that gets set earlier... The error mentioned happens when it gets to the ClassRef1.Add(_selectedClass0) part of the else statement. Which _selectedClass0 string value at the time of error is "--". I have a feeling it is in how the list is being created but I am not certain... Any ideas???
You're not initializing classRef1.
Dim classRef1 As new List(Of String)
Another thing I see is in the first line - I've made the changes I see:
Dim _courses1 As Integer = db.courses.Where(Function(r) r.course_day = "Tuesday").Count()
You don't need ToList at the beginning if all your getting is the count.
You are declaring classRef1 to be a list of strings here:
Dim classRef1 As List(Of String)
But you're never actually creating an instance using New. I'm not sure about the VB syntax, as I'm a C# developer, but I'd guess you should add the following line right after the declaration:
classRef1 = New List(Of String)

Combining extension methods

I'm trying to write 2 extension methods to handle Enum types. One to use the description attribute to give some better explanation to the enum options and a second method to list the enum options and their description to use in a selectlist or some kind of collection.
You can read my code up to now here:
<Extension()> _
Public Function ToDescriptionString(ByVal en As System.Enum) As String
Dim type As Type = en.GetType
Dim entries() As String = en.ToString().Split(","c)
Dim description(entries.Length) As String
For i = 0 To entries.Length - 1
Dim fieldInfo = type.GetField(entries(i).Trim())
Dim attributes() = DirectCast(fieldInfo.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
description(i) = If(attributes.Length > 0, attributes(0).Description, entries(i).Trim())
Next
Return String.Join(", ", description)
End Function
<Extension()> _
Public Function ToListFirstTry(ByVal en As System.Enum) As IEnumerable
Dim type As Type = en.GetType
Dim items = From item In System.Enum.GetValues(type) _
Select New With {.Value = item, .Text = item.ToDescriptionString}
Return items
End Function
<Extension()> _
Public Function ToListSecondTry(ByVal en As System.Enum) As IEnumerable
Dim list As New Dictionary(Of Integer, String)
Dim enumValues As Array = System.Enum.GetValues(en.GetType)
For Each value In enumValues
list.Add(value, value.ToDescriptionString)
Next
Return list
End Function
So my problem is both extension methods don't work that well together. The methods that converts the enum options to an ienumerable can't use the extension method to get the description.
I found all kind of examples to do one of both but never in combination with each other. What am I doing wrong? I still new to these new .NET 3.5 stuff.
The problem is that Enum.GetValues just returns a weakly typed Array.
Try this:
Public Function ToListFirstTry(ByVal en As System.Enum) As IEnumerable
Dim type As Type = en.GetType
Dim items = From item In System.Enum.GetValues(type).Cast(Of Enum)() _
Select New With {.Value = item, .Text = item.ToDescriptionString}
Return items
End Function
(It looks like explicitly typed range variables in VB queries don't mean the same thing as in C#.)

Modeling database records as types

I'm rewriting a C# library in F# in which most of the classes map one-to-one with database tables (similar to ActiveRecord). I'm considering whether to use records or classes (maybe even DUs?). There's a fair amount of validation in the property setters to maintain invariants. What would be the best way to model this in F#? I don't want an object that violates business logic to be persisted to the database. Any ideas are welcome.
A few additional thoughts...
Is it better to move the invariants to an external 'controller' class? Coming from C# it feels wrong to allow an object that corresponds to a database record to contain anything that can't be saved to the database. I suppose because failing earlier seems better than failing later.
You can have your data in a record, and still keep the validation logic with the data type, by attaching methods to the record:
type Person =
{ First : string;
Last : string; } with
member x.IsValid () =
let hasValue = System.String.IsNullOrEmpty >> not
hasValue x.First && hasValue x.Last
let jeff = { First = "Jeff"; Last = "Goldblum" }
let jerry = { jeff with First = "Jerry" }
let broken = { jerry with Last = "" }
let valid = [jeff; jerry; broken]
|> List.filter (fun x -> x.IsValid())
The copy semantics for records are almost as convenient as setting a property. The validation doesn't happen on property set, but it's easy to filter a list of records down to only the valid ones.
This should actually be a good way for you to handle it. Having your validation logic in the constructor will give you piece of mind later on in your code because the object is immutable. This also opens up multi-threading possibilities.
Immutable Version
type Customer (id, name) =
do // Constructor
if id <= 0 then
raise(new ArgumentException("Invalid ID.", "id"))
elif String.IsNullOrEmpty(name) then
raise(new ArgumentException("Invalid Name.", "name"))
member this.ID
with get() = id
member this.Name
with get() = name
member this.ModifyName value =
new Customer(id, value)
Mutable Version
type Customer (id) =
let mutable name = ""
do // Constructor
if id <= 0 then
raise(new ArgumentException("Invalid ID.", "id"))
member this.ID
with get() = id
member this.Name
with get() = name
and set value =
if String.IsNullOrEmpty(name) then
raise(new ArgumentException("Invalid Name.", "value"))
name <- value
Have you taken a look at my FunctionalNHibernate project? It's designed as a layer on top of nhibernate to let you declaratively map records to a database. It's early days, but it's just about usable:
http://bitbucket.org/robertpi/functionalnhibernate/

Resources