OK, this worked just fine in VS 2013. It's only when I started work anew on the project after my upgrade to 2015 that the problem has showed up.
In a nutshell, I'm unsure how to tell the WCF Proxy Generator to specify a CLR namespace for a property type; apparently this is required now.
Here's my contract:
<ServiceContract>
Friend Interface IService
<OperationContract> Function CheckFiles() As List(Of String)
<OperationContract> Function CreateBackup(AllFiles As List(Of String)) As BackupResult
End Interface
Here's the class being returned:
Public Class BackupResult
Public Property DbService As New DbService
Public Property TmpFolder As System.IO.DirectoryInfo ' <== Problem here '
Public Property Chunks As Integer
End Class
And just for clarity, here's the class for the DbService property (although its only relevance for this question is to show that it doesn't have any System.IO references).
Public Class DbService
Public Property ErrorMessage As String = String.Empty
Public Property HasError As Boolean = False
End Class
My problem is that the proxy generator doesn't seem to be able to see that DirectoryInfo is in the System.IO namespace—it keeps generating it in the service's namespace. (When I comment out the CreateBackup() function, rerun the service and update the reference, the QbBackup.DirectoryInfo class isn't generated. I don't get the warning shown below and everything works—like it did in 2013—but of course without the property I need.)
Here's the generated code:
Namespace QbServer
' ... '
' '
' Other generated code here '
' '
' ... '
' '
' Note the generated DirectoryInfo class and '
' the BackupResult.TmpFolder property of type '
' QbServer.DirectoryInfo, when the namespace '
' should be System.IO instead '
' '
<System.Diagnostics.DebuggerStepThroughAttribute(),
System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0"),
System.Runtime.Serialization.DataContractAttribute(Name:="BackupResult", [Namespace]:="http://schemas.datacontract.org/2004/07/Service"),
System.SerializableAttribute()>
Partial Public Class BackupResult
Inherits Object
Implements System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged
<System.NonSerializedAttribute()>
Private extensionDataField As System.Runtime.Serialization.ExtensionDataObject
<System.Runtime.Serialization.OptionalFieldAttribute()>
Private ChunksField As Integer
<System.Runtime.Serialization.OptionalFieldAttribute()>
Private DbServiceField As QbServer.DbService
<System.Runtime.Serialization.OptionalFieldAttribute()>
Private TmpFolderField As QbServer.DirectoryInfo
<Global.System.ComponentModel.BrowsableAttribute(False)>
Public Property ExtensionData() As System.Runtime.Serialization.ExtensionDataObject Implements System.Runtime.Serialization.IExtensibleDataObject.ExtensionData
Get
Return Me.extensionDataField
End Get
Set
Me.extensionDataField = Value
End Set
End Property
<System.Runtime.Serialization.DataMemberAttribute()>
Public Property Chunks() As Integer
Get
Return Me.ChunksField
End Get
Set
If (Me.ChunksField.Equals(Value) <> True) Then
Me.ChunksField = Value
Me.RaisePropertyChanged("Chunks")
End If
End Set
End Property
<System.Runtime.Serialization.DataMemberAttribute()>
Public Property DbService() As QbServer.DbService
Get
Return Me.DbServiceField
End Get
Set
If (Object.ReferenceEquals(Me.DbServiceField, Value) <> True) Then
Me.DbServiceField = Value
Me.RaisePropertyChanged("DbService")
End If
End Set
End Property
<System.Runtime.Serialization.DataMemberAttribute()>
Public Property TmpFolder() As QbServer.DirectoryInfo
Get
Return Me.TmpFolderField
End Get
Set
If (Object.ReferenceEquals(Me.TmpFolderField, Value) <> True) Then
Me.TmpFolderField = Value
Me.RaisePropertyChanged("TmpFolder")
End If
End Set
End Property
Public Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Protected Sub RaisePropertyChanged(ByVal propertyName As String)
Dim propertyChanged As System.ComponentModel.PropertyChangedEventHandler = Me.PropertyChangedEvent
If (Not (propertyChanged) Is Nothing) Then
propertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(propertyName))
End If
End Sub
End Class
<System.Diagnostics.DebuggerStepThroughAttribute(),
System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")>
Public Class DirectoryInfo
End Class
End Namespace
And here's the warning I'm getting in Visual Studio 2015:
Custom tool warning: Cannot import wsdl:portType
Detail: An exception was thrown while running a WSDL import extension: System.ServiceModel.Description.DataContractSerializerMessageContractImporter
Error: ISerializable type with data contract name 'DirectoryInfo' in namespace 'http://schemas.datacontract.org/2004/07/System.IO' cannot be imported. The data contract namespace cannot be customized for ISerializable types and the generated namespace 'QbServer' does not match the required CLR namespace 'System.IO'. Check if the required namespace has been mapped to a different data contract namespace and consider mapping it explicitly using the namespaces collection.
XPath to Error Source: //wsdl:definitions[#targetNamespace='http://tempuri.org/']/wsdl:portType[#name='IService'] ConsoleTest D:\Dev\Customers\OIT\Active\ConsoleTest\Service References\QbServer\Reference.svcmap 1
This all results in the proxy classes not being generated.
I've been reading this and this, but they seem to pertain to custom namespaces at the service level. I need to know how to tell the generator to recognize the property type as a CLR type and NOT generate a DirectoryInfo class of its own.
The class System.IO.DirectoryInfo is not supported by the DataContractSerializer. Instead you could try using the XmlSerializer, but you'll likely run into other issues.
A simple solution is to add a string property which captures the data needed to recreate the correct objects. You can keep the original property as well, just be sure to mark it with the [NonSerialized] attribute.
Alternatively you can use the OnSerializing and OnDeserializing attributes to ensure that the DirectoryInfo value is stored in the string field and so that the DirectoryInfo is restored after deserialization.
For more information see:
https://blogs.msdn.microsoft.com/carlosfigueira/2011/09/05/wcf-extensibility-serialization-callbacks/
Related
I have the next Code in EF Core 3.1 in language VB.NET
Dim supplierID as string="1545464"
Dim results = (From pa In DC.product.AsNoTracking()
Where pa.supplierID = supplierID
Select pa)
The exception throw is:
The LINQ expression 'DbSet<product>
.Where(p => Operators.CompareString(
Left: p.supplierID,
Right: __$VB$Local_supplierID_0,
TextCompare: False) == 0)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
I found the following solution:
Dim supplierID as string="1545464"
Dim results = (From pa In DC.product.AsNoTracking()
Where pa.supplierID.Equals(supplierID)
Select pa)
Is my solution correct, using .Equals()? In C# language if it works with the operator "=="
I have created a small solution with which you can reproduce the error.
The solution has 4 projects:
Sup.Entities (C#)
Sup.DAL (C#)
Sup.ConsoleApp1 (C#)
Sup.consoleAppVB (VB.NET)
This is the error that occurs in Sup.ConsoleAppVB (VB.NET)
This is the result in Sup.ConsoleApp1 (C#)
Attached solution Download that includes projects and an SQL file to create the database and 1 table with 3 rows.
Please change connectionstring for UseSqlServer("...") in OnConfiguring Context
I just ran into this issue but since I have developed my own LINQ to SQL evaluator before I knew how to solve the problem. VB.NET transforms the = operator for strings into a call to Microsoft.VisualBasic.CompilerServices.Operators.CompareString(). So when the expression tree is evaluated this method must be handled. I suspect the reason for this is because VB handles string comparisons to null ((text = Nothing) = True).
I didn't download your sample but I fixed it in an ASP.NET Core application.
If you were using LINQ, this would be handled inside an ExpressionVisitor but for Entity Framework Core 3.1, I found that you can implement an IMethodCallTranslator.
However, Entity Framework uses SqlExpression expressions so an ISqlExpressionFactory is needed to create them. Fortunately, dependency injection can be used to get an implementation from IServiceCollection.
Public Class VbCompareStringMethodCallTranslator : Implements IMethodCallTranslator
Private mExpressionFactory As ISqlExpressionFactory
Public Sub New(expressionFactory As ISqlExpressionFactory)
Me.mExpressionFactory = expressionFactory
End Sub
Public Function Translate(instance As SqlExpression, method As MethodInfo, arguments As IReadOnlyList(Of SqlExpression)) As SqlExpression Implements IMethodCallTranslator.Translate
If method IsNot Nothing Then
If method.Name = "CompareString" AndAlso method.DeclaringType?.Name = "Operators" AndAlso
method.DeclaringType?.Namespace = "Microsoft.VisualBasic.CompilerServices" Then
Dim left = arguments(0)
Dim right = arguments(1)
If method.Name Is NameOf(String.Compare) AndAlso arguments.Count = 2 AndAlso
arguments(0).Type.UnwrapNullableType Is arguments(1).Type.UnwrapNullableType Then
left = arguments(0)
right = arguments(1)
ElseIf method.Name Is NameOf(String.CompareTo) AndAlso arguments.Count = 1 AndAlso
instance IsNot Nothing AndAlso instance.Type.UnwrapNullableType Is arguments(0).Type.UnwrapNullableType Then
left = instance
right = arguments(0)
End If
If left IsNot Nothing AndAlso right IsNot Nothing Then
Return Me.mExpressionFactory.[Case]({New CaseWhenClause(Me.mExpressionFactory.Equal(left, right), Me.mExpressionFactory.Constant(0)),
New CaseWhenClause(Me.mExpressionFactory.GreaterThan(left, right), Me.mExpressionFactory.Constant(1)),
New CaseWhenClause(Me.mExpressionFactory.LessThan(left, right), Me.mExpressionFactory.Constant(-1))},
Nothing)
End If
End If
End If
Return Nothing
End Function
End Class
Making use of the following extension method
Public Module SharedTypeExtensions
<Extension()>
Public Function UnwrapNullableType(type As Type) As Type
Return If(Nullable.GetUnderlyingType(type), type)
End Function
End Module
You can see that this is the code used by Entity Framework to handle string comparisons here https://github.com/dotnet/efcore/blob/3656e9daa9b81398d8c065a702fd5dca91979f49/src/EFCore.Relational/Query/Internal/ComparisonTranslator.cs
So now this needs to be hooked up and the following plumbing code can be used
Public Class VbMethodCallTranslatorPlugin : Implements IMethodCallTranslatorPlugin
Public Sub New(expressionFactory As ISqlExpressionFactory)
Me.Translators = {New VbCompareStringMethodCallTranslator(expressionFactory)}
End Sub
Public ReadOnly Property Translators As IEnumerable(Of IMethodCallTranslator) Implements IMethodCallTranslatorPlugin.Translators
End Class
Public Class VbDbContextOptionsExtension : Implements IDbContextOptionsExtension
Public Sub ApplyServices(services As IServiceCollection) Implements IDbContextOptionsExtension.ApplyServices
services.AddSingleton(Of IMethodCallTranslatorPlugin, VbMethodCallTranslatorPlugin)
End Sub
Public Sub Validate(options As IDbContextOptions) Implements IDbContextOptionsExtension.Validate
End Sub
Public ReadOnly Property Info As DbContextOptionsExtensionInfo Implements IDbContextOptionsExtension.Info
Get
Return New VbDbContextOptionsExtensionInfo(Me)
End Get
End Property
End Class
Public Class VbDbContextOptionsExtensionInfo : Inherits DbContextOptionsExtensionInfo
Public Sub New(extension As IDbContextOptionsExtension)
MyBase.New(extension)
End Sub
Public Overrides Function GetServiceProviderHashCode() As Long
Return Me.Extension.GetHashCode
End Function
Public Overrides Sub PopulateDebugInfo(<NotNullAttribute> debugInfo As IDictionary(Of String, String))
debugInfo("VB:TranslateMethods") = True.ToString
End Sub
Public Overrides ReadOnly Property IsDatabaseProvider As Boolean
Get
Return False
End Get
End Property
Public Overrides ReadOnly Property LogFragment As String
Get
Return "VbMethodSupport=true"
End Get
End Property
End Class
Now this can hooked up using the DbContextOptionsBuilder, but the following extension method will make this easier
Public Module VbDbContextOptionsBuilderExtensions
<Extension>
Public Function AddVbSupport(optionsBuilder As DbContextOptionsBuilder) As DbContextOptionsBuilder
Dim builder = CType(optionsBuilder, IDbContextOptionsBuilderInfrastructure)
Dim extension = If(optionsBuilder.Options.FindExtension(Of VbDbContextOptionsExtension), New VbDbContextOptionsExtension)
builder.AddOrUpdateExtension(extension)
Return optionsBuilder
End Function
End Module
Now you can hook this up while setting up your DbContext
services.AddDbContext(Of ApplicationDbContext)(Sub(options)
options.UseSqlServer(Me.Configuration.GetConnectionString("ConnectionString"),
Sub(dbOptions)
dbOptions.MigrationsAssembly("Database.Migrations")
End Sub)
options.AddVbSupport
End Sub)
Additional Info
This appears to be a bug in Entity Framework rather than VB.NET just not being supported. You can find this code in the dotnet efcore repository.
https://github.com/dotnet/efcore/blob/7cb52b388a2d9fd8f9c2c499ef3ffb9753d9932a/src/EFCore/Query/Internal/QueryOptimizingExpressionVisitor.cs#L113-L132
I submitted a bug report here
https://github.com/dotnet/efcore/issues/20889
Vote it up so the devs will fix the issue!
Update 1
Looks like this will be fixed in .NET 5
Update 2
The above solution was causing issues after refreshing the page a bunch of times. I would get an error something to the effect of "more than 20 IService instances have been created"
In order to fix this I just added the expression transform into a different part of the pipeline.
Imports System.Linq.Expressions
Imports System.Runtime.CompilerServices
Imports Microsoft.EntityFrameworkCore
Imports Microsoft.EntityFrameworkCore.Query
Public Class VbRelationalQueryTranslationPreprocessorFactory : Implements IQueryTranslationPreprocessorFactory
Private ReadOnly mDependencies As QueryTranslationPreprocessorDependencies
Private ReadOnly mRelationalDependencies As RelationalQueryTranslationPreprocessorDependencies
Public Sub New(dependencies As QueryTranslationPreprocessorDependencies, relationalDependencies As RelationalQueryTranslationPreprocessorDependencies)
Me.mDependencies = dependencies
Me.mRelationalDependencies = relationalDependencies
End Sub
Public Overridable Function Create(queryCompilationContext As QueryCompilationContext) As QueryTranslationPreprocessor Implements IQueryTranslationPreprocessorFactory.Create
Return New VbRelationalQueryTranslationPreprocessor(Me.mDependencies, Me.mRelationalDependencies, queryCompilationContext)
End Function
End Class
Public Class VbRelationalQueryTranslationPreprocessor : Inherits RelationalQueryTranslationPreprocessor
Public Sub New(dependencies As QueryTranslationPreprocessorDependencies, relationalDependencies As RelationalQueryTranslationPreprocessorDependencies, queryCompilationContext As QueryCompilationContext)
MyBase.New(dependencies, relationalDependencies, queryCompilationContext)
End Sub
Public Overrides Function Process(query As Expression) As Expression
query = New LanguageNormalizingExpressionVisitor().Visit(query)
Return MyBase.Process(query)
End Function
End Class
Public Class LanguageNormalizingExpressionVisitor : Inherits ExpressionVisitor
Protected Overrides Function VisitBinary(node As BinaryExpression) As Expression
Dim methodCall = TryCast(node.Left, MethodCallExpression)
If methodCall IsNot Nothing Then
' Replace calls to comparestring with a binary equals on the operands
If methodCall.Method.Name = "CompareString" AndAlso methodCall.Method.DeclaringType?.Name = "Operators" AndAlso methodCall.Method.DeclaringType?.Namespace = "Microsoft.VisualBasic.CompilerServices" Then
Dim left = Me.Visit(methodCall.Arguments(0))
Dim right = Me.Visit(methodCall.Arguments(1))
Return Expression.MakeBinary(node.NodeType, left, right)
End If
End If
Return MyBase.VisitBinary(node)
End Function
End Class
Public Module VbDbContextOptionsBuilderExtensions
<Extension>
Public Function AddVbSupport(optionsBuilder As DbContextOptionsBuilder) As DbContextOptionsBuilder
optionsBuilder.ReplaceService(Of IQueryTranslationPreprocessorFactory, VbRelationalQueryTranslationPreprocessorFactory)()
Return optionsBuilder
End Function
End Module
I'm trying to teach myself reflection and have been googling but I can't wrap my head around it entirely. I created a class called DataClass which contains a method called GetClassFromDB as you can see below, which will be inherited from multiple classes.
What I am attempting to do is have my dataclass read the TableName property that is defined within objResults. Once I pull in the tablename from objResults I would query the SQL database for a dataset. Once I have the dataset I would create a new object of the same TYPE inheriting this class (Which will be different types) and populate it from the dataset. Once I have the newly populated class I will return it for use.
I believe I have gotten most of the way there properly (Please correct me if there is a better way), but my real question is this. How can I create a new class of the type thats deriving that class from that string name that I getting in my code, or the type. I would want to have all the accessible properties from objResults available.
Namespace MyApp
Public Class DataClass
Private _TableName As String
Private _Name As String
Overridable ReadOnly Property TableName As String
Get
Return _TableName
End Get
End Property
Public Overloads Function GetClassFromDB() As Object
Try
Dim BaseObject As New Object
'Get the object name
Dim objName As String = MyBase.GetType().Name
'Gets the type thats calling this method
Dim objDerived As Type = MyBase.GetType()
'Get the property info to request the tablename from the derived class
Dim TableName As PropertyInfo = objDerived.GetProperty("TableName")
Dim TableNameString As String = TableName.GetValue(Me, Nothing).ToString
'Once I get the table name from objResults I can perform the SQL
Dim QueryResults as DataSet = SQLiteCLass.Query("Select * FROM TableNameString")
'Once I get data from the SQL I want to create a new object of the type deriving this method.
'In this example is objResults
Dim NewObject as objDerived
'Now I can fill my new object with the results and return it as an object
'THIS IS MY QUESTION - How can I create a new object of the TYPE that I receive from Reflection
Return False
Catch ex As Exception
Return False
End Try
End Function
End Class
End Namespace
and this is a sample class that would inherit my dataclass.
Public Class objResults
Inherits MyApp.DataClass
Private _GameID As Guid
Public Property GameID As Guid
Get
Return _GameID
End Get
Set(ByVal value As Guid)
_GameID = value
End Set
End Property
Public Overrides ReadOnly Property TableName As String
Get
Return "This is my tablename"
End Get
End Property
End Class
and this is how I would use this in code.
Dim objResult as New objResults
Dim TodaysResult as objResultsCollection
TodaysResult = objResult.GetClassFromDB()
VB.net allows you to skip the qualification of a function call with the module name:
Public Module EvalDataFetcher
Public Function JoinStr(ByVal values As IEnumerable(Of String)) As String
' body
End Function
End Module
And then do:
Dim foo As String = JoinStr(myBars)
How to force the users to use the fully qualified form? ie force:
Dim foo As String = EvalDataFetcher.JoinStr(myBars)
If there is a way to force you to specify the module name, I'm not sure what it would be. However, the way you ca do it is to make it a class with shared members rather than a module. For instance:
Public Class EvalDataFetcher
Public Shared Function JoinStr(ByVal values As IEnumerable(Of String)) As String
' body
End Function
End Module
Now, when you call the JoinStr method, you will be forced to specify the class name:
Dim foo1 As String = JoinStr(myBars) ' Won't compile
Dim foo2 As String = EvalDataFetcher.JoinStr(myBars) ' Works
This behavior can be achieved with HideModuleName attribute as workaround - HideModuleNameAttribute
Wrap your module with namespace and add HideModuleName attribute to the module
Namespace Utils
<HideModuleName()>
Friend Module UtilsModule
Public Sub YourMethod(parameter As Object)
'Method code
End Sub
End Module
End Namespace
If namespace will not be added with the Imports in the file, then
you will need to use namespace name and HideModuleName attribute will hide module name from the intellisense
Utils.YourMethod(param)
I'm having a heck of a time trying to add fields like department and title.
I'm using this to create a user account:
Dim ctx As New PrincipalContext(ContextType.Domain, "domain.name.pvt", "OU=Users,DC=global,DC=pvt")
Dim usr As UserPrincipal = New UserPrincipal(ctx)
I have no problem creating the account but can't add simple things like Department and Title. I read about using extensions but its in C++ and have no clue on how to do it.
Any help would be great!!! Thanks!
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
To extend the UserPrincipal class, you don't need much - something like this will suffice (I wrote this in C# originally and just converted it to VB.NET on the 'net - I hope there's no issues with the VB.NET code!)
Imports System.DirectoryServices.AccountManagement
Namespace ADExtended
<DirectoryRdnPrefix("CN")> _
<DirectoryObjectClass("Person")> _
Public Class UserPrincipalEx
Inherits UserPrincipal
' Inplement the constructor using the base class constructor.
Public Sub New(context As PrincipalContext)
MyBase.New(context)
End Sub
' Implement the constructor with initialization parameters.
Public Sub New(context As PrincipalContext, samAccountName As String, password As String, enabled As Boolean)
MyBase.New(context, samAccountName, password, enabled)
End Sub
' Create the "Department" property.
<DirectoryProperty("department")> _
Public Property Department() As String
Get
If ExtensionGet("department").Length <> 1 Then
Return String.Empty
End If
Return DirectCast(ExtensionGet("department")(0), String)
End Get
Set
ExtensionSet("department", value)
End Set
End Property
' Create the "Title" property.
<DirectoryProperty("title")> _
Public Property Title() As String
Get
If ExtensionGet("title").Length <> 1 Then
Return String.Empty
End If
Return DirectCast(ExtensionGet("title")(0), String)
End Get
Set
ExtensionSet("title", value)
End Set
End Property
' Implement the overloaded search method FindByIdentity.
Public Shared Shadows Function FindByIdentity(context As PrincipalContext, identityValue As String) As UserPrincipalEx
Return DirectCast(FindByIdentityWithType(context, GetType(UserPrincipalEx), identityValue), UserPrincipalEx)
End Function
' Implement the overloaded search method FindByIdentity.
Public Shared Shadows Function FindByIdentity(context As PrincipalContext, identityType As IdentityType, identityValue As String) As UserPrincipalEx
Return DirectCast(FindByIdentityWithType(context, GetType(UserPrincipalEx), identityType, identityValue), UserPrincipalEx)
End Function
End Class
End Namespace
Now, you just use the UserPrincipalEx class:
Dim ctx As New PrincipalContext(ContextType.Domain, "domain.name.pvt", "OU=Users,DC=global,DC=pvt")
Dim usr As UserPrincipalEx = New UserPrincipalEx(ctx)
usr.Title = "......."
usr.Department = "......."
The new S.DS.AM makes it really easy to play around with users and groups in AD!
I have a POCO object and I want to mark a property as a key (see TestClass below).
I'm getting the following error.
'Key' cannot be used as an attribute because it is not a class. C:\Users\zzz\Documents\Visual Studio 2010\Projects\zzz\zzz\BO\TestClass.vb
Public Class TestClass
<Key()>
Private _TestIdentifier As String
Public Property TestIdentifier() As String
Get
Return _TestIdentifier
End Get
Set(ByVal value As String)
_TestIdentifier = value
' Me.NotifyPropertyChanged("TestIdentifier")
End Set
End Property
End Class
I had a missing import and reference
Imports System.ComponentModel.DataAnnotations
And reference System.ComponentModel.DataAnnotations.dll