Entity Framework errors while saving base class if there is any inheritance - vb.net

I am using EF 6.1. I have a model "Request" built from the wizard directly from my database. In my context file (EMContext.vb) I have
Public Overridable Property Requests As DbSet(Of Request)
When I type in
Dim db As New EMContext
Dim req As New Request()
With req
.RequestedBy = "bar"
.EventName = "Goo"
.RequestedOn = Now
.RequestStatusID = 1
End With
db.Requests.Add(req)
db.SaveChanges()
everything works exactly as expected. No problems. It saves.
However, if I add a class (anywhere in the app)
Class foo
Inherits Request
Public Property s As String
End Class
and then run the exact same code I get
{"An error occurred while updating the entries. See the inner exception for details."}
Looking at the inner exception:
{"Invalid column name 's'.
Invalid column name 'Discriminator'."}
Why in the heck is it even looking at the inherited class properties?
BTW, if I remove all the properties from the inherited class, I still get the Invalid Column 'Discriminator' error.

Then create a custom class that the json parses to and then you can call the Entity and make it from this class.
<Serializable()>
Public Class jSonParsedObject
'properties that match the Entity object
'custom properties you need for other work
End Class
Usage:
Dim jsonObj As jSonParsedObject = SomeMethodThatParsesAndReturnsData()
Dim req As New Request()
With req
.RequestedBy = jsonObj.RequestedBy
.EventName = jsonObj.EventName
.RequestedOn = jsonObj.RequestedOn
.RequestStatusID = jsonObj.RequestStatusID
End With
...

Related

Replacing an object by a deserialized version of it, and preserving references

Say I have an object of my custom class, called AppSettings, which has various properties that hold both value types (integers, doubles, strings, etc.) and reference types (arrays, other custom objects, etc.). Some of these custom objects have their own custom objects, so the path down to some of the value type properties can go very deep.
For example:
<Serializable()>
Public Class AppSettings
Public Property windowHeight As Integer = 600
Public Property windowWidth As Integer = 800
Public Property defaultLengthUnit As Unit = Units.meters
Public Property defaultAngleUnit As Unit = Units.degrees
End Class
Where Unit class is defined as:
<Serializable()>
Public Class Unit
Public Property Name As String
Public Property Abbreviation As String
Public Property Scale As Double
End Class
And Units module is defined as:
Public Module Units
Public meters As New Unit With {
.Name = "Meters",
.Abbreviation = "m.",
.Scale = 1
}
Public degrees As New Unit With {
.Name = "Degrees",
.Abbreviation = "°",
.Scale = 1
}
End Module
Some other code might refer or bind to some of the reference type properties, or their internal properties. Now, let's say I provide a way for the user to save current state of AppSettings by serializing it into XML:
Public Sub SerializeAppSettings(ByVal filename As String)
Using sw As StreamWriter = New StreamWriter(filename)
Dim xmls As XmlSerializer = New XmlSerializer(GetType(AppSettings))
xmls.Serialize(sw, appSettings)
End Using
End Sub
and then load them back (by deserializing) at any time while running the application:
Public Function DeserializeAppSettings(ByVal filename As String) As AppSettings
If Not File.Exists(filename) Then Return Nothing
Using sr As StreamReader = New StreamReader(filename)
Dim xmls As XmlSerializer = New XmlSerializer(GetType(AppSettings))
Return TryCast(xmls.Deserialize(sr), AppSettings)
End Using
End Function
It is called like so:
AppSettings = DeserializeAppSettings(settingsFilePath)
The problem here is that all the references to AppSettings that other objects and bindings have, are now broken, because deserialization replaces the old instance of AppSettings with a completely new instance, and the references are not transferred to it.
It appears that this doesn't break references to value-type properties (like windowHeight, which is Integer), but it definitely breaks references to reference-type properties, like defaultLengthUnit. So for example, if some other object or WPF control is referring/binding to, say, AppSettings.defaultLengthUnit.scaleToBaseUnit, it doesn't work anymore.
I wonder, how can I fix this, so that deserialization would replace the old instance of AppSettings and transfer all the references from it to the new instance that it generated?
As I understand it, there are three ways to go about it:
Replace the old instance with an new one in the exact same memory allocation, with the same internal ID, which would probably be too hacky, and I'm not sure if at all possible.
Another way would be for the DeserializeAppSettings function to overwrite each property value of the current AppSettings instance, one by one, by the deserialized values. However, since some properties of AppSettings are objects, which have their own objects, which have their own objects (and so on), I would basically need to type out all the hierarchy tree in that DeserializeAppSettings function to get down to the value type properties. And every time I would need to add or remove any property in the AppSettings class (or in any class that is used in it's properties), I would also need to manually update the parsing code in DeserializeAppSettings function. This is seriously unmaintainable.
Lastly, it would probably be possible to automate this value replacement through reflection, but reflection is very slow, and generally discouraged if there is any other option.
I hope I am missing something obvious here. Any suggestions on how to transfer all the references to AppSettings when the old instance of it is replaced with a new one through deserialization?
EDIT: Updated the code to include all the relevant classes.

Out of Stack Space Error when setting Custom Object Parameter

I have a custom class module called Service with string parameters.
I instantiate the class by creating an object this_service like so:
Dim this_service As Service
Set this_service = New Service
Then I try to set a parameter to any string value like so:
this_service.Key = "HELLO"
When I run the macro I get the 28 Runtime Error, Out of Stack Space.
In my class module Service I have the following parameter definition and method calls:
Private pKey As String
Public Property Get Key() As String
Key = pKey
End Property
Public Property Let Key(Value As String)
Key = Value
End Property
I can't see any reason why I'd be getting this runtime error?
In Public Property Let it should be:
pKey = Value
Right now it calls the setter recursively (indefinitely).

How should I serialize a vb.NET object which has class attributes with JsonConvert?

I am trying to use Json.NET to serialize an object in vb.NET.
The object fails to serialize correctly. This seems to be because of some attributes on the class.
Below is the class definition – a very simple definition.
Here is the code to serialize:
Dim MyObject As New TestClass() With {.Property1 = "Hello", .Property2 = 3}
Dim Serialized As String = JsonConvert.SerializeObject(MyObject)
After the 2 lines above execute, the variable Serialized has the following value (which is not what I would expect or want):
"Namespace1.TestClass"
When I remove the class attributes completely (just the class attributes, not the property attributes) and then execute the same 2 lines of code, the variable Serialized has the following value (which is what I would expect):
{"Property1":"Hello","Property2":"3"}
This is only an example: we have many such classes with these kinds of attributes. We need to serialize them with Json.NET.
Removing the attributes is not possible, the classes I am dealing with are part of a system of existing applications and WCF based web services (i.e. part of our system needs our current serialization system for WCF kept in place, and another part of our system needs to serialize the same class with Json .. I won't go into the "why" details, just that we are serializing thousands of such objects for database writes and have speed and space isues).
I realize also that I can use JsonTextWriter to serialize, but then we have maintenance issues – every time we add/remove a property we have to remember to maintain the serialization code appropriately.
So how must I serialize this class correctly without removing the attributes?
I have not seen anything on the NewtonSoft site, nor anywhere else, which addresses this specific problem.
Here again is the class definition, along with the attributes.
<System.CodeDom.Compiler.GeneratedCodeAttribute ("System.Xml", "2.0.50727.3053"), _
System.SerializableAttribute(), _
System.Diagnostics.DebuggerStepThroughAttribute(), _
System.ComponentModel.DesignerCategoryAttribute("code"), _
System.Xml.Serialization.XmlTypeAttribute ([Namespace]:="http://Namespace.com/SomePath/SomeXsd.xsd", TypeName:="TestClass"), _
System.ComponentModel.TypeConverterAttribute(GetType (System.ComponentModel.ExpandableObjectConverter))> _
Partial Public Class TestClass
Private _Property1 As String
Private _Property2 As Integer
<System.ComponentModel.Browsable(False), System.Xml.Serialization.XmlIgnoreAttribute()> _
Public Property Property1() As String
Get
Return Me._Property1
End Get
Set(ByVal value As String)
If (Me._Property1 <> value) Then
Me._Property1 = value
End If
End Set
End Property
<System.ComponentModel.Browsable(False), System.Xml.Serialization.XmlIgnoreAttribute()> _
Public Property Property2() As String
Get
Return Me._Property2
End Get
Set(ByVal value As String)
If (Me._Property2 <> value) Then
Me._Property2 = value
End If
End Set
End Property
End Class
The problem is the TypeConverterAttribute on your class. When Json.Net sees that, it will use the associated TypeConverter to convert the object to a string. In this case, it results in the class's type name being output.
You can override the unwanted behavior by adding a JsonObjectAttribute to the classes that have a TypeConverterAttribute applied. But since it appears that your classes are generated code, it might not be feasible to do that on an class-by-class basis unless you can modify the code generator. In that case, another alternative is to use a custom IContractResolver to force Json.Net to ignore the TypeConverter on classes that have it.
Here is the code you would need for the resolver:
Class TypeConverterIgnoringResolver
Inherits DefaultContractResolver
Protected Overrides Function CreateContract(objectType As Type) As JsonContract
If objectType.GetCustomAttributes(True) _
.OfType(Of System.ComponentModel.TypeConverterAttribute)() _
.Any() _
Then
Return MyBase.CreateObjectContract(objectType)
End If
Return MyBase.CreateContract(objectType)
End Function
End Class
You can use the resolver like this:
Dim MyObject As New TestClass() With {.Property1 = "Hello", .Property2 = 3}
Dim Settings As New JsonSerializerSettings
Settings.ContractResolver = New TypeConverterIgnoringResolver()
Dim Serialized As String = JsonConvert.SerializeObject(MyObject, Settings)
Console.WriteLine(Serialized)
Fiddle: https://dotnetfiddle.net/s6Ebmc
I guess you don't want to change your TestClass as it must have been generated by some tool, I would suggest to derive a new class from it:
Imports Newtonsoft.Json
<JsonObject()>
Public Class OtherClass
Inherits TestClass
End Class
and use the attribute <JsonObject()>. This should do the trick:
Dim MyObject As New OtherClass() With {.Property1 = "Hello", .Property2 = 3}
Dim Serialized As String = JsonConvert.SerializeObject(MyObject)
UPDATE:
Since you're already working with a Partial you can extend it creating a new one (in another folder):
Imports Newtonsoft.Json
<JsonObject()>
Partial Public Class TestClass
End Class

Why is this Entity Framework association not loading lazily?

I'm using a Code First Entity Framework approach, and in my OnModelCreating function I have the following code:
With modelBuilder.Entity(Of FS_Item)()
.HasKey(Function(e) e.ItemKey)
.Property(Function(e) e.ItemRowVersion).IsConcurrencyToken()
.HasMany(Function(e) e.ItemInventories) _
.WithRequired(Function(e) e.Item).HasForeignKey(Function(e) e.ItemKey)
End With
Elsewhere I have a Web API Get implementation with some diagnostic code I'm looking at in a debugger:
Public Function GetValue(ByVal id As String) As FS_Item
GetValue = If(data.FS_Item.Where(Function(i) i.ItemNumber = id).SingleOrDefault(), New FS_Item())
Dim c = GetValue.ItemInventories.Count
End Function
I expect that c should get a non-zero value by looking up rows in the FS_Inventory view where ItemKey matches the retrieved FS_Item row's ItemKey. But I'm getting 0 even though there are matching rows. Am I calling .HasMany, .WithRequired and .HasForeignKey properly?
Note that .WithRequired is operating on the return value from the previous line whereas the other lines are operating on the With block expression.
Edit This model for FS_Item has been requested. Here it is:
Partial Public Class FS_Item
Public Property ItemNumber As String
Public Property ItemDescription As String
Public Property ItemUM As String
Public Property ItemRevision As String
Public Property MakeBuyCode As String
' Many many more properties
Public Property ItemRowVersion As Byte()
Public Property ItemKey As Integer
Private _ItemInventories As ICollection(Of FS_ItemInventory) = New HashSet(Of FS_ItemInventory)
Public Overridable Property ItemInventories As ICollection(Of FS_ItemInventory)
Get
Return _ItemInventories
End Get
Friend Set(value As ICollection(Of FS_ItemInventory))
_ItemInventories = value
End Set
End Property
End Class
Edit Learned something interesting. If I change Dim c = GetValue.ItemInventories.Count to this:
Dim c = data.FS_ItemInventory.ToList()
Dim correctCount = GetValue.ItemInventories.Count
Then correctCount gets the value of 3. It's like it understands the association between the objects, but not how to automatically query them as I'm used to coming from LINQ-to-SQL. Is EF different somehow in this regard?
Edit I have determined that I can make the associated objects load using this explicit loading code:
data.Entry(GetValue).Collection(Function(e) e.ItemInventories).Load()
What I want to understand now is what exactly determines whether an entity will load lazily or not? From all indications I can find, it should have loaded lazily. I even tried changing the declaration of ItemInventories to this, but then I got a NullReferenceException when trying to access it:
Public Overridable Property ItemInventories As ICollection(Of FS_ItemInventory)
It turns out that code which I thought was unrelated had disabled lazy loading. I have this in the constructor of FSDB:
DirectCast(Me, IObjectContextAdapter).ObjectContext.ContextOptions.ProxyCreationEnabled = False
Thanks to EF 4 - Lazy Loading Without Proxies I see that this will also disable lazy loading. The reason that code had been added was due to another error:
Type
'System.Data.Entity.DynamicProxies.FS_Item_64115A45C642902D6044AFA1AFD239E7DCB82FD000A10FE4F8DE6EA26A2AB418'
with data contract name
'FS_Item_64115A45C642902D6044AFA1AFD239E7DCB82FD000A10FE4F8DE6EA26A2AB418:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies'
is not expected. Consider using a DataContractResolver or add any
types not known statically to the list of known types - for example,
by using the KnownTypeAttribute attribute or by adding them to the
list of known types passed to DataContractSerializer.
And according to Serialization of Entity Framework objects with One to Many Relationship, the easy solution for that was to disable proxies.

Only first DataAnnotation validation being applied

I'm currently working on a Winforms application written in VB.NET and implementing the Entity Framework (4.4). I want to add validation attributes to my entities so that I can validate them on the UI - just as I do in MVC.
I have created my 'Buddy Class' which contains an IsValid method and points to a 'MetaData' class that contains the data annotations.
Imports System.ComponentModel.DataAnnotations
Imports System.Runtime.Serialization
Imports System.ComponentModel
<MetadataTypeAttribute(GetType(ProductMetadata))>
Public Class Product
Private _validationResults As New List(Of ValidationResult)
Public ReadOnly Property ValidationResults() As List(Of ValidationResult)
Get
Return _validationResults
End Get
End Property
Public Function IsValid() As Boolean
TypeDescriptor.AddProviderTransparent(New AssociatedMetadataTypeTypeDescriptionProvider(GetType(Product), GetType(ProductMetadata)), GetType(Product))
Dim result As Boolean = True
Dim context = New ValidationContext(Me, Nothing, Nothing)
Dim validation = Validator.TryValidateObject(Me, context, _validationResults)
If Not validation Then
result = False
End If
Return result
End Function
End Class
Friend NotInheritable Class ProductMetadata
<Required(ErrorMessage:="Product Name is Required", AllowEmptyStrings:=False)>
<MaxLength(50, ErrorMessage:="Too Long")>
Public Property ProductName() As Global.System.String
<Required(ErrorMessage:="Description is Required")>
<MinLength(20, ErrorMessage:="Description must be at least 20 characters")>
<MaxLength(60, ErrorMessage:="Description must not exceed 60 characters")>
Public Property ShortDescription As Global.System.String
<Required(ErrorMessage:="Notes are Required")>
<MinLength(20, ErrorMessage:="Notes must be at least 20 characters")>
<MaxLength(1000, ErrorMessage:="Notes must not exceed 1000 characters")>
Public Property Notes As Global.System.String
End Class
The first line in the IsValid method registers the MetaData class (only way I could find that actually worked - otherwise no annotations were honored!). I then use the System.ComponentModel.Validator.TryValidateObject method to perform the validation.
When I call the IsValid method on an instance with an empty (null/nothing) ProductName the validation fails and the ValidationResults collection is populated with the correct error message. So far so good.....
However, if I call IsValid on an instance with a ProductName which is longer than 50 characters the validation passes despite the MaxLength attribute!
Also, if I call IsValid on an instance with a valid ProductName (not empty and not more than 50 characters) but without a ShortDescription the validation passes even though there is a Required annotation on that property.
What am I doing wrong here?
Try the other method signature for TryValidateObject() and explicitly set validateAllProperties to true:
Dim validation = Validator.TryValidateObject(
Me, context, _validationResults, true)