/// <summary> /// Get (from store, or creating the first time it is needed) data contract for the type, columns spec and data mapper. /// </summary> /// <param name="IsGeneric"></param> /// <param name="type"></param> /// <param name="columns"></param> /// <param name="mapper"></param> /// <returns></returns> /// <remarks> /// In theory, mapping depends on Plugin, Factory, and ConnectionString as well; /// in practice, including those would make it much harder to provide the very useful /// <see cref="DataContract.Map(string)"/> feature. /// I think it seems (more or less?) reasonable to suppose that any one class will only /// be read from and written one database with one mapping at a time? In fact, since /// Mighty only supports one mapping per class, maybe this is effectively enforced anyway? /// </remarks> internal DataContract Get(bool IsGeneric, Type type, string columns, SqlNamingMapper mapper) { DataContractKey key = new DataContractKey(IsGeneric, type, columns, mapper); CacheHits++; return(store.GetOrAdd(key, k => { CacheHits--; CacheMisses++; return new DataContract(k); })); }
/// <summary> /// Create a new data contract corresponding to the values in the key /// </summary> /// <param name="Key">All the items on which the contract depends</param> public DataContract(DataContractKey Key) { this.Key = Key; if (!Key.DynamicNullContract) { var ReadColumnList = new List <string>(); var KeyColumnsList = new List <string>(); bool foundControlledColumn; bool foundRenamedColumn; // These two don't need to be ConcurrentDictionary - they can't be written to concurrently (they are set up in the constructor then not modified), even though they may be read concurrently ColumnNameToMemberInfo = new Dictionary <string, DataContractMemberInfo>(Key.DatabaseTableSettings.CaseSensitiveColumnMapping ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); MemberNameToColumnName = new Dictionary <string, string>(Key.DatabaseTableSettings.CaseSensitiveColumnMapping ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); if (Key.IsGeneric) { AddReflectedColumns(out bool fc1, out bool fr1, ReadColumnList, KeyColumnsList, Key, BindingFlags.Instance | BindingFlags.Public); AddReflectedColumns(out bool fc2, out bool fr2, ReadColumnList, KeyColumnsList, Key, BindingFlags.Instance | BindingFlags.NonPublic); foundControlledColumn = fc1 || fc2; foundRenamedColumn = fr1 || fr2; } else { foreach (var column in Key.DynamicColumnSpec.Split(',')) { AddReflectedColumn(out bool fc, out bool fr, ReadColumnList, KeyColumnsList, Key, null, column, true); } foundControlledColumn = true; foundRenamedColumn = true; } if (foundControlledColumn) { // We have a read column list if there are any controlled columns (including e.g. ignored columns) in the contract ReadColumns = string.Join(", ", ReadColumnList); } if (foundRenamedColumn) { // This switches on auto-mapping by defaut if there are any actually renamed columns AutoMapSettings = Key.DatabaseTableSettings.AutoMap; } if (KeyColumnsList.Count > 0) { KeyColumns = string.Join(", ", KeyColumnsList); } } }
/// <summary> /// Include reflected columns /// </summary> /// <param name="foundControlledColumn"></param> /// <param name="foundRenamedColumn"></param> /// <param name="ReadColumnList"></param> /// <param name="KeyColumnsList"></param> /// <param name="key"></param> /// <param name="bindingFlags"></param> /// <returns>Whether a controlled column (<see cref="DatabaseColumnAttribute"/> or <see cref="DatabaseIgnoreAttribute"/>) was found</returns> protected void AddReflectedColumns( out bool foundControlledColumn, out bool foundRenamedColumn, List <string> ReadColumnList, List <string> KeyColumnsList, DataContractKey key, BindingFlags bindingFlags) { foundControlledColumn = false; foundRenamedColumn = false; foreach (var member in key.DataItemType.GetMembers(bindingFlags) .Where(m => m is FieldInfo || m is PropertyInfo)) { AddReflectedColumn( out bool fc, out bool fr, ReadColumnList, KeyColumnsList, key, member, null, (bindingFlags & BindingFlags.Public) != 0); foundControlledColumn = fc || foundControlledColumn; foundRenamedColumn = fr || foundRenamedColumn; } }
/// <summary> /// Get (from store, or creating the first time it is needed) data contract for the type, columns spec and data mapper. /// </summary> /// <param name="IsGeneric"></param> /// <param name="type"></param> /// <param name="columns"></param> /// <param name="mapper"></param> /// <returns></returns> /// <remarks> /// In theory, mapping depends on Plugin, Factory, and ConnectionString as well; /// in practice, including those would make it much harder to provide the very useful /// <see cref="DataContract.Map(string)"/> feature. /// I think it seems (more or less?) reasonable to suppose that any one class will only /// be read from and written one database with one mapping at a time? In fact, since /// Mighty only supports one mapping per class, maybe this is effectively enforced anyway? /// </remarks> internal DataContract Get(bool IsGeneric, Type type, string columns, SqlNamingMapper mapper) { DataContractKey key = new DataContractKey(IsGeneric, type, columns, mapper); DataContract value; if (store.TryGetValue(key, out value)) { CacheHits++; } else { CacheMisses++; value = new DataContract(key); store.Add(key, value); } return(value); }
/// <summary> /// Add a reflected field to the column list /// </summary> /// <param name="foundControlledColumn"></param> /// <param name="foundRenamedColumn"></param> /// <param name="ReadColumnList"></param> /// <param name="KeyColumnsList"></param> /// <param name="key"></param> /// <param name="member"></param> /// <param name="name"></param> /// <param name="include">The initial default include status (depending on public, non-public or columns-driven)</param> /// <returns>Whether a controlled column (<see cref="DatabaseColumnAttribute"/> or <see cref="DatabaseIgnoreAttribute"/>) was found</returns> protected void AddReflectedColumn( out bool foundControlledColumn, out bool foundRenamedColumn, List <string> ReadColumnList, List <string> KeyColumnsList, DataContractKey key, MemberInfo member, string name, bool include) { foundControlledColumn = false; foundRenamedColumn = false; bool isKey = false; string sqlColumnName = null; DataDirection dataDirection = 0; string transformSql = null; // Control things by attributes if (member != null) { foreach (var attr in member.GetCustomAttributes(false)) { if (attr is DatabaseColumnAttribute) { var colAttr = (DatabaseColumnAttribute)attr; include = true; sqlColumnName = colAttr.Name; dataDirection = colAttr.Direction; transformSql = colAttr.Transform; foundControlledColumn = true; } if (attr is DatabaseIgnoreAttribute) { include = false; foundControlledColumn = true; } if (attr is DatabasePrimaryKeyAttribute) { isKey = true; } } } name = name ?? member.Name; // Also control things by the mapper DataDirection mapperDirection = key.ColumnDataDirection(key.DataItemType, name); if (mapperDirection != 0) { // We could do one of several things, but to make it like the ignore setting, we're // OR-ing the direction settings dataDirection |= mapperDirection; //// Not sure about this, but since having a column name in the mapper doesn't //// switch this on, then probably this shouldn't either? //foundcontrolledcolumn = true; } // The column will be ignored if the mapper or the attribute say so if (key.IgnoreColumn(key.DataItemType, name)) { include = false; foundControlledColumn = true; } if (include) { // the column name from the attribute has precedence sqlColumnName = sqlColumnName ?? key.ColumnName(key.DataItemType, name); if (sqlColumnName != name) { foundRenamedColumn = true; } ColumnNameToMemberInfo.Add(sqlColumnName, new DataContractMemberInfo(key.DataItemType, member, name, dataDirection)); MemberNameToColumnName.Add(name, sqlColumnName); ReadColumnList.Add($"{(transformSql != null ? $"{transformSql} AS " : "")}{sqlColumnName}"); if (isKey) { KeyColumnsList.Add(sqlColumnName); } } }