DEV Community

Simon Foster
Simon Foster

Posted on • Originally published at funkysi1701.com on

Upgrading to .Net 7

I have a Blazor application that was built with .Net Core 3, and I have updated it to .Net 5 and later .Net 6 when those versions of were released. In November .Net 7 is released so I have been testing it with the .Net 7 previews.

However for this update I encountered a few more issues than I remember from the other updates.

Automapper

The first issue I encountered was with Automapper. My Unit tests failed with the following error

Error Message:
   System.ArgumentException : GenericArguments[0], 'System.Char', on 'T MaxFloat[T](System.Collections.Generic.IEnumerable`1[T])' violates the constraint of type 'T'.
---- System.Security.VerificationException : Method System.Linq.Enumerable.MaxFloat: type argument 'System.Char' violates the constraint of type parameter 'T'.
  Stack Trace:
     at System.RuntimeType.ValidateGenericArguments(MemberInfo definition, RuntimeType[] genericArguments, Exception e)
   at System.Reflection.RuntimeMethodInfo.MakeGenericMethod(Type[] methodInstantiation)
   at AutoMapper.Internal.TypeDetails.<>c __DisplayClass25_1.<GetPublicNoArgExtensionMethods>b__ 10(MethodInfo extensionMethod)
   at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
   at System.Linq.Enumerable.ConcatIterator`1.MoveNext()
   at System.Linq.Enumerable.SelectManyIterator[TSource,TCollection,TResult](IEnumerable`1 source, Func`2 collectionSelector, Func`3 resultSelector)+MoveNext()
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at System.Linq.Enumerable.UnionIterator`1.GetNext()
   at System.Linq.Enumerable.UnionIterator`1.MoveNext()
   at System.Linq.Enumerable.ConcatIterator`1.MoveNext()
   at AutoMapper.Internal.TypeDetails.PossibleNames()
   at AutoMapper.Internal.TypeDetails.GetMember(String name)
   at AutoMapper.Configuration.Conventions.DefaultName.GetMatchingMemberInfo(TypeDetails sourceTypeDetails, Type destType, Type destMemberType, String nameToSearch)
   at AutoMapper.Configuration.Conventions.ParentSourceToDestinationNameMapper.GetMatchingMemberInfo(TypeDetails sourceTypeDetails, Type destType, Type destMemberType, String nameToSearch)
   at AutoMapper.Configuration.Conventions.DefaultMember.MapDestinationPropertyToSource(ProfileMap options, TypeDetails sourceTypeDetails, Type destType, Type destMemberType, String nameToSearch, List`1 resolvers, IMemberConfiguration parent, Boolean isReverseMap)
   at AutoMapper.Configuration.Conventions.MemberConfiguration.MapDestinationPropertyToSource(ProfileMap options, TypeDetails sourceType, Type destType, Type destMemberType, String nameToSearch, List`1 resolvers, Boolean isReverseMap)
   at AutoMapper.Configuration.Conventions.NameSplitMember.MapDestinationPropertyToSource(ProfileMap options, TypeDetails sourceType, Type destType, Type destMemberType, String nameToSearch, List`1 resolvers, IMemberConfiguration parent, Boolean isReverseMap)
   at AutoMapper.Configuration.Conventions.MemberConfiguration.MapDestinationPropertyToSource(ProfileMap options, TypeDetails sourceType, Type destType, Type destMemberType, String nameToSearch, List`1 resolvers, Boolean isReverseMap)
   at AutoMapper.ProfileMap.MapDestinationPropertyToSource(TypeDetails sourceTypeDetails, Type destType, Type destMemberType, String destMemberName, List`1 members, Boolean reverseNamingConventions)
   at AutoMapper.TypeMap..ctor(Type sourceType, Type destinationType, ProfileMap profile, Boolean isReverseMap)
   at AutoMapper.ProfileMap.BuildTypeMap(IGlobalConfiguration configurationProvider, ITypeMapConfiguration config)
   at AutoMapper.ProfileMap.Register(IGlobalConfiguration configurationProvider)
   at AutoMapper.MapperConfiguration.Seal()
   at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression configurationExpression)
   at AutoMapper.MapperConfiguration..ctor(Action`1 configure)
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
----- Inner Stack Trace -----
   at System.RuntimeMethodHandle.GetStubIfNeeded(RuntimeMethodHandleInternal method, RuntimeType declaringType, RuntimeType[] methodInstantiation)
   at System.Reflection.RuntimeMethodInfo.MakeGenericMethod(Type[] methodInstantiation)

Enter fullscreen mode Exit fullscreen mode

At first I didn’t realise it was an issue with Automapper and I started searching for breaking changes in dotnet 7. Eventually this lead to finding that a change to .Net had created an issue with version 11 of Automapper. The following github issues explains more about what was involved in fixing this.

https://github.com/dotnet/runtime/issues/69119

https://github.com/AutoMapper/AutoMapper/pull/3999

This week version 12 of Automapper was released and it fixed this issue for me.

Code Coverage

My build includes the calculation of code coverage of my unit tests. For some reason this step was failing.

    - task: DotNetCoreCLI@2
      displayName: 'Running API Tests'
      inputs:
        command: 'test'
        arguments: '--no-restore --no-build --configuration $(buildConfiguration) --runtime win-x64 /p:CollectCoverage=true /p:CoverletOutputFormat=opencover'
        projects: 'Path To csproj file'
        nobuild: true    

Enter fullscreen mode Exit fullscreen mode

For .Net 6 this step would run my unit tests and include a section that looked a bit like this.

Calculating coverage result...
  Generating report 'D:\a\1\s\WhatEver\coverage.opencover.xml'

+---------------------------+--------+--------+--------+
| Module                    | Line   | Branch | Method |
+---------------------------+--------+--------+--------+
| Something                 | 50.91% | 48.77% | 65.44% |
+---------------------------+--------+--------+--------+
| Something.Core            | 41.79% | 6.92%  | 47.2%  |
+---------------------------+--------+--------+--------+
| Something.Core.Email      | 96.55% | 50%    | 100%   |
+---------------------------+--------+--------+--------+

+---------+--------+--------+--------+
|         | Line   | Branch | Method |
+---------+--------+--------+--------+
| Total   | 49.46% | 44.2%  | 53.55% |
+---------+--------+--------+--------+
| Average | 63.08% | 35.22% | 70.88% |
+---------+--------+--------+--------+

Enter fullscreen mode Exit fullscreen mode

However for .Net 7 the unit tests would pass but the coverage bit above would not run, and no visible errors to help me.

Testing dotnet test locally I discovered that if I included the path and filename to the csproj file the coverage would not run, if I removed this it would work as per .Net 6. No idea why this was happening.

I did some testing with the DotNetCoreCLI@2 task, but I couldn’t get the coverage to work. However I was testing this locally via the command line so it was a simple thing to swap DotNetCoreCLI@2 for a command line step. After I did that the coverage started working again!

Here is the command line step I added

    - task: CmdLine@2
      displayName: 'Running API Tests'
      inputs:
        script: |
          "C:\Program Files\dotnet\dotnet.exe" test --logger trx --results-directory D:\a\_temp --no-restore --no-build --configuration Release --runtime win-x64 /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
        workingDirectory: '$(Build.SourcesDirectory)/PathToWhereEver'  

Enter fullscreen mode Exit fullscreen mode

SQL Server tables with triggers

My build was all working so now time to test the application was working. But an error was being thrown if I tried to save any data to the SQL Server database.

Could not save changes because the target table has database triggers. Please configure your entity type accordingly, see https://aka.ms/efcore-docs-sqlserver-save-changes-and-triggers for more information. The target table 'Something' of the DML statement cannot have any enabled triggers if the statement contains an OUTPUT clause without INTO clause. 

Enter fullscreen mode Exit fullscreen mode

A nice error, as it gives me a link to the change adn what to do to fix.

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Add(_ => new BlankTriggerAddingConvention());
}


public class BlankTriggerAddingConvention : IModelFinalizingConvention
{
    public virtual void ProcessModelFinalizing(
        IConventionModelBuilder modelBuilder,
        IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
        {
            var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
            if (table != null
                && entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(table.Value) == null))
            {
                entityType.Builder.HasTrigger(table.Value.Name + "_Trigger");
            }

            foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table))
            {
                if (entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(fragment.StoreObject) == null))
                {
                    entityType.Builder.HasTrigger(fragment.StoreObject.Name + "_Trigger");
                }
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Adding the following to my DBContext allowed data to be saved even if triggers existed on my database.

A longer term fix, should be to investigate if the triggers on the database are still needed and remove if not. From reading the docs, it sounds like a performance gain would be had after doing this!

Conclusion

These are the issues I have encountered so far updating to .Net 7. A few more than for .Net 5 or 6. However so far I have managed to find a solution for all issues. I expect one more release candidate, with the final version of .Net 7 coming out in November.

Top comments (0)