From 2d7f7bf2da3596a951151959d4741ede7a3d7f14 Mon Sep 17 00:00:00 2001 From: Aaron Lamb Date: Tue, 5 Sep 2023 18:22:46 +0100 Subject: [PATCH 1/3] Add all writeable properties to TypeProperties cache This catches any writeable property, including internal properties that were previously ignored --- src/Dapper.Contrib/SqlMapperExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Dapper.Contrib/SqlMapperExtensions.cs b/src/Dapper.Contrib/SqlMapperExtensions.cs index 9a30e805..24db853f 100644 --- a/src/Dapper.Contrib/SqlMapperExtensions.cs +++ b/src/Dapper.Contrib/SqlMapperExtensions.cs @@ -127,7 +127,8 @@ private static List TypePropertiesCache(Type type) return pis.ToList(); } - var properties = type.GetProperties().Where(IsWriteable).ToArray(); + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(IsWriteable).ToArray(); TypeProperties[type.TypeHandle] = properties; return properties.ToList(); } From 619193e9678b76360a81a2d3000e2b4bc171247f Mon Sep 17 00:00:00 2001 From: Aaron Lamb Date: Tue, 5 Sep 2023 19:15:50 +0100 Subject: [PATCH 2/3] Adds extra check for private fields Binding flags filter still pulls back private properties, this prevents those properties from being mapped --- src/Dapper.Contrib/SqlMapperExtensions.cs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Dapper.Contrib/SqlMapperExtensions.cs b/src/Dapper.Contrib/SqlMapperExtensions.cs index 24db853f..248a3f85 100644 --- a/src/Dapper.Contrib/SqlMapperExtensions.cs +++ b/src/Dapper.Contrib/SqlMapperExtensions.cs @@ -128,7 +128,15 @@ private static List TypePropertiesCache(Type type) } var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .Where(IsWriteable).ToArray(); + .Where(IsWriteable) + .Where(p => !IsPrivate(p)) + .ToArray(); + + if (properties.Length == 0) + { + throw new DataException($"Entity of type '{type.FullName}' has no accessible properties"); + } + TypeProperties[type.TypeHandle] = properties; return properties.ToList(); } @@ -142,6 +150,11 @@ private static bool IsWriteable(PropertyInfo pi) return writeAttribute.Write; } + private static bool IsPrivate(PropertyInfo pi) + { + return pi.GetGetMethod()?.IsPrivate ?? true; + } + private static PropertyInfo GetSingleKey(string method) { var type = typeof(T); @@ -157,10 +170,10 @@ private static PropertyInfo GetSingleKey(string method) } /// - /// Returns a single entity by a single id from table "Ts". + /// Returns a single entity by a single id from table "Ts". /// Id must be marked with [Key] attribute. /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension - /// for optimal performance. + /// for optimal performance. /// /// Interface or type to create and populate /// Open SqlConnection @@ -288,7 +301,7 @@ private static string GetTableName(Type type) } else { - //NOTE: This as dynamic trick falls back to handle both our own Table-attribute as well as the one in EntityFramework + //NOTE: This as dynamic trick falls back to handle both our own Table-attribute as well as the one in EntityFramework var tableAttrName = type.GetCustomAttribute(false)?.Name ?? (type.GetCustomAttributes(false).FirstOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic)?.Name; @@ -645,7 +658,7 @@ private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder) private static void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity) { - //Define the field and the property + //Define the field and the property var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private); var property = typeBuilder.DefineProperty(propertyName, System.Reflection.PropertyAttributes.None, From f9984455c3c42a540eb96d7010b52826c47034be Mon Sep 17 00:00:00 2001 From: Aaron Lamb Date: Tue, 5 Sep 2023 19:18:38 +0100 Subject: [PATCH 3/3] Reverts formatting changes --- src/Dapper.Contrib/SqlMapperExtensions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Dapper.Contrib/SqlMapperExtensions.cs b/src/Dapper.Contrib/SqlMapperExtensions.cs index 248a3f85..5e4a52e7 100644 --- a/src/Dapper.Contrib/SqlMapperExtensions.cs +++ b/src/Dapper.Contrib/SqlMapperExtensions.cs @@ -170,10 +170,10 @@ private static PropertyInfo GetSingleKey(string method) } /// - /// Returns a single entity by a single id from table "Ts". + /// Returns a single entity by a single id from table "Ts". /// Id must be marked with [Key] attribute. /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension - /// for optimal performance. + /// for optimal performance. /// /// Interface or type to create and populate /// Open SqlConnection @@ -301,7 +301,7 @@ private static string GetTableName(Type type) } else { - //NOTE: This as dynamic trick falls back to handle both our own Table-attribute as well as the one in EntityFramework + //NOTE: This as dynamic trick falls back to handle both our own Table-attribute as well as the one in EntityFramework var tableAttrName = type.GetCustomAttribute(false)?.Name ?? (type.GetCustomAttributes(false).FirstOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic)?.Name; @@ -658,7 +658,7 @@ private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder) private static void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity) { - //Define the field and the property + //Define the field and the property var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private); var property = typeBuilder.DefineProperty(propertyName, System.Reflection.PropertyAttributes.None,