{"action":"created","issue":{"url":"https://api.github.com/repos/fluentsprings/ExpressMapper/issues/98","repository_url":"https://api.github.com/repos/fluentsprings/ExpressMapper","labels_url":"https://api.github.com/repos/fluentsprings/ExpressMapper/issues/98/labels{/name}","comments_url":"https://api.github.com/repos/fluentsprings/ExpressMapper/issues/98/comments","events_url":"https://api.github.com/repos/fluentsprings/ExpressMapper/issues/98/events","html_url":"https://github.com/fluentsprings/ExpressMapper/issues/98","id":159654785,"number":98,"title":"Allow override name comparison strategy","user":{"login":"sergiorykov","id":292285,"avatar_url":"https://avatars.githubusercontent.com/u/292285?v=3","gravatar_id":"","url":"https://api.github.com/users/sergiorykov","html_url":"https://github.com/sergiorykov","followers_url":"https://api.github.com/users/sergiorykov/followers","following_url":"https://api.github.com/users/sergiorykov/following{/other_user}","gists_url":"https://api.github.com/users/sergiorykov/gists{/gist_id}","starred_url":"https://api.github.com/users/sergiorykov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sergiorykov/subscriptions","organizations_url":"https://api.github.com/users/sergiorykov/orgs","repos_url":"https://api.github.com/users/sergiorykov/repos","events_url":"https://api.github.com/users/sergiorykov/events{/privacy}","received_events_url":"https://api.github.com/users/sergiorykov/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":9,"created_at":"2016-06-10T15:00:12Z","updated_at":"2016-06-16T16:43:30Z","closed_at":null,"body":"There are two kinds of built in name comparison strategies: case sensitive/case insensitive.\r\n\r\nLike that\r\n```\r\nstring.Equals(x.Name, prop.Name, stringComparison)\r\nstring.Equals(destProp.Name, matchStart, _stringComparison)\r\n\r\n internal StringComparison GetStringCase()\r\n        {\r\n            StringComparison stringComparison;\r\n\r\n            if (MappingServiceProvider.CaseSensetiveMemberMap && !CaseSensetiveOverride)\r\n            {\r\n                stringComparison = MappingServiceProvider.CaseSensetiveMemberMap\r\n                    ? StringComparison.CurrentCulture\r\n                    : StringComparison.OrdinalIgnoreCase;\r\n            }\r\n            else\r\n            {\r\n                stringComparison = CaseSensetiveMember\r\n                    ? StringComparison.CurrentCulture\r\n                    : StringComparison.OrdinalIgnoreCase;\r\n            }\r\n            return stringComparison;\r\n        }\r\n\r\n```\r\n\r\nI suggest to extract it in separate class to allow to write custom strategy.\r\n\r\nFor example, I need to map generated wrappers for protobuf .proto files which is written in c style:\r\n```\r\npublic class ProtoA \r\n{\r\n      public string very_important_content {get;set;}\r\n      public long timestamp {get;set;}\r\n}\r\n\r\npublic class DomainA \r\n{\r\n      public string VeryImportantContent {get;set;}\r\n      public DateTimeOffset Timestamp {get;set;}\r\n}\r\n```\r\n\r\nI will create strategy for mapper and attach it to mapper:\r\n```\r\npublic class UnixNameComparer : INameComparer \r\n{\r\n      public bool AreEqual(string sourceName, string destinationName) \r\n      {\r\n              // to lower and remove '_' and compare ordingalignorecase\r\n              return true;\r\n      }\r\n}\r\n\r\nMapper.NameComparer = new UnixNameComparer();\r\n```\r\n\r\nThis code only illustrates my proposal. Feature is rather simple. We only need to add backward compability with CaseSensetiveMemberMap (mark it as obsolote and use to override default name comparison strategy).\r\n"},"comment":{"url":"https://api.github.com/repos/fluentsprings/ExpressMapper/issues/comments/226543177","html_url":"https://github.com/fluentsprings/ExpressMapper/issues/98#issuecomment-226543177","issue_url":"https://api.github.com/repos/fluentsprings/ExpressMapper/issues/98","id":226543177,"user":{"login":"sergiorykov","id":292285,"avatar_url":"https://avatars.githubusercontent.com/u/292285?v=3","gravatar_id":"","url":"https://api.github.com/users/sergiorykov","html_url":"https://github.com/sergiorykov","followers_url":"https://api.github.com/users/sergiorykov/followers","following_url":"https://api.github.com/users/sergiorykov/following{/other_user}","gists_url":"https://api.github.com/users/sergiorykov/gists{/gist_id}","starred_url":"https://api.github.com/users/sergiorykov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sergiorykov/subscriptions","organizations_url":"https://api.github.com/users/sergiorykov/orgs","repos_url":"https://api.github.com/users/sergiorykov/repos","events_url":"https://api.github.com/users/sergiorykov/events{/privacy}","received_events_url":"https://api.github.com/users/sergiorykov/received_events","type":"User","site_admin":false},"created_at":"2016-06-16T16:43:30Z","updated_at":"2016-06-16T16:43:30Z","body":"@JonPSmith Thank you for your comments. I have not write any documentation in comments - it's just working prototype, proof of concept. I will definetly add comments to simplify usage.\r\n\r\nI'm not trying to reimplement flattening in AutoMapper's style. They have one more level of abstraction for properties. In our case it's not possible at all (without complete rewriting of API). \r\n\r\nOriginal scheme of detection names for source and destination members relies on case sensivity and assumption that flatten name for destination property consists from source property names.\r\n\r\nsource.son.grandson.myInt -> destination.sonGrandsonMyInt\r\n\r\nAutomapper allows schemes like that:\r\n\r\nSourceNameConvention = LowerCaseUnderscores\r\nDestinationNameConvention = PascalCase\r\nsource.son.grandson.my_int -> destination.SonGrandsonMyInt \r\n\r\nSourceNameConvention = LowerCaseUnderscores\r\nDestinationNameConvention = PascalCase\r\nSource.AddPostfix(\"_dto\")\r\nDestination.AddPrefix(\"domain_\")\r\nsource.son_dto.grandson_dto.my_int_dto -> destination.domain_SonGrandsonMyInt\r\n\r\nThey can do it because they can divide names on components (from both sides), normalize destination name to source name using name convention for source members. \r\n\r\nI've done the same thing according ExpressMapper's API. I needed to do two things:\r\n- extract interface for member name comparison\r\n- override it because string concatenation (prefix + matchedSourceProp.Name) willnot work even underscore name comparison\r\n\r\n```\r\npublic interface IMemberNameComparer\r\n    {\r\n        bool Satisfies(string sourceMemberName, string destinationMemberName);\r\n        bool Satisfies(IEnumerable<string> chainOfSourceMemberNames, string destinationMemberName);\r\n        bool SatisfiesAsFlatten(IEnumerable<string> chainOfSourceMemberNames, string destinationMemberName);\r\n        bool SatisfiesAsSelf(string memberName, string otherMemberName);\r\n    }\r\n```\r\n\r\nIt's full interface to implement any name comparison strategy (according to current design)\r\nSatisfies - compares equality of member names in both cases - direct mapping and flattening\r\n\r\nsource.son.grandson.my_int -> destination.SonGrandsonMyInt \r\n```\r\nSatisfies(new [] {\"son\", \"grandson\", \"my_int\"}, \"SonGrandsonMyInt\") == true\r\nSatisfiesAsFlatten(new [] {\"son\", \"grandson\", \"my_int\"}, \"SonGrandsonMyInt\") == true\r\nSatisfiesAsFlatten(new [] {\"son\", \"grandson\"}, \"SonGrandsonMyInt\") == true\r\n```\r\n\r\nlast on `SatisfiesAsSelf` is used for filtering self properties (source - source, dest - dest) and ignored by user.\r\n\r\nIt's been done using two techniques - name conventions allows parse member names to parts and recompose it using style of opposite side.\r\n\r\nsource.son.grandson.my_int -> destination.SonGrandsonMyInt \r\nSourceNameConvention = LowerCaseUnderscores\r\nDestinationNameConvention = PascalCase\r\n\r\nwe will try to normalize source member names (using Rules) and we will get several possibilities for original name (AutoMapper does exactly the same thing under the cover).\r\n\r\nthe only rule that implemented now is AsIsMemberNameNormalizationRule - it returns name it self\r\nfor PostFix rule Source.AddPostfix(\"_dto\") it will try to cut from source member name that prefix and will increase number of prototypes of semantic member name (dto_son -> [son, dto_son]).\r\n\r\nthen we will try compose all possible source names using dest name convention and compare them with dest prop name:\r\n\r\n[[son],[grandson],[my_int]] -> [[Son],[Grandson],[MyInt]] -> SonGrandsonMyInt == destination.SonGrandsonMyInt \r\n\r\nfor flatten it will compare using `string.StartsWith`, for direct mapping of full flatten mapping: `string.Equals`. \r\n\r\nEF is just custom case:\r\n\r\nSatisfiesAsFlatten(new [] {\"son\", \"children\", <EF suffix> }, \"SonChildrenCount\") == true\r\nvar enumerableMethod = \r\n```\r\nFlattenLinqMethod.EnumerableEndMatchsWithLinqMethod(sourcePropPath.Select(p => p.Name), destProp.Name, _nameComparer);\r\n\r\nreturn EnumerableMethodLookup.SingleOrDefault(x =>\r\n            {\r\n                var chainOfSourcePropsWithLinqSuffix = chainOfSourcePropNames.ToList();\r\n                chainOfSourcePropsWithLinqSuffix.Add(x._methodName);\r\n                return comparer.Satisfies(chainOfSourcePropsWithLinqSuffix, destinationName);\r\n            });\r\n```"}}
{"actor":{"display_login":"sergiorykov"}}