提交 6249b68d 编写于 作者: A Adam Schroder

Implement linear block identity generator

上级 dfc5e15e
......@@ -76,12 +76,12 @@ private async Task<object> InsertAsyncImp<T>(PocoData pocoData, string tableName
if (!autoIncrement)
{
await _dbType.ExecuteNonQueryAsync(this, cmd).ConfigureAwait(false);
id = InsertStatements.AssignNonIncrementPrimaryKey(primaryKeyName, poco, preparedInsert);
id = preparedInsert.GeneratedId != null ? preparedInsert.GeneratedId : InsertStatements.AssignNonIncrementPrimaryKey(primaryKeyName, poco, pocoData);
}
else
{
id = await _dbType.ExecuteInsertAsync(this, cmd, primaryKeyName, preparedInsert.PocoData.TableInfo.UseOutputClause, poco, preparedInsert.Rawvalues.ToArray()).ConfigureAwait(false);
InsertStatements.AssignPrimaryKey(primaryKeyName, poco, id, preparedInsert);
InsertStatements.AssignPrimaryKey(primaryKeyName, poco, id, pocoData);
}
return id;
......
......@@ -1464,12 +1464,12 @@ private object InsertImp<T>(PocoData pocoData, string tableName, string primaryK
if (!autoIncrement)
{
ExecuteNonQueryHelper(cmd);
id = InsertStatements.AssignNonIncrementPrimaryKey(primaryKeyName, poco, preparedInsert);
id = preparedInsert.GeneratedId != null ? preparedInsert.GeneratedId : InsertStatements.AssignNonIncrementPrimaryKey(primaryKeyName, poco, pocoData);
}
else
{
id = _dbType.ExecuteInsert(this, cmd, primaryKeyName, preparedInsert.PocoData.TableInfo.UseOutputClause, poco, preparedInsert.Rawvalues.ToArray());
InsertStatements.AssignPrimaryKey(primaryKeyName, poco, id, preparedInsert);
InsertStatements.AssignPrimaryKey(primaryKeyName, poco, id, pocoData);
}
return id;
......@@ -1962,6 +1962,18 @@ public IPocoDataFactory PocoDataFactory
set { _pocoDataFactory = value; }
}
private IIdentityGenerator _identityGenerator;
public IIdentityGenerator IdentityGenerator
{
get
{
if (_identityGenerator == null)
throw new NotImplementedException("No identityGenerator has been specified");
return _identityGenerator;
}
set { _identityGenerator = value; }
}
public string ConnectionString { get { return _connectionString; } }
// Member variables
......
......@@ -70,6 +70,11 @@ public void PrimaryKeysAutoIncremented(Func<Type, bool> primaryKeyAutoIncrementF
_scannerSettings.PrimaryKeysAutoIncremented = primaryKeyAutoIncrementFunc;
}
public void PrimaryKeysIdGenerated(Func<Type, bool> primaryKeyIdGeneratedFunc)
{
_scannerSettings.PrimaryKeysIdGenerated = primaryKeyIdGeneratedFunc;
}
//public void OverrideWithAttributes()
//{
// _scannerSettings.OverrideWithAttributes = true;
......
......@@ -23,6 +23,7 @@ public ConventionScannerSettings()
public Func<Type, string> TablesNamed { get; set; }
public Func<Type, string> PrimaryKeysNamed { get; set; }
public Func<Type, bool> PrimaryKeysAutoIncremented { get; set; }
public Func<Type, bool> PrimaryKeysIdGenerated { get; internal set; }
public Func<Type, string> SequencesNamed { get; set; }
public Func<Type, bool> UseOutputClauseWhere { get; set; }
public Func<Type, Type> PersistedTypesBy { get; set; }
......@@ -44,7 +45,7 @@ public ConventionScannerSettings()
public Func<MemberInfo, bool> SerializedWhere { get; set; }
public bool Lazy { get; set; }
//public bool OverrideWithAttributes { get; set; }
}
}
\ No newline at end of file
......@@ -56,6 +56,7 @@ private static Mappings CreateMappings(ConventionScannerSettings scannerSettings
PersistedType = scannerSettings.PersistedTypesBy(type),
SequenceName = scannerSettings.SequencesNamed(type),
UseOutputClause = scannerSettings.UseOutputClauseWhere(type),
UseIdGenerator = scannerSettings.PrimaryKeysIdGenerated(type),
ExplicitColumns = true
};
......@@ -159,6 +160,7 @@ private static ConventionScannerSettings ProcessSettings(Action<IConventionScann
var defaultScannerSettings = new ConventionScannerSettings
{
PrimaryKeysAutoIncremented = x => true,
PrimaryKeysIdGenerated = x => false,
PrimaryKeysNamed = x => "ID",
TablesNamed = x => x.Name,
DbColumnsNamed = x => x.Name,
......@@ -208,6 +210,8 @@ private static void MergeAttributeOverrides(Dictionary<Type, TypeDefinition> con
typeDefinition.Value.SequenceName = tableInfo.SequenceName;
typeDefinition.Value.AutoIncrement = tableInfo.AutoIncrement;
typeDefinition.Value.UseOutputClause = tableInfo.UseOutputClause;
typeDefinition.Value.UseIdGenerator = tableInfo.UseIdGenerator;
typeDefinition.Value.PersistedType = tableInfo.PersistedType;
foreach (var columnDefinition in typeDefinition.Value.ColumnConfiguration)
{
......@@ -249,6 +253,7 @@ private static void MergeOverrides(Dictionary<Type, TypeDefinition> config, Mapp
convTableDefinition.ExplicitColumns = overrideTypeDefinition.Value.ExplicitColumns ?? convTableDefinition.ExplicitColumns;
convTableDefinition.UseOutputClause = overrideTypeDefinition.Value.UseOutputClause ?? convTableDefinition.UseOutputClause;
convTableDefinition.PersistedType = overrideTypeDefinition.Value.PersistedType ?? convTableDefinition.PersistedType;
convTableDefinition.UseIdGenerator = overrideTypeDefinition.Value.UseIdGenerator ?? convTableDefinition.UseIdGenerator;
foreach (var overrideColumnDefinition in overrideMappings.Config[overrideTypeDefinition.Key].ColumnConfiguration)
{
......
......@@ -61,7 +61,9 @@ protected override TableInfoPlan GetTableInfo(Type type, ColumnInfo[] columnInfo
SequenceName = sequenceName,
AutoIncrement = autoIncrement,
AutoAlias = autoAlias,
PersistedType = typeConfig.PersistedType
PersistedType = typeConfig.PersistedType,
UseIdGenerator = typeConfig.UseIdGenerator ?? false,
UseOutputClause = typeConfig.UseOutputClause ?? false
};
}
......
......@@ -17,6 +17,7 @@ public interface IConventionScanner
void TablesNamed(Func<Type, string> tableFunc);
void PrimaryKeysNamed(Func<Type, string> primaryKeyFunc);
void PrimaryKeysAutoIncremented(Func<Type, bool> primaryKeyAutoIncrementFunc);
void PrimaryKeysIdGenerated(Func<Type, bool> primaryKeyIdGeneratedFunc);
void SequencesNamed(Func<Type, string> sequencesFunc);
void PersistedTypesBy(Func<Type, Type> persistedTypesByFunc);
......
......@@ -85,11 +85,12 @@ public Map<T> PrimaryKey(string primaryKeyColumn, bool autoIncrement)
return this;
}
public Map<T> PrimaryKey(string primaryKeyColumn, bool autoIncrement, bool useOutputClause)
public Map<T> PrimaryKey(string primaryKeyColumn, bool autoIncrement = false, bool useOutputClause = false, bool UseIdGenerator = false)
{
_petaPocoTypeDefinition.PrimaryKey = primaryKeyColumn;
_petaPocoTypeDefinition.AutoIncrement = autoIncrement;
_petaPocoTypeDefinition.UseOutputClause = useOutputClause;
_petaPocoTypeDefinition.UseIdGenerator = UseIdGenerator;
return this;
}
......
......@@ -20,5 +20,6 @@ public TypeDefinition(Type type)
public Dictionary<string, ColumnDefinition> ColumnConfiguration { get; set; }
public bool? UseOutputClause { get; set; }
public Type PersistedType { get; set; }
public bool? UseIdGenerator { get; set; }
}
}
\ No newline at end of file
......@@ -196,6 +196,11 @@ public interface IDatabase : IDisposable, IDatabaseQuery, IDatabaseConfig
/// Determines whether the POCO already exists
/// </summary>
bool IsNew<T>(T poco);
/// <summary>
/// If set, can be used to retrieve a new id or used implicitly when the UseIdGenerator is set to true on the Primary Key
/// </summary>
IIdentityGenerator IdentityGenerator { get; set; }
}
public interface IDatabaseConfig
......
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
namespace NPoco
{
public interface IIdentityGenerator
{
long Generate<T>();
long Generate(Type type);
}
public class LinearBlockIndentityGenerator : IIdentityGenerator
{
public string TableName { get; set; }
public string KeyColumn { get; set; }
public string ValueColumn { get; set; }
public long BlockSize { get; set; }
private static Dictionary<Type, Data> _data = new Dictionary<Type, Data>();
private static object _lock = new object();
private readonly Func<IDatabase> _databaseFactory;
public LinearBlockIndentityGenerator(Func<IDatabase> databaseFactory)
{
_databaseFactory = databaseFactory;
TableName = "NPocoIds";
KeyColumn = "id";
ValueColumn = "nextval";
BlockSize = 50;
}
public struct Data
{
public long allocNext;
public long allocHi;
}
public long Generate<T>()
{
return Generate(typeof(T));
}
public long Generate(Type type)
{
lock (_lock)
{
var data = _data.ContainsKey(type) ? _data[type] : new Data();
if (data.allocNext >= data.allocHi)
{
long allocated = AllocateBlock(type);
data.allocNext = allocated;
data.allocHi = allocated + BlockSize;
}
data.allocNext = data.allocNext + 1;
_data[type] = data;
return data.allocNext;
}
}
private long AllocateBlock(Type type)
{
long? result = 0;
int rows;
using (var db = _databaseFactory())
{
var pocoData = db.PocoDataFactory.ForType(type);
var querySql = string.Format("select {0} from {1} where {2} = @0", db.DatabaseType.EscapeSqlIdentifier(ValueColumn), db.DatabaseType.EscapeTableName(TableName), db.DatabaseType.EscapeSqlIdentifier(KeyColumn));
var updateSql = string.Format("update {0} set {1} = @0 where {2} = @1 and {1} = @2", db.DatabaseType.EscapeTableName(TableName), db.DatabaseType.EscapeSqlIdentifier(ValueColumn), db.DatabaseType.EscapeSqlIdentifier(KeyColumn));
var insertSql = string.Format("insert into {0} ({1}, {2}) select @0, @1 /*poco_dual*/ where not exists (select {1} from {0} where {1} = @0)", db.DatabaseType.EscapeTableName(TableName), db.DatabaseType.EscapeSqlIdentifier(KeyColumn), db.DatabaseType.EscapeSqlIdentifier(ValueColumn));
do
{
// The loop ensures atomicity of the select + update even for no transaction or read committed isolation level
try
{
result = db.ExecuteScalar<long?>(querySql, pocoData.TableInfo.TableName);
if (result == null)
{
db.Execute(insertSql, pocoData.TableInfo.TableName, 0);
result = 0;
}
rows = db.Execute(updateSql, result + BlockSize, pocoData.TableInfo.TableName, result);
}
catch (Exception sqle)
{
throw new Exception("Could not retrieve value", sqle);
}
} while (rows == 0);
}
return result.Value;
}
}
}
......@@ -16,6 +16,7 @@ public class PreparedInsertSql
public string VersionName { get; set; }
public string Sql { get; set; }
public List<object> Rawvalues { get; set; }
public long? GeneratedId { get; set; }
}
public static PreparedInsertSql PrepareInsertSql<T>(Database database, PocoData pd, string tableName, string primaryKeyName, bool autoIncrement, T poco)
......@@ -26,6 +27,8 @@ public static PreparedInsertSql PrepareInsertSql<T>(Database database, PocoData
var index = 0;
var versionName = "";
var newId = AssignIdGeneratedPrimaryKey(database.IdentityGenerator, pd, primaryKeyName, poco);
foreach (var pocoColumn in pd.Columns.Values)
{
// Don't insert result columns
......@@ -98,14 +101,15 @@ public static PreparedInsertSql PrepareInsertSql<T>(Database database, PocoData
PocoData = pd,
Sql = sql,
Rawvalues = rawvalues,
VersionName = versionName
VersionName = versionName,
GeneratedId = newId
};
}
public static object AssignNonIncrementPrimaryKey<T>(string primaryKeyName, T poco, PreparedInsertSql preparedSql)
public static object AssignNonIncrementPrimaryKey<T>(string primaryKeyName, T poco, PocoData pocoData)
{
PocoColumn pkColumn;
if (primaryKeyName != null && preparedSql.PocoData.Columns.TryGetValue(primaryKeyName, out pkColumn))
if (primaryKeyName != null && pocoData.Columns.TryGetValue(primaryKeyName, out pkColumn))
return pkColumn.GetValue(poco);
return null;
}
......@@ -122,17 +126,33 @@ public static void AssignVersion<T>(T poco, PreparedInsertSql preparedSql)
}
}
public static void AssignPrimaryKey<T>(string primaryKeyName, T poco, object id, PreparedInsertSql preparedSql)
public static void AssignPrimaryKey<T>(string primaryKeyName, T poco, object id, PocoData pocoData)
{
if (primaryKeyName != null && id != null && id.GetType().GetTypeInfo().IsValueType)
{
PocoColumn pc;
if (preparedSql.PocoData.Columns.TryGetValue(primaryKeyName, out pc))
if (pocoData.Columns.TryGetValue(primaryKeyName, out pc))
{
pc.SetValue(poco, pc.ChangeType(id));
}
}
}
public static long? AssignIdGeneratedPrimaryKey<T>(IIdentityGenerator identityGenerator, PocoData pocoData, string primaryKeyName, T poco)
{
if (pocoData.TableInfo.UseIdGenerator)
{
if (identityGenerator == null)
{
throw new NullReferenceException("IdentityGenerator on Database has not been set and UseIdGenerator has been used");
}
var newId = identityGenerator.Generate(pocoData.Type);
AssignPrimaryKey(primaryKeyName, poco, newId, pocoData);
return newId;
}
return null;
}
}
}
}
......@@ -15,5 +15,6 @@ public PrimaryKeyAttribute(string primaryKey)
public string SequenceName { get; set; }
public bool AutoIncrement { get; set; }
public bool UseOutputClause { get; set; }
public bool UseIdGenerator { get; set; }
}
}
\ No newline at end of file
......@@ -13,6 +13,7 @@ public class TableInfo
public string AutoAlias { get; set; }
public bool UseOutputClause { get; set; }
public Type PersistedType { get; set; }
public bool UseIdGenerator { get; set; }
public TableInfo Clone()
{
......@@ -24,7 +25,8 @@ public TableInfo Clone()
PrimaryKey = PrimaryKey,
SequenceName = SequenceName,
UseOutputClause = UseOutputClause,
PersistedType = PersistedType
PersistedType = PersistedType,
UseIdGenerator = UseIdGenerator
};
}
......@@ -40,11 +42,12 @@ public static TableInfo FromPoco(Type t)
a = t.GetTypeInfo().GetCustomAttributes(typeof(PrimaryKeyAttribute), true).ToArray();
tableInfo.PrimaryKey = a.Length == 0 ? "ID" : (a[0] as PrimaryKeyAttribute).Value;
tableInfo.SequenceName = a.Length == 0 ? null : (a[0] as PrimaryKeyAttribute).SequenceName;
tableInfo.UseIdGenerator = a.Length == 0 ? false : (a[0] as PrimaryKeyAttribute).UseIdGenerator;
tableInfo.AutoIncrement = a.Length == 0 ? true : (a[0] as PrimaryKeyAttribute).AutoIncrement;
tableInfo.UseOutputClause = a.Length == 0 ? false : (a[0] as PrimaryKeyAttribute).UseOutputClause;
// Set autoincrement false if primary key has multiple columns
tableInfo.AutoIncrement = tableInfo.AutoIncrement ? !tableInfo.PrimaryKey.Contains(',') : tableInfo.AutoIncrement;
tableInfo.AutoIncrement = tableInfo.AutoIncrement ? !(tableInfo.PrimaryKey.Contains(',') || tableInfo.UseIdGenerator) : tableInfo.AutoIncrement;
a = t.GetTypeInfo().GetCustomAttributes(typeof(PersistedTypeAttribute), true).ToArray();
tableInfo.PersistedType = a.Length == 0 ? null : (a[0] as PersistedTypeAttribute).PersistedType;
......
......@@ -38,7 +38,14 @@ public void SetUp()
case 2: // SQL Local DB
TestDatabase = new SQLLocalDatabase();
Database = new Database(TestDatabase.Connection, new SqlServer2008DatabaseType() { UseOutputClause = false }, IsolationLevel.ReadUncommitted); // Need read uncommitted for the transaction tests
var dbType = new SqlServer2008DatabaseType() { UseOutputClause = false };
Database = new Database(TestDatabase.Connection, dbType, IsolationLevel.ReadUncommitted)
{
IdentityGenerator = new LinearBlockIndentityGenerator(() => new Database(new SQLLocalDatabase(true).Connection, dbType))
{
BlockSize = 1000
}
}; // Need read uncommitted for the transaction tests
break;
case 3: // SQL Server
......
......@@ -9,4 +9,11 @@ public class GuidFromDb
public Guid Id { get; private set; }
public string Name { get; set; }
}
[TableName("IdGenerated"), PrimaryKey("Id", UseIdGenerator = true)]
public class InsertFromIdGenerator
{
public long Id { get; private set; }
public string Data { get; set; }
}
}
......@@ -15,7 +15,7 @@ public class SQLLocalDatabase : TestDatabase
protected string FQDBFile { get; set; }
protected string FQLogFile { get; set; }
public SQLLocalDatabase()
public SQLLocalDatabase(bool dontCreate = false)
{
DbType = new SqlServer2012DatabaseType();
DBPath = Directory.GetCurrentDirectory();
......@@ -26,17 +26,21 @@ public SQLLocalDatabase()
ConnectionString = String.Format("Data Source=(LocalDB)\\v11.0;Integrated Security=True;AttachDbFileName=\"{0}\";", FQDBFile);
ProviderName = "System.Data.SqlClient";
RecreateDataBase();
if (!dontCreate)
{
RecreateDataBase();
}
EnsureSharedConnectionConfigured();
// Console.WriteLine("Tables (Constructor): " + Environment.NewLine);
//#if !DNXCORE50
// var dt = ((SqlConnection)Connection).GetSchema("Tables");
// foreach (DataRow row in dt.Rows)
// {
// Console.WriteLine((string)row[2]);
// }
//#endif
// Console.WriteLine("Tables (Constructor): " + Environment.NewLine);
//#if !DNXCORE50
// var dt = ((SqlConnection)Connection).GetSchema("Tables");
// foreach (DataRow row in dt.Rows)
// {
// Console.WriteLine((string)row[2]);
// }
//#endif
}
public override void EnsureSharedConnectionConfigured()
......@@ -137,6 +141,14 @@ Address nvarchar(200)
";
cmd.ExecuteNonQuery();
cmd.CommandText = @"
CREATE TABLE IdGenerated(
Id int PRIMARY KEY NOT NULL,
Data nvarchar(512) NULL
);
";
cmd.ExecuteNonQuery();
cmd.CommandText = @"
CREATE TABLE ComplexMap(
Id int Identity(1,1) PRIMARY KEY NOT NULL,
......@@ -218,6 +230,16 @@ select @Name
";
cmd.ExecuteNonQuery();
cmd.CommandText = @"
create table NPocoIds(
id nvarchar(200) primary key,
nextval bigint
)
";
cmd.ExecuteNonQuery();
// Console.WriteLine("Tables (CreateDB): " + Environment.NewLine);
//#if !DNXCORE50
// var dt = conn.GetSchema("Tables");
......
......@@ -3,6 +3,7 @@
using NPoco;
using NPoco.Tests.Common;
using NUnit.Framework;
using System.Diagnostics;
namespace NPoco.Tests.DecoratedTests.CRUDTests
{
......@@ -92,7 +93,7 @@ FROM CompositeObjects
Assert.AreEqual(dataKey3ID, verify.Key3ID);
Assert.AreEqual(dataTextData, verify.TextData);
}
[Test]
public void VerifyNullablesCanBeInserted()
{
......@@ -114,7 +115,7 @@ public void InsertBatch()
Age = x + 1
});
Database.InsertBatch(users, new BatchOptions() {BatchSize = 22});
Database.InsertBatch(users, new BatchOptions() { BatchSize = 22 });
var result = Database.Query<UserDecorated>().Count();
......@@ -140,5 +141,24 @@ public void VerifyOnlyAutoGeneratedPrimaryKeyInTableIsSet()
var verify = Database.Single<JustPrimaryKey>("select * from justprimarykey");
Assert.AreEqual(user.Id, verify.Id);
}
[Test]
public void InsertFromIdGeneratorDbIsSet()
{
var user = new InsertFromIdGenerator();
user.Data = "TextData";
Database.Insert(user);
var user2 = new InsertFromIdGenerator();
user2.Data = "TextData2";
Database.Insert(user2);
var verify = Database.Query<InsertFromIdGenerator>().OrderBy(x => x.Id).ToList();
Assert.True(verify[0].Id > 0);
Assert.AreEqual(user.Id, verify[0].Id);
Assert.AreEqual(user.Data, verify[0].Data);
Assert.True(verify[1].Id > verify[0].Id);
Assert.AreEqual(user2.Id, verify[1].Id);
Assert.AreEqual(user2.Data, verify[1].Data);
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册