/// <summary>
        /// Accept a V3 request URI and return a V4 request URI.  V4 request URI retains base path.
        /// </summary>
        /// <param name="requestUri">V3 Request URI</param>
        /// <returns>V4 Request URI</returns>
        public Uri TranslateUri(Uri requestUri)
        {
            // Use UriTranslator to walk v3 segments, translating each to v4 and returning.
            ODataPath            v3path        = new ODataUriParser(this.v3Model, this.serviceRoot).ParsePath(requestUri);
            UriSegmentTranslator uriTranslator = new UriSegmentTranslator(this.v4Model);

            Microsoft.OData.UriParser.ODataPath v4path = new Microsoft.OData.UriParser.ODataPath(v3path.WalkWith(uriTranslator));

            // Parse query options for translation
            NameValueCollection requestQuery = HttpUtility.ParseQueryString(requestUri.Query);

            // Create a v4 ODataUri and utilized ODataUriExtensions methods to build v4 URI
            ODataUri v4Uri = new ODataUri()
            {
                Path   = v4path,
                Filter = ParseFilterFromQueryOrNull(requestQuery, v4path, v3path)
            };

            Uri v4RelativeUri = ODataUriExtensions.BuildUri(v4Uri, ODataUrlKeyDelimiter.Parentheses);
            Uri v4FullUri     = new Uri(serviceRoot, v4RelativeUri);

            // Translated query only contains translated filter clause
            // We need to move everything from v3 query that does not require translation to v4 query
            NameValueCollection translatedQuery = HttpUtility.ParseQueryString(v4FullUri.Query);

            // Copy values from requestQuery to new translated query.  Iterate with multiple loops
            // because NameValueCollections also support multiple values to one key
            foreach (string k in requestQuery)
            {
                foreach (string v in requestQuery.GetValues(k))
                {
                    string key = k.Trim();
                    if (key == "$inlinecount")
                    {
                        translatedQuery["$count"] = ParseInlineCountFromQuery(v.Trim());
                    }
                    else if (key != "$filter")
                    {
                        translatedQuery[k] = v;
                    }
                }
            }

            UriBuilder builder = new UriBuilder(serviceRoot.Scheme, serviceRoot.Host, serviceRoot.Port, v4FullUri.AbsolutePath);

            // NameValueCollection ToString is overriden to produce a URL-encoded string, decode it to maintain consistency
            builder.Query = WebUtility.UrlDecode(translatedQuery.ToString());

            return(builder.Uri);
        }
        // new, ok
        private ODataQueryContext GetQueryContext(Type rootType, Type entityType)
        {
            MakeModel(rootType);
            //var entitySet = _model.FindDeclaredEntitySet(rootType.Name);
            var segment = new Microsoft.OData.UriParser.PathTemplateSegment($"~/"); // {rootType.Name}

            var path = new ODataPath(segment);
            var uriParseODataPath = new Microsoft.OData.UriParser.ODataPath(segment);
            var queryContext      = new ODataQueryContext(_model, entityType, path)
            {
                DefaultQuerySettings =
                {
                    EnableCount   = true,
                    EnableExpand  = true,
                    EnableFilter  = true,
                    EnableOrderBy = true,
                    EnableSelect  = true,
                    //MaxTop = 100,
                },
            };

            return(queryContext);
        }
        // If filter clause is found in query, translate from v3 filter clause to v4 clause
        private Microsoft.OData.UriParser.FilterClause ParseFilterFromQueryOrNull(NameValueCollection query, Microsoft.OData.UriParser.ODataPath pathSegments, ODataPath v3Segments)
        {
            Microsoft.OData.UriParser.FilterClause v4FilterClause = null;
            // The MSDN specification advises checking if NameValueCollection contains key by using indexing
            // https://docs.microsoft.com/en-us/dotnet/api/system.collections.specialized.namevaluecollection.item?redirectedfrom=MSDN&view=netframework-4.8#System_Collections_Specialized_NameValueCollection_Item_System_String_
            if (query["$filter"] != null)
            {
                // Parse filter clause in v3
                EntitySetSegment        entitySegment  = v3Segments.Reverse().FirstOrDefault(segment => segment is EntitySetSegment) as EntitySetSegment;
                Data.Edm.IEdmEntityType entityType     = entitySegment.EntitySet.ElementType;
                FilterClause            v3FilterClause = ODataUriParser.ParseFilter(query["$filter"], v3Model, entityType);

                // Translate node and range variable into v4 format
                QueryNodeTranslator queryTranslator = new QueryNodeTranslator(v4Model);
                Microsoft.OData.UriParser.SingleValueNode v4Node = (Microsoft.OData.UriParser.SingleValueNode)v3FilterClause.Expression.Accept(queryTranslator);
                Microsoft.OData.UriParser.RangeVariable   v4Var  = queryTranslator.TranslateRangeVariable(v3FilterClause.RangeVariable);
                v4FilterClause = new Microsoft.OData.UriParser.FilterClause(v4Node, v4Var);
            }
            return(v4FilterClause);
        }