void CollectMappedTables() { mappedTables.Clear(); foreach (MemberInfo memberInfo in GetType().GetMembers( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) { Type memberType; PropertyInfo propertyInfo = memberInfo as PropertyInfo; FieldInfo fieldInfo = memberInfo as FieldInfo; if (propertyInfo != null) { memberType = propertyInfo.PropertyType; } else if (fieldInfo != null) { memberType = fieldInfo.FieldType; } else { continue; } SQLiteMapperTableAttribute tableAttribute = memberInfo .GetCustomAttributes(typeof(SQLiteMapperTableAttribute), inherit: true) .Cast <SQLiteMapperTableAttribute>() .FirstOrDefault(); if (tableAttribute == null) { continue; } if (!memberType.IsGenericType || !typeof(MappedTable <>).IsAssignableFrom(memberType.GetGenericTypeDefinition())) { throw new ArgumentException("Invalid type for property " + memberInfo.Name + ": must be based on MappedTable<T>"); } MappedTable mappedTable = (MappedTable)Activator.CreateInstance(memberType, nonPublic: true); mappedTable.TableName = memberInfo.Name; mappedTable.RecordType = memberType.GetGenericArguments()[0]; mappedTable.Context = this; mappedTable.TrackChanges = typeof(IMappedRecordWithChangeTracking).IsAssignableFrom(mappedTable.RecordType); var columnSet = SQLiteMapperHelpers.CreateColumnSetForType(mappedTable.RecordType); mappedTable.ColumnSet = columnSet; string errorPrefix = "Problem with definition of table \"" + mappedTable.TableName + "\": "; foreach (var column in mappedTable.ColumnSet.Columns) { if (StringComparer.OrdinalIgnoreCase.Equals(column.Name, SQLiteMapper.ChangeNumberColumnName)) { if (!mappedTable.TrackChanges) { throw new InvalidOperationException(errorPrefix + "The reserved " + SQLiteMapper.ChangeNumberColumnName + " column cannot be used" + " unless the record implements the " + typeof(IMappedRecordWithChangeTracking).Name + " interface"); } if (column.Nullable) { throw new InvalidOperationException(errorPrefix + "The " + SQLiteMapper.ChangeNumberColumnName + " column cannot be nullable"); } bool defaultValueOkay = false; try { long value = (long)SQLiteMapperHelpers.ConvertObjectToSql(typeof(long), column.SqlDefaultValue); if (value == -1) { defaultValueOkay = true; } } catch { } if (!defaultValueOkay) { throw new InvalidOperationException(errorPrefix + "The " + SQLiteMapper.ChangeNumberColumnName + " column must have -1 as its default value"); } } } mappedTable.Indexes.AddRange( mappedTable.RecordType.GetCustomAttributes(typeof(MappedTableIndexAttribute), inherit: true) .Cast <MappedTableIndexAttribute>() // Ensure nondeterministic ordering .OrderBy(x => x.ToString()) ); if (propertyInfo != null) { propertyInfo.SetValue(this, mappedTable, null); } else { fieldInfo.SetValue(this, mappedTable); } mappedTables.Add(mappedTable); } }
public void InsertRecords <T>(MappedTable table, IEnumerable <T> records, SQLiteConflictResolution conflictResolution = SQLiteConflictResolution.Abort, Type recordType = null) { if (recordType == null) { recordType = typeof(T); } var columnSet = SQLiteMapperHelpers.CreateColumnSetForType(recordType); var descriptors = SQLiteMapperHelpers.GetPropertyDescriptors(recordType, columnSet); WrapDatabaseExceptions(() => { using (SQLiteCommand command = connection.CreateCommand()) using (SQLiteCommand fixReplaceCommand = connection.CreateCommand()) { command.CommandText = SQLiteMapperHelpers.GetInsertStatement(columnSet, table.TableName, conflictResolution); foreach (var column in columnSet.Columns) { var parameter = command.CreateParameter(); parameter.Direction = ParameterDirection.Input; command.Parameters.Add(parameter); } command.Prepare(); // SQLite implements "INSERT OR REPLACE" as a DELETE followed by an INSERT, which can cause // trouble when our TrackChanges trigger tries to compute MAX(ChangeNumber). The workaround // is to make sure an accurate ChangeNumber is in the C# record before we start // the INSERT OR REPLACE. (Normally this is already true, but sometimes it's 0 or a // little outdated so we do a SELECT to be sure.) bool fixReplace = table.TrackChanges && conflictResolution == SQLiteConflictResolution.Replace; SQLiteParameter fixReplaceParameter = null; PropertyDescriptor fixReplacePrimaryKeyDescriptor = null; if (fixReplace) { if (columnSet.PrimaryKeyColumn == null) { throw new InvalidOperationException( "SQLiteConflictResolution.Replace cannot be used without including the primary key"); } int primaryKeyIndex = columnSet.Columns.IndexOf(columnSet.PrimaryKeyColumn); fixReplacePrimaryKeyDescriptor = descriptors[primaryKeyIndex]; fixReplaceCommand.CommandText = string.Format( "SELECT [{0}] FROM [{1}] WHERE [{2}] = ?", SQLiteMapper.ChangeNumberColumnName, table.TableName, columnSet.PrimaryKeyColumn.Name); fixReplaceParameter = fixReplaceCommand.CreateParameter(); fixReplaceParameter.Direction = ParameterDirection.Input; fixReplaceCommand.Parameters.Add(fixReplaceParameter); } using (var transaction = this.BeginTransaction()) { foreach (var record in records) { if (fixReplace) { var castedRecord = record as IMappedRecordWithChangeTracking; if (castedRecord == null) { throw new InvalidOperationException( "SQLiteConflictResolution.Replace cannot be used without including the ChangeNumber column"); } fixReplaceParameter.Value = fixReplacePrimaryKeyDescriptor.GetValue(record); // NOTE: If the SELECT returns no rows, Convert.ToInt64() will convert NULL to 0 castedRecord.ChangeNumber = Convert.ToInt64(fixReplaceCommand.ExecuteScalar()); } for (int i = 0; i < columnSet.Columns.Count; ++i) { var column = columnSet.Columns[i]; var parameter = command.Parameters[i]; var descriptor = descriptors[i]; object obj = descriptor.GetValue(record); parameter.Value = SQLiteMapperHelpers.ConvertObjectToSql(descriptor.PropertyType, obj); } int recordsModified = command.ExecuteNonQuery(); if (SQLiteMapper.DefaultLogLevel >= SQLiteMapperLogLevel.Verbose) { Debug.WriteLine("Inserted " + recordsModified + " rows"); } } transaction.Commit(); } } }); }