Entity Framework : Why this code doesn't work - vb.net

I'm using Entity Framework 6.0, DbContext. I'm using this method to copy an object and some related children:
Imports System.Data.Objects
Imports System.Data.Objects.DataClasses
Imports System.Runtime.CompilerServices
Public Module Entities
<Extension()>
Public Function CloneEntity(Of T As Class)(entity As T, context As ObjectContext, Optional include As List(Of IncludeEntity) = Nothing, Optional copyKeys As Boolean = False) As T
Return CloneEntityHelper(entity, context, include, copyKeys)
End Function
Private Function CloneEntityHelper(Of T As Class)(entity As T, context As ObjectContext, Optional include As List(Of IncludeEntity) = Nothing, Optional copyKeys As Boolean = False) As T
If include Is Nothing Then include = New List(Of IncludeEntity)()
Dim myType = entity.GetType()
Dim methodInfo = context.GetType().GetMethod("CreateObject").MakeGenericMethod(myType)
Dim result = methodInfo.Invoke(context, Nothing)
Dim propertyInfo = entity.GetType().GetProperties()
For Each info In propertyInfo
Dim attributes = info.GetCustomAttributes(GetType(EdmScalarPropertyAttribute), False).ToList()
For Each attr As EdmScalarPropertyAttribute In attributes
If (Not copyKeys) AndAlso attr.EntityKeyProperty
Continue For
End If
info.SetValue(result, info.GetValue(entity, Nothing), Nothing)
Next
If info.PropertyType.Name.Equals("EntityCollection`1", StringComparison.OrdinalIgnoreCase) Then
Dim shouldInclude = include.SingleOrDefault(Function(i) i.Name.Equals(info.Name, StringComparison.OrdinalIgnoreCase))
If shouldInclude Is Nothing Then Continue For
Dim relatedChildren = info.GetValue(entity, Nothing)
Dim propertyType As Type = relatedChildren.GetType().GetGenericArguments().First()
Dim genericType As Type = GetType(EntityCollection(Of ))
Dim boundType = genericType.MakeGenericType(propertyType)
Dim children = Activator.CreateInstance(boundType)
For Each child In relatedChildren
Dim cloneChild = CloneEntityHelper(child, context, shouldInclude.Children, shouldInclude.CopyKeys)
children.Add(cloneChild)
Next
info.SetValue(result, children, Nothing)
End If
Next
Return result
End Function
Public Class IncludeEntity
Public Property Name As String
Public Property Children As New List(Of IncludeEntity)
Public Property CopyKeys As Boolean
Public Sub New(propertyName As String, ParamArray childNodes() As String)
Name = propertyName
Children = childNodes.Select(Function(n) new IncludeEntity(n)).ToList()
End Sub
End Class
End Module
Now I'm using the code like below :
Dim litm, newitm As New MyObject
Dim inc = New List(Of IncludeEntity)()
inc.Add(New IncludeEntity("Child_list"))
litm=context.MyObjects.FirstOrDefault
newitm = litm.CloneEntity(CType(context, Entity.Infrastructure.IObjectContextAdapter).ObjectContext,include:=inc)
The code is executed without errors, but nothing is copied, so newitm is empty.
I have inspected the code and found that this line on the CloneEntity function :
Dim myType = entity.GetType()
Is producing a strange type.
I'm expecting that the type will be of MyObject type, but instead this return :
MyObject_F2FFE64DA472EB2B2BDF7E143DE887D3845AD9D1731FD3107937062AC0C2E4BB
This line too :
Dim result = methodInfo.Invoke(context, Nothing)
produces the same strange type.
I don't know if this is the problem, but this is the only strange thing I have noticed.
Can you help me to find out why this code doesn't work?
Thank you!

Entity framework, like many other ORMs will build a proxy type for your entities so that it can intercept calls to:
Lazy load the contents of any collection contained as properties within your entity, when you access those collection properties.
Detect that you have made changes to the properties of an instance as part of dirty checking, so that it will know which objects are dirty and need to be saved to the database when you invoke SaveChanges.
Refer for example to EF returning proxy class instead of actual entity or Working with Proxies.
If you want to find out the underlying type of your entity that is wrapped by the proxy, i.e. the one that would match with the type you are looking for (e.g. MyObject), you can do that using a method in the object context:
var underlyingType = ObjectContext.GetObjectType(entity.GetType());

Related

.Where method not defined on generic typed list?

When I try to use the .Where() method on a list, this does method does not seem to be defined if the list is of a generic type:
In my program, I have a class called Warning, and in another class, a list of warnings, defined as:
Dim warningList As List(Of Warning)
When I try to manipulate this list as:
Dim item = warningList.Where(Function(x) x.GetName() = "Foo").FirstOrDefault()
This works completely fine, but when I try it like this:
Dim itemList
if(type = "Warning") Then 'Please note that this condition is true...
itemList = warningList
End If
Dim item = itemList.Where(Function(x) x.GetName() = "Foo").FirstOrDefault()
I get an exception, stating that method .Where() is not defined for class Warning
Can anybody tell me why this is?
Thank you!
Now that you've edited your question it's clear.
You declare itemList without a type, so it's Object implicitly(in VB.NET with option strict set to Off which i strongly recommend against).
Now that you have declared a variable of type Object you can asssign any type to it. But you would have to cast it back to its real type List(Of Warning) to be able to use list or LINQ methods(which extend IEnumerable(Of T).
But instead declare it with the correct type:
Dim itemList As List(Of Warning)
if(type = "Warning") Then
itemList = warningList
End If
Dim item = itemList.Where(Function(x) x.GetName() = "Foo").FirstOrDefault()
Including to comment to explain why Warning is not related to this problem:
That's not the real code. If warningList is really a List(Of Warning)
you should be able to use Enumerable.Where(if LINQ is
imported). The fact that you assign this instance to another variable
(on declaration) doesn't change anything because that variable's type
is also a List(Of Warning). So itemList.Where should work too. Warning
has nothing to do with it because the type which is extended by Where
is IEnumerable(Of T), T can be any type(even Object). Since List(Of T)
implements IEnumerable(Of T) you can use Enumerable.Where on any list
(or array).
If you actually have multiple types and Warning is just one of it, you should implement a common interface. Here's an example:
Public Enum NotificationType
Warning
Info
[Error]
End Enum
Public Interface INamedNotification
ReadOnly Property Type As NotificationType
Property Name As string
End Interface
Public Class Warning
Implements INamedNotification
Public Sub New( name As String )
Me.Name = name
End Sub
Public Property Name As String Implements INamedNotification.Name
Public ReadOnly Property Type As NotificationType Implements INamedNotification.Type
Get
Return NotificationType.Warning
End Get
End Property
End Class
Now you can declare a List(Of INamedNotification) and fill it with whatever implements this interface, like the Warning class:
Dim notificationList As List(Of INamedNotification)
if type = "Warning" Then
itemList = warningList
Else If type = "Info"
itemList = infoList
End If
Dim item = notificationList.Where(Function(x) x.Name = "Foo").FirstOrDefault()

Merge two PropertyInfo

I've two structures and one class in my namespace Dimension. These structures are Dimension.Derived and Dimension.Basis. The class was called Exponent. I had overrided the Function ToString() of my class to get the DisplayNameAttribute of properties in my structure Dimension.Derived.
Public Overrides Function ToString() As String
Dim oType As Type
oType = GetType(Dimension.Derived)
Dim colMemberInfo() As PropertyInfo = oType.GetProperties
For Each oMemberInfo In colMemberInfo
If Me = oMemberInfo.GetValue(oMemberInfo) Then
Dim de As New Dimension.Exponent
de = oMemberInfo.GetValue(oType)
Dim attr() As DisplayNameAttribute = DirectCast(oMemberInfo.GetCustomAttributes(GetType(DisplayNameAttribute), False), DisplayNameAttribute())
If attr.Length > 0 Then
Return attr(0).DisplayName
Else
Exit For
End If
End If
Next
Return Nothing
End Function
That works fine, but it should search through both structures. Therefore, I changed the first lines into
Dim oType1, oType2 As Type
oType1 = GetType(Dimension.Derived)
oType2 = GetType(Dimension.Basis)
Dim colMemberInfo() As PropertyInfo = oType1.GetProperties And oType2.GetProperties
But this throws an exception that the And-Operator is not declared for PropertyInfo. Surely I can repeat the For-Each-loop for the another structure, but that's not the intention. What should I do to merge these PropertyInfos?
And is a boolean operator. It's for True/False values. You want to get a list of PropertyInfo from the two types, so try:
Dim properties as List(Of PropertyInfo) = New List(Of PropertyInfo)
properties.AddRange(oType1.GetProperties())
properties.AddRange(oType2.GetProperties())

.net - Using Class as one parameter

I have a class with several properties.
Public Class test
Public Property a As String
Public Property b As String
Public Property c As String
Public Property d As String
Public Property e As String
Public Property f As String
Public Property g As String
End Class
In my VB.net code, I am assigning a value to each property.
I want to send the whole test class as one parameter, and use all the values inside it.
So that if I add extra parameters later on, I want them to be used dynamically, instead of writing this everytime:
Textbox1.text= test.a & test.b & test.c .......
Any way to do this?
Im not really writing the values in a textbox, but this is just an simplified example.
I think what you want is a property. You'll need to add a property to your class like:
Public Property Combination() As String
Get
Return a & b & c & d & e ...
End Get
End Property
Then to get the value you'd use
Textbox1.text = test.combination
(for more details you can see http://www.dotnetperls.com/property-vbnet)
I recommend you override the built-in ToString function. Also, to further simplify this, add a CType operator.
Public Class test
Public Property a As String
Public Property b As String
Public Property c As String
Public Property d As String
Public Property e As String
Public Property f As String
Public Property g As String
Public Shared Widening Operator CType(obj As test) As String
Return If((obj Is Nothing), Nothing, obj.ToString())
End Operator
Public Overrides Function ToString() As String
Return String.Concat(Me.a, Me.b, Me.c, Me.d, Me.e, Me.f, Me.g)
End Function
End Class
The you could just do:
Textbox1.text = test
There is a way to dynamically get and set the value of properties on any object. Such functionality in .NET is collectively referred to as Reflection. For instance, to loop through all of the properties in an object, you could do something like this:
Public Function GetPropertyValues(o As Object) As String
Dim builder As New StringBuilder()
For Each i As PropertyInfo In o.GetType().GetProperties
Dim value As Object = Nothing
If i.CanRead Then
value = i.GetValue(o)
End If
If value IsNot Nothing Then
builder.Append(value.ToString())
End If
Next
Return builder.ToString()
End Function
In the above example, it calls i.GetValue to get the value of the property, but you can also call i.SetValue to set the value of the property. However, reflection is inefficient and, if used inappropriately, it can lead to brittle code. As such, as a general rule, you should avoid using reflection as long as there is any other better way to do the same thing. In other words, you should typically save reflection as a last resort.
Without more details, it's difficult to say for sure what other options would work well in your particular situation, but I strongly suspect that a better solution would be to use a List or Dictionary, for instance:
Dim myList As New List(Of String)()
myList.Add("first")
myList.Add("second")
myList.Add("third")
' ...
For Each i As String In myList
Textbox1.Text &= i
Next
Or:
Dim myDictionary As New Dictionary(Of String, String)()
myDictionary("a") = "first"
myDictionary("b") = "first"
myDictionary("c") = "first"
' ...
For Each i As KeyValuePair(Of String, String) In myDictionary
Textbox1.Text &= i.Value
Next

Difficulties sending a type to a generic list

I'm trying to use a generic list without knowing the type when loading the page. I have a typePropertyCollection which inherits from List(Of PropertyData). The usercontrol that uses this collection doesn't know what type of data is used (which objects). So when the page is loaded, I pass along the type to the usercontrol using a dependencyproperty. This type ends up in this method:
Private Shared Sub OnObjectTypeChanged(ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
Dim objectType As Type = TryCast(args.NewValue, Type)
Dim aList As List(Of PropertyData) = New TypePropertyCollection(Of objectType)
End Sub
I can succesfully retrieve the type from the EventArgs and put it in a variable. When I'm creating a new typePropertyCollection, I want to pass the type to the generic list, but it says the objectType isn't defined, although is is declared just the line above.
Any suggestions?
Edit
The class typepropertyCollection looks like this:
Public Sub New()
Dim properties = New List(Of PropertyInfo)(GetType(T).GetProperties(BindingFlags.Public Or BindingFlags.Instance))
For Each propertyToCheck In properties
Dim descriptionAttribute = propertyToCheck.GetCustomAttributes(GetType(DescriptionAttribute), True)
If Not descriptionAttribute Is Nothing AndAlso descriptionAttribute.Length > 0 Then
Add(New PropertyData() With {.Description = DirectCast(descriptionAttribute(0), DescriptionAttribute).Description, .PropertyName = propertyToCheck.Name})
Else
Add(New PropertyData() With {.Description = propertyToCheck.Name, .PropertyName = propertyToCheck.Name})
End If
Next
End Sub
To use this collection, I'm creating a new class which inherits from the typcollection:
Public Class CustomerTypePropertyCollection
Inherits TypePropertyCollection(Of Person)
End Class
I cannot do this because Person (I named it Person here to make it easier) is not known in that solution. It should also be possible to make collection of other types which or not known. That's why I wanted to pass the type of the object and use it that way.
Dim aList As List(Of PropertyData) = New TypePropertyCollection(Of Type)
This error is because you are attempting to create a TypePropertyCollection of Type 'objectType', objecttype is the variable name of a variable with a type of type .'. You would need a TypePropertyCollection of Type Type Type, not the variable name. Come back to me if there are other issues beyond this.

How do you return an object as the return value through a RealProxy transparent proxy?

I'm working up a system where I plan on using RealProxy objects to enable intercepting method calls against a set of objects, handling the call, and then returning appropriate results.
This works just find for simple return types like strings or ints, but I can't seem to return objects from the RealProxy.Invoke method.
Everything works. I get no errors, but the returned value is always NOTHING, instead of an object.
I've worked up the smallest sample code I could, and have included it below.
Essentially, just call RPtest and single step through.
The code creates a simple object, RPTestA, with a string field and an object valued field
It then retrieves the string
Dim x = c.Name
Which works fine
and then attempts to retrieve the object
Dim r = c.SubObj
Which always returns nothing.
However, in the FieldGetter routine, this code:
'---- the field is an OBJECT type field
Dim mc = New MethodCallMessageWrapper(Msg)
'---- create the object
Dim o = Activator.CreateInstance(t)
'---- and construct the return message with that object
Dim r = New ReturnMessage(o, mc.Args, mc.Args.Length, mc.LogicalCallContext, mc)
Return r
appears to work just fine, setting the ReturnValue field of the ReturnMessage to the object that was created by the Activator.CreateInstance(t) call just above.
I suspect it's a serialization thing of some sort, but I'm at a loss.
You should be able to run this code straight away, but just pasting it into a new VB.net project.
'----------------------------------------------------------------------------
Imports System.Security.Permissions
Imports System.Diagnostics
Imports System.Reflection
Imports System.Runtime.CompilerServices
Imports System.Runtime.Serialization
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Activation
Imports System.Runtime.Remoting.Messaging
Imports System.Runtime.Remoting.Proxies
Public Module RPTest
Public Sub RPTest()
'---- create a new object that is automatically proxied
' See the RPProxyAttribute for details
Dim c = New RPTestA
Dim x = c.Name
'x is returned as a string value just fine
Dim r = c.SubObj
'********* PROBLEM IS HERE, r ends up nothing
End Sub
End Module
'ROOT test object
Public Class RPTestA
Inherits RPBase
Public Name As String = "Test Name"
Public SubObj As RPTestB
End Class
'SUB OBJECT which should be returned as a field value from the root object above
Public Class RPTestB
Inherits RPBase
Public SubProperty As String = "SubObj Test Property"
End Class
''' <summary>
''' Base proxyable object class
''' </summary>
''' <remarks></remarks>
<RPProxy()> _
Public MustInherit Class RPBase
Inherits ContextBoundObject
End Class
<PermissionSet(SecurityAction.Demand, Name:="FullTrust")> _
Public Class RPProxy
Inherits RealProxy
Private m_target As MarshalByRefObject
Public Sub New()
m_target = DirectCast(Activator.CreateInstance(GetType(ConfigRP)), MarshalByRefObject)
Dim myObjRef = RemotingServices.Marshal(m_target)
End Sub
Public Sub New(ByVal classToProxy As Type)
MyBase.New(classToProxy)
End Sub
Public Sub New(ByVal ClassToProxy As Type, ByVal targetObject As MarshalByRefObject)
m_target = targetObject
Dim myObjRef = RemotingServices.Marshal(m_target)
End Sub
Public Overrides Function Invoke(ByVal msg As IMessage) As IMessage
Dim returnMsg As IMethodReturnMessage = Nothing
If TypeOf msg Is IConstructionCallMessage Then
'---- handle constructor calls
Dim ConstructionCallMessage = DirectCast(msg, IConstructionCallMessage)
returnMsg = InitializeServerObject(ConstructionCallMessage)
Me.m_target = Me.GetUnwrappedServer()
SetStubData(Me, Me.m_target)
Return returnMsg
ElseIf TypeOf msg Is IMethodCallMessage Then
'---- handle all other method calls
Dim methodCallMessage = DirectCast(msg, IMethodCallMessage)
'---- before message processing
preprocess(methodCallMessage)
'---- execute the method call
Dim rawReturnMessage = RemotingServices.ExecuteMessage(Me.m_target, methodCallMessage)
'---- and postprocess
returnMsg = postprocess(methodCallMessage, rawReturnMessage)
Else
Throw New NotSupportedException()
End If
Return returnMsg
End Function
'Called BEFORE the actual method is invoked
Private Sub PreProcess(ByVal msg As IMessage)
Console.WriteLine("before method call...")
End Sub
'Called AFTER the actual method is invoked
Private Function PostProcess(ByVal Msg As IMethodCallMessage, ByVal msgReturn As ReturnMessage) As ReturnMessage
Dim r As ReturnMessage
If Msg.MethodName = "FieldGetter" Then
r = FieldGetter(Msg, msgReturn)
ElseIf Msg.MethodName = "FieldSetter" Then
'na
r = msgReturn
ElseIf Msg.MethodName.StartsWith("get_") Then
'na
r = msgReturn
ElseIf Msg.MethodName.StartsWith("set_") Then
'na
r = msgReturn
Else
r = msgReturn
End If
Return r
End Function
Private Function FieldGetter(ByVal Msg As IMethodCallMessage, ByVal msgReturn As IMethodReturnMessage) As IMethodReturnMessage
Dim t = Me.Target.GetType
'---- This retrieves the type of the field that the getter should retrieve
t = t.GetField(Msg.InArgs(1), BindingFlags.Instance Or BindingFlags.Public).FieldType
If t.Name = "String" Then
'---- just return what the object returned as a result of ExecuteMessage
Return msgReturn
ElseIf t.BaseType.Equals(GetType(RPBase)) Then
'---- the field is an OBJECT type field
Dim mc = New MethodCallMessageWrapper(Msg)
'---- create the object
Dim o = Activator.CreateInstance(t)
'---- and construct the return message with that object
Dim r = New ReturnMessage(o, mc.Args, mc.Args.Length, mc.LogicalCallContext, mc)
Return r
Else
Return msgReturn
End If
End Function
Public Property Target() As Object
Get
Return Me.m_target
End Get
Set(ByVal value As Object)
Me.m_target = value
End Set
End Property
End Class
<AttributeUsage(AttributeTargets.Class)> _
<SecurityPermissionAttribute(SecurityAction.Demand, Flags:=SecurityPermissionFlag.Infrastructure)> _
Public Class RPProxyAttribute
Inherits ProxyAttribute
Public Overrides Function CreateInstance(ByVal Type As Type) As MarshalByRefObject
Dim proxy = New RPProxy(Type)
Dim transparentProxy = DirectCast(proxy.GetTransparentProxy(), MarshalByRefObject)
Return transparentProxy
End Function
End Class
Well, it turns out to be a pretty simple fix, once you work past the god awful ReturnMessage constructor that's quite misleading!
Many thanks to an old colleague of mine, Rich Quackenbush, for taking a few minutes and checking this out. Sometimes, you can't see the forest for the trees!
Anyway, in FieldGetter, I was doing this
ElseIf t.BaseType.Equals(GetType(RPBase)) Then
'---- the field is an OBJECT type field
Dim mc = New MethodCallMessageWrapper(Msg)
'---- create the object
Dim o = Activator.CreateInstance(t)
'---- and construct the return message with that object
Dim r = New ReturnMessage(o, mc.Args, mc.Args.Length, mc.LogicalCallContext, mc)
Return r
Seems completely reasonable, that newly created object being passed into the ReturnMessage constructor argument called ReturnValue.
But no. You actually have to create an object array and pass it is as the 3 element in that array, like this:
ElseIf t.BaseType.Equals(GetType(RPBase)) Then
'---- the field is an OBJECT type field
Dim mc = New MethodCallMessageWrapper(Msg) '---- create the object
Dim o = Activator.CreateInstance(t)
'---- and construct the return message with that object
Dim r = New ReturnMessage(Nothing, New Object() {Nothing, Nothing, o}, 3, mc.LogicalCallContext, mc)
Return r
It turns out, this is because the FieldGetter function is what in being "called" and intercepted by the proxy, and it's signature is
FieldGetter(StringtypeName,StringfieldName,Object&val)
Which, for purposes of constructing a ReturnMessage for that call means that it doesn't have a Returnvalue at all, but rather that the return value is returned as the 3'rd argument in that list.
Since I'm not actually calling the real FieldGetter function, the first two argument (the typename and fieldname) are immaterial, but that 3'rd argument is the proper place to put the return value.
It's always obvious in hindsight!
Many thanks to Rich.