{"action":"created","issue":{"url":"https://api.github.com/repos/praeclarum/sqlite-net/issues/623","repository_url":"https://api.github.com/repos/praeclarum/sqlite-net","labels_url":"https://api.github.com/repos/praeclarum/sqlite-net/issues/623/labels{/name}","comments_url":"https://api.github.com/repos/praeclarum/sqlite-net/issues/623/comments","events_url":"https://api.github.com/repos/praeclarum/sqlite-net/issues/623/events","html_url":"https://github.com/praeclarum/sqlite-net/pull/623","id":252440199,"node_id":"MDExOlB1bGxSZXF1ZXN0MTM3MzM2MTAz","number":623,"title":"add support for custom table mappings using attribute","user":{"login":"nberardi","id":279540,"node_id":"MDQ6VXNlcjI3OTU0MA==","avatar_url":"https://avatars1.githubusercontent.com/u/279540?v=4","gravatar_id":"","url":"https://api.github.com/users/nberardi","html_url":"https://github.com/nberardi","followers_url":"https://api.github.com/users/nberardi/followers","following_url":"https://api.github.com/users/nberardi/following{/other_user}","gists_url":"https://api.github.com/users/nberardi/gists{/gist_id}","starred_url":"https://api.github.com/users/nberardi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nberardi/subscriptions","organizations_url":"https://api.github.com/users/nberardi/orgs","repos_url":"https://api.github.com/users/nberardi/repos","events_url":"https://api.github.com/users/nberardi/events{/privacy}","received_events_url":"https://api.github.com/users/nberardi/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2017-08-23T22:57:10Z","updated_at":"2018-06-20T00:28:10Z","closed_at":null,"author_association":"NONE","pull_request":{"url":"https://api.github.com/repos/praeclarum/sqlite-net/pulls/623","html_url":"https://github.com/praeclarum/sqlite-net/pull/623","diff_url":"https://github.com/praeclarum/sqlite-net/pull/623.diff","patch_url":"https://github.com/praeclarum/sqlite-net/pull/623.patch"},"body":"The primary goal of this was two fold,\r\n\r\n1.  To improve performance across all connections so that the code doesn't need to parse the type every time to get the `TableMapping`.\r\n2.  And to seperate out the central place for type to table mapping, so that it can be abstracted, customized, and controlled by implimentors instead of having to accept the SQLite.NET default mapping.\r\n\r\nThis is the breakup of #599 that was asked for.\r\n\r\nAn example of how I have used this in production code is the following.  The goal of the custom mapping was to control the private setting interface so that it followed some very strict control paradigms and didn't allow client code to touch the values.\r\n\r\n```csharp\r\n    [Table(\"files\"), TableMapper(typeof(FileRecordMapper))]\r\n    public class FilesSyncRecord : ISyncRecord, IFileSystemObject, IEquatable<FilesSyncRecord>\r\n    {\r\n           ...\r\n\r\n        [Column(\"local_path\"), Indexed(Name = \"IX_file_path\", Unique = true)]\r\n        public string LocalPath\r\n        {\r\n            get\r\n            {\r\n                return _path;\r\n            }\r\n            private set\r\n            {\r\n               _path = value;\r\n            }\r\n        }\r\n\r\n        [Column(\"local_name\")]\r\n        public string LocalName\r\n        {\r\n            get\r\n            {\r\n                return _name;\r\n            }\r\n            private set\r\n            {\r\n               _name = value;\r\n            }\r\n        }\r\n\r\n        [Column(\"local_parent_path\"), Indexed(Name = \"IX_parent_path\")]\r\n        public string LocalParentPath\r\n        {\r\n            get {\r\n                return _parentPath;\r\n            }\r\n            private set\r\n            {\r\n                _parentPath = value;\r\n            }\r\n        }\r\n\r\n        public void SetLocalPathFromDatabase([CanBeNull] string s)\r\n        {\r\n            if (String.IsNullOrEmpty(s))\r\n            {\r\n                _path = null;\r\n                return;\r\n            }\r\n\r\n            var value = s.Replace(ColdBootPathSeperator, PathEx.DirectorySeparatorString);\r\n            _path = PathEx.NormalizePathWithoutTrailingSlash(value);\r\n        }\r\n\r\n        public void SetLocalNameFromDatabase([CanBeNull] string s)\r\n        {\r\n            _name = s;\r\n        }\r\n\r\n        public void SetLocalParentPathFromDatabase([CanBeNull] string s)\r\n        {\r\n            if (String.IsNullOrEmpty(s))\r\n            {\r\n                _parentPath = null;\r\n                return;\r\n            }\r\n\r\n            var value = s.Replace(ColdBootPathSeperator, PathEx.DirectorySeparatorString);\r\n            _parentPath = PathEx.NormalizePathWithoutTrailingSlash(value);\r\n        }\r\n    }\r\n\r\n    public class FileRecordMapper : ITableMapper\r\n    {\r\n        private static readonly Action<PropertyInfo, object, object> _setValue = (prop, obj, val) => prop.SetValue(obj, val, null);\r\n        private static readonly Func<PropertyInfo, object, object> _getValue = (prop, obj) => prop.GetValue(obj, null);\r\n\r\n        public List<TableMapping.Column> GetColumns(Type t, CreateFlags createFlags = CreateFlags.None)\r\n        {\r\n            var schema = GetSchema(t);\r\n            var props = schema.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty);\r\n\r\n            var cols = new List<TableMapping.Column>();\r\n            foreach (var p in props)\r\n                AddColumn(cols, p, createFlags);\r\n\r\n            return cols;\r\n        }\r\n\r\n        public static void SetPropertyValue<T, TVal>(object obj, [CanBeNull]object value, Action<T, TVal> exec)\r\n            where T : class\r\n        {\r\n            var x = obj as T;\r\n            if (x == null)\r\n                throw new Exception($\"The expected type is {typeof(T).Name}, but it was {obj.GetType().Name}.\");\r\n\r\n            var tvalType = typeof(TVal);\r\n\r\n            if (tvalType.IsValueType && (tvalType.IsGenericType == false || tvalType.GetGenericTypeDefinition() != typeof(Nullable<>)) && value == null)\r\n                throw new ArgumentNullException($\"Type {tvalType.Name} cannot be null\");\r\n\r\n            var val = (TVal)value;\r\n            exec(x, val);\r\n        }\r\n\r\n        protected virtual bool AddColumn(List<TableMapping.Column> cols, PropertyInfo p, CreateFlags createFlags)\r\n        {\r\n            Action<PropertyInfo, object, object> setValue;\r\n            switch (p.Name)\r\n            {\r\n                case nameof(FilesSyncRecord.LocalPath):\r\n                    setValue = (prop, obj, val) => SetPropertyValue<FilesSyncRecord, string>(obj, val, (x, v) => x.SetLocalPathFromDatabase(v));\r\n\r\n                    cols.Add(new TableMapping.Column(p, setValue, _getValue, createFlags));\r\n                    break;\r\n                case nameof(FilesSyncRecord.LocalName):\r\n                    setValue = (prop, obj, val) => SetPropertyValue<FilesSyncRecord, string>(obj, val, (x, v) => x.SetLocalNameFromDatabase(v));\r\n\r\n                    cols.Add(new TableMapping.Column(p, setValue, _getValue, createFlags));\r\n                    break;\r\n                case nameof(FilesSyncRecord.LocalParentPath):\r\n                    setValue = (prop, obj, val) => SetPropertyValue<FilesSyncRecord, string>(obj, val, (x, v) => x.SetLocalParentPathFromDatabase(v));\r\n\r\n                    cols.Add(new TableMapping.Column(p, setValue, _getValue, createFlags));\r\n                    break;\r\n                default:\r\n                    var ignore = p.GetCustomAttributes(typeof(IgnoreAttribute), true).Length > 0;\r\n                    if (p.CanWrite && !ignore)\r\n                        cols.Add(new TableMapping.Column(p, _setValue, _getValue, createFlags));\r\n                    break;\r\n            }\r\n\r\n            return true;\r\n        }\r\n\r\n        public object CreateInstance(Type t)\r\n        {\r\n            return new FilesSyncRecord();\r\n        }\r\n\r\n        public Type GetSchema(Type t) => t;\r\n    }\r\n```"},"comment":{"url":"https://api.github.com/repos/praeclarum/sqlite-net/issues/comments/398586464","html_url":"https://github.com/praeclarum/sqlite-net/pull/623#issuecomment-398586464","issue_url":"https://api.github.com/repos/praeclarum/sqlite-net/issues/623","id":398586464,"node_id":"MDEyOklzc3VlQ29tbWVudDM5ODU4NjQ2NA==","user":{"login":"Rivolvan","id":7454687,"node_id":"MDQ6VXNlcjc0NTQ2ODc=","avatar_url":"https://avatars0.githubusercontent.com/u/7454687?v=4","gravatar_id":"","url":"https://api.github.com/users/Rivolvan","html_url":"https://github.com/Rivolvan","followers_url":"https://api.github.com/users/Rivolvan/followers","following_url":"https://api.github.com/users/Rivolvan/following{/other_user}","gists_url":"https://api.github.com/users/Rivolvan/gists{/gist_id}","starred_url":"https://api.github.com/users/Rivolvan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Rivolvan/subscriptions","organizations_url":"https://api.github.com/users/Rivolvan/orgs","repos_url":"https://api.github.com/users/Rivolvan/repos","events_url":"https://api.github.com/users/Rivolvan/events{/privacy}","received_events_url":"https://api.github.com/users/Rivolvan/received_events","type":"User","site_admin":false},"created_at":"2018-06-20T00:28:10Z","updated_at":"2018-06-20T00:28:10Z","author_association":"NONE","body":"is this still on the roadmap to be merged?"}}
{"actor":{"display_login":"Rivolvan"}}