/// <summary> /// Creates a new TableInfo for the specified type, with optional table mapper. /// </summary> /// <param name="type">The entity <see cref="Type"/>.</param> /// <param name="tableNameMapper">A delegate for how to generate the table name from the <paramref name="type"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="type"/></exception> public TableInfo(Type type, TableNameMapperDelegate tableNameMapper) { ClassType = type ?? throw new ArgumentNullException(nameof(type)); if (tableNameMapper != null) { TableName = TableNameMapper(type); } else { var tableAttr = type.GetCustomAttributes(false).SingleOrDefaultOfType("TableAttribute"); if (tableAttr != null) { TableName = tableAttr.Name; if (tableAttr.Schema != null) { SchemaName = tableAttr.Schema; } } else { TableName = type.Name + "s"; if (type.IsInterface && TableName.StartsWith("I")) { TableName = TableName.Substring(1); } } } Columns = type.GetProperties() .Where(typeProperty => !typeProperty.GetCustomAttributes(false).AnyOfType <IgnoreAttribute>()) .Select(typeProperty => { var attributes = typeProperty.GetCustomAttributes(false); var columnAtt = attributes.SingleOrDefaultOfType("ColumnAttribute"); var seqAtt = attributes.SingleOrDefaultOfType <SequenceAttribute>(); var genAtt = attributes.SingleOrDefaultOfType <DatabaseGeneratedAttribute>(); var hasReadOnlyAttribute = attributes.AnyOfType <ReadOnlyAttribute>(); // Microsoft implies that TimestampAttribute is equivalent to ConcurrencyCheck + IsGenerated. // @see https://www.learnentityframeworkcore.com/configuration/data-annotation-attributes/timestamp-attribute var hasTimestampAttribute = attributes.OfType <TimestampAttribute>().Any(); var ci = new ColumnInfo { Property = typeProperty, ColumnName = columnAtt?.Name ?? typeProperty.Name, PropertyName = typeProperty.Name, IsKey = attributes.AnyOfType <KeyAttribute>(), IsIdentity = genAtt?.DatabaseGeneratedOption == DatabaseGeneratedOption.Identity || seqAtt != null, IsGenerated = genAtt != null && genAtt.DatabaseGeneratedOption != DatabaseGeneratedOption.None || seqAtt != null || hasTimestampAttribute, IsConcurrencyToken = hasTimestampAttribute || attributes.AnyOfType <ConcurrencyCheckAttribute>(), ExcludeOnSelect = attributes.AnyOfType <IgnoreSelectAttribute>(), SequenceName = seqAtt?.Name }; ci.IsNullable = !ci.IsKey && // do not allow Keys to be nullable !attributes.AnyOfType <RequiredAttribute>() && // Required cannot be null. LATER: do we want to validate empty values? Using this for pre-C# 8 nullable enforcement ci.Property.IsNullable(); ci.ExcludeOnInsert = ci.IsGenerated && seqAtt == null || attributes.AnyOfType <IgnoreInsertAttribute>() || hasReadOnlyAttribute; ci.ExcludeOnUpdate = ci.IsGenerated || attributes.AnyOfType <IgnoreUpdateAttribute>() || hasReadOnlyAttribute; if (!ci.IsGenerated) { return(ci); } var parameter = Expression.Parameter(type); var property = Expression.Property(parameter, ci.Property); var conversion = Expression.Convert(property, typeof(object)); var lambda = Expression.Lambda(conversion, parameter); ci.Output = lambda; return(ci); }) .ToArray(); if (!Columns.Any(columnInfo => columnInfo.IsKey)) { var idProp = Columns.FirstOrDefault(columnInfo => string.Equals(columnInfo.PropertyName, "id", StringComparison.CurrentCultureIgnoreCase)); if (idProp != null) { idProp.IsKey = idProp.IsGenerated = idProp.IsIdentity = idProp.ExcludeOnInsert = idProp.ExcludeOnUpdate = true; } } _insertColumns = new Lazy <IEnumerable <ColumnInfo> >(() => Columns.Where(ci => !ci.ExcludeOnInsert), true); _updateColumns = new Lazy <IEnumerable <ColumnInfo> >(() => Columns.Where(ci => !ci.ExcludeOnUpdate), true); _selectColumns = new Lazy <IEnumerable <ColumnInfo> >(() => Columns.Where(ci => !ci.ExcludeOnSelect), true); _keyColumns = new Lazy <IEnumerable <ColumnInfo> >(() => Columns.Where(ci => ci.IsKey), true); _generatedColumns = new Lazy <IEnumerable <ColumnInfo> >(() => Columns.Where(ci => ci.IsGenerated), true); _concurrencyCheckColumns = new Lazy <IEnumerable <ColumnInfo> >(() => Columns.Where(ci => ci.IsConcurrencyToken), true); _comparisonColumns = new Lazy <IEnumerable <ColumnInfo> >(() => Columns.Where(ci => ci.IsKey || ci.IsConcurrencyToken), true); _propertyList = new Lazy <IEnumerable <PropertyInfo> >(() => Columns.Select(ci => ci.Property), true); }
/// <summary> /// /// </summary> /// <param name="type"></param> /// <param name="tablenameMapper"></param> public TableInfo(Type type, TableNameMapperDelegate tablenameMapper) { ClassType = type; if (tablenameMapper != null) { TableName = TableNameMapper(type); } else { //NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework var tableAttr = type #if NETSTANDARD1_3 .GetTypeInfo() #endif .GetCustomAttributes(false).SingleOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic; if (tableAttr != null) { TableName = tableAttr.Name; if (tableAttr.Schema != null) { SchemaName = tableAttr.Schema; } } else { TableName = type.Name + "s"; if (type.IsInterface() && TableName.StartsWith("I")) { TableName = TableName.Substring(1); } } } ColumnInfos = type.GetProperties() .Where(t => t.GetCustomAttributes(typeof(IgnoreAttribute), false).Count() == 0) .Select(t => { var columnAtt = t.GetCustomAttributes(false).SingleOrDefault(attr => attr.GetType().Name == "ColumnAttribute") as dynamic; var ci = new ColumnInfo { Property = t, ColumnName = columnAtt?.Name ?? t.Name, PropertyName = t.Name, IsKey = t.GetCustomAttributes(true).Any(a => a is KeyAttribute), IsIdentity = t.GetCustomAttributes(true).Any(a => a is DatabaseGeneratedAttribute && (a as DatabaseGeneratedAttribute).DatabaseGeneratedOption == DatabaseGeneratedOption.Identity), IsGenerated = t.GetCustomAttributes(true).Any(a => a is DatabaseGeneratedAttribute && (a as DatabaseGeneratedAttribute).DatabaseGeneratedOption != DatabaseGeneratedOption.None), ExcludeOnSelect = t.GetCustomAttributes(true).Any(a => a is IgnoreSelectAttribute) }; ci.ExcludeOnInsert = ci.IsGenerated || t.GetCustomAttributes(true).Any(a => a is IgnoreInsertAttribute) || t.GetCustomAttributes(true).Any(a => a is ReadOnlyAttribute); ci.ExcludeOnUpdate = ci.IsGenerated || t.GetCustomAttributes(true).Any(a => a is IgnoreUpdateAttribute) || t.GetCustomAttributes(true).Any(a => a is ReadOnlyAttribute); return(ci); }) .ToArray(); if (!ColumnInfos.Any(k => k.IsKey)) { var idProp = ColumnInfos.FirstOrDefault(p => string.Equals(p.PropertyName, "id", StringComparison.CurrentCultureIgnoreCase)); if (idProp != null) { idProp.IsKey = idProp.IsGenerated = idProp.IsIdentity = idProp.ExcludeOnInsert = idProp.ExcludeOnUpdate = true; } } }