From f2cede987cc2334b7dd5f7bbca8d085c322e7d04 Mon Sep 17 00:00:00 2001 From: Mike Wertman Date: Tue, 6 May 2014 20:29:57 -0400 Subject: [PATCH 1/2] Fix the InMemoryAdapter so it properly cleans up the Detail/Master records on join table delete. Added a test for the scenario. --- Simple.Data.InMemoryTest/InMemoryTests.cs | 31 ++++++++++++++++++ Simple.Data/InMemoryAdapter.cs | 40 +++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/Simple.Data.InMemoryTest/InMemoryTests.cs b/Simple.Data.InMemoryTest/InMemoryTests.cs index d462583d..85509968 100644 --- a/Simple.Data.InMemoryTest/InMemoryTests.cs +++ b/Simple.Data.InMemoryTest/InMemoryTests.cs @@ -249,6 +249,37 @@ public void TestDeleteBy() Assert.IsNull(record); } + [Test] + public void TestFindAcrossJoinAfterDelete() + { + var adapter = new InMemoryAdapter(); + adapter.SetKeyColumn("Thing1Table", "Id"); + adapter.SetKeyColumn("Thing2Table", "Id"); + adapter.Join.Master("Thing1Table", "Id").Detail("ThingJoin", "Thing1Id"); + adapter.Join.Master("Thing2Table", "Id").Detail("ThingJoin", "Thing2Id"); + + Database.UseMockAdapter(adapter); + var db = Database.Open(); + + var thing1 = new {Id = 1, Name = "Thing1"}; + var thing2 = new {Id = 2, Name = "Thing2"}; + var thingJoin = new {Thing1Id = 1, Thing2Id = 2}; + + db.Thing1Table.Insert(thing1); + db.Thing2Table.Insert(thing2); + db.ThingJoin.Insert(thingJoin); + + //Delete the join object. + db.ThingJoin.DeleteAll(db.ThingJoin.Thing1Id == 1 && db.ThingJoin.Thing2Id == 2); + + //Ensure after we drop one of the relationships, there is only no records left in the join table. + Assert.AreEqual(0, db.ThingJoin.All().ToList().Count); + + //Ensure we don't find the Thing across the join after the delete + var foundByJoin = db.Thing1Table.FindAll(db.Thing1Table.ThingJoin.Thing1Id == 1).FirstOrDefault(); + Assert.IsNull(foundByJoin); + } + [Test] public void TestOrderBy() { diff --git a/Simple.Data/InMemoryAdapter.cs b/Simple.Data/InMemoryAdapter.cs index 0a82b0a3..167129d6 100644 --- a/Simple.Data/InMemoryAdapter.cs +++ b/Simple.Data/InMemoryAdapter.cs @@ -172,6 +172,44 @@ var detail in } } + private void DeleteAsDetail(string tableName, IDictionary data) + { + foreach (var @join in _joins.Where(j => j.DetailTableName.Equals(tableName, StringComparison.OrdinalIgnoreCase))) + { + if (!data.ContainsKey(@join.DetailKey)) continue; + foreach ( + var master in + GetTable(@join.MasterTableName).Where( + d => d.ContainsKey(@join.MasterKey) && d[@join.MasterKey].Equals(data[@join.DetailKey]))) + { + data[@join.MasterPropertyName] = master; + if (master.ContainsKey(@join.DetailPropertyName)) + { + ((List>)master[@join.DetailPropertyName]).Remove(data); + } + } + } + } + + private void DeleteAsMaster(string tableName, IDictionary data) + { + foreach (var @join in _joins.Where(j => j.MasterTableName.Equals(tableName, StringComparison.OrdinalIgnoreCase))) + { + if (!data.ContainsKey(@join.MasterKey)) continue; + foreach ( + var detail in + GetTable(@join.DetailTableName).Where( + d => d.ContainsKey(@join.DetailKey) && d[@join.DetailKey].Equals(data[@join.MasterKey]))) + { + detail[@join.MasterPropertyName] = data; + if (data.ContainsKey(@join.DetailPropertyName)) + { + ((List>)data[@join.DetailPropertyName]).Add(data); + } + } + } + } + public override int Update(string tableName, IDictionary data, SimpleExpression criteria) { int count = 0; @@ -196,6 +234,8 @@ public override int Delete(string tableName, SimpleExpression criteria) List> deletions = Find(tableName, criteria).ToList(); foreach (var record in deletions) { + DeleteAsDetail(tableName, record); + DeleteAsMaster(tableName, record); GetTable(tableName).Remove(record); } return deletions.Count; From e5569a23b6605f16df5588385973aba4fc70cd6d Mon Sep 17 00:00:00 2001 From: Mike Wertman Date: Thu, 8 May 2014 14:03:19 -0400 Subject: [PATCH 2/2] shared connection fixes for transactionscope --- Simple.Data.Ado.Test/ProviderHelperTest.cs | 10 ++++ Simple.Data.Ado.Test/TestCustomInserter.cs | 10 ++++ Simple.Data.Ado/AdoAdapter.cs | 1 + Simple.Data.Ado/IConnectionProvider.cs | 2 + .../Ado/MockConnectionProvider.cs | 10 ++++ .../SqlCe40ConnectionProvider.cs | 10 ++++ .../SqlConnectionProvider.cs | 13 +++++- Simple.Data.SqlServer/SqlSchemaProvider.cs | 46 +++++++++++++++++-- 8 files changed, 96 insertions(+), 6 deletions(-) diff --git a/Simple.Data.Ado.Test/ProviderHelperTest.cs b/Simple.Data.Ado.Test/ProviderHelperTest.cs index a5af8079..4e0af7bf 100644 --- a/Simple.Data.Ado.Test/ProviderHelperTest.cs +++ b/Simple.Data.Ado.Test/ProviderHelperTest.cs @@ -90,6 +90,16 @@ public IProcedureExecutor GetProcedureExecutor(AdoAdapter adapter, ObjectName pr throw new NotImplementedException(); } + public void SetSharedConnection(object sharedConnection) + { + throw new NotImplementedException(); + } + + public bool IsSharedConnection() + { + return false; + } + public Type RequestedServiceType { get; private set; } public Object GetService(Type serviceType) { diff --git a/Simple.Data.Ado.Test/TestCustomInserter.cs b/Simple.Data.Ado.Test/TestCustomInserter.cs index 5d8afd1e..f8ef3d12 100644 --- a/Simple.Data.Ado.Test/TestCustomInserter.cs +++ b/Simple.Data.Ado.Test/TestCustomInserter.cs @@ -64,6 +64,16 @@ public IProcedureExecutor GetProcedureExecutor(AdoAdapter adapter, ObjectName pr { throw new NotImplementedException(); } + + public void SetSharedConnection(object sharedConnection) + { + throw new NotImplementedException(); + } + + public bool IsSharedConnection() + { + return false; + } } public class StubSchemaProvider : ISchemaProvider diff --git a/Simple.Data.Ado/AdoAdapter.cs b/Simple.Data.Ado/AdoAdapter.cs index bd713a32..a95c55df 100644 --- a/Simple.Data.Ado/AdoAdapter.cs +++ b/Simple.Data.Ado/AdoAdapter.cs @@ -318,6 +318,7 @@ internal int Execute(ICommandBuilder commandBuilder, IDbTransaction dbTransactio public void UseSharedConnection(IDbConnection connection) { _sharedConnection = connection; + _connectionProvider.SetSharedConnection(connection); } public void StopUsingSharedConnection() diff --git a/Simple.Data.Ado/IConnectionProvider.cs b/Simple.Data.Ado/IConnectionProvider.cs index 103f54e9..3ad1be56 100644 --- a/Simple.Data.Ado/IConnectionProvider.cs +++ b/Simple.Data.Ado/IConnectionProvider.cs @@ -14,5 +14,7 @@ public interface IConnectionProvider string GetIdentityFunction(); bool SupportsStoredProcedures { get; } IProcedureExecutor GetProcedureExecutor(AdoAdapter adapter, ObjectName procedureName); + void SetSharedConnection(object sharedConnection); + bool IsSharedConnection(); } } \ No newline at end of file diff --git a/Simple.Data.Mocking/Ado/MockConnectionProvider.cs b/Simple.Data.Mocking/Ado/MockConnectionProvider.cs index a998ad92..3a664071 100644 --- a/Simple.Data.Mocking/Ado/MockConnectionProvider.cs +++ b/Simple.Data.Mocking/Ado/MockConnectionProvider.cs @@ -74,6 +74,16 @@ public IProcedureExecutor GetProcedureExecutor(AdoAdapter adapter, ObjectName pr return new ProcedureExecutor(adapter, procedureName); } + public void SetSharedConnection(object sharedConnection) + { + throw new NotImplementedException(); + } + + public bool IsSharedConnection() + { + return false; + } + private bool _supportsCompoundStatements = true; public bool SupportsCompoundStatements { diff --git a/Simple.Data.SqlCe40/SqlCe40ConnectionProvider.cs b/Simple.Data.SqlCe40/SqlCe40ConnectionProvider.cs index 9d2e0e9d..5e909b1e 100644 --- a/Simple.Data.SqlCe40/SqlCe40ConnectionProvider.cs +++ b/Simple.Data.SqlCe40/SqlCe40ConnectionProvider.cs @@ -74,5 +74,15 @@ public IProcedureExecutor GetProcedureExecutor(AdoAdapter adapter, ObjectName pr { throw new NotSupportedException("SQL Server Compact Edition does not support stored procedures."); } + + public void SetSharedConnection(object sharedConnection) + { + throw new NotImplementedException(); + } + + public bool IsSharedConnection() + { + return false; + } } } diff --git a/Simple.Data.SqlServer/SqlConnectionProvider.cs b/Simple.Data.SqlServer/SqlConnectionProvider.cs index c2c1b265..1a8261d1 100644 --- a/Simple.Data.SqlServer/SqlConnectionProvider.cs +++ b/Simple.Data.SqlServer/SqlConnectionProvider.cs @@ -14,6 +14,7 @@ namespace Simple.Data.SqlServer public class SqlConnectionProvider : IConnectionProvider { private string _connectionString; + private SqlConnection _sharedConnection; public SqlConnectionProvider() { @@ -27,7 +28,7 @@ public SqlConnectionProvider(string connectionString) public IDbConnection CreateConnection() { - return new SqlConnection(_connectionString); + return _sharedConnection ?? new SqlConnection(_connectionString); } public ISchemaProvider GetSchemaProvider() @@ -79,5 +80,15 @@ public IProcedureExecutor GetProcedureExecutor(AdoAdapter adapter, ObjectName pr { return new ProcedureExecutor(adapter, procedureName); } + + public void SetSharedConnection(object sharedConnection) + { + _sharedConnection = (SqlConnection) sharedConnection; + } + + public bool IsSharedConnection() + { + return _sharedConnection != null; + } } } diff --git a/Simple.Data.SqlServer/SqlSchemaProvider.cs b/Simple.Data.SqlServer/SqlSchemaProvider.cs index 9d022670..8633d67f 100644 --- a/Simple.Data.SqlServer/SqlSchemaProvider.cs +++ b/Simple.Data.SqlServer/SqlSchemaProvider.cs @@ -72,11 +72,22 @@ public IEnumerable GetStoredProcedures() private IEnumerable GetSchema(string collectionName, params string[] constraints) { - using (var cn = ConnectionProvider.CreateConnection()) + var cn = ConnectionProvider.CreateConnection() as SqlConnection; + try { - cn.Open(); + if (!ConnectionProvider.IsSharedConnection()) + cn.Open(); - return cn.GetSchema(collectionName, constraints).AsEnumerable(); + var schema = cn.GetSchema(collectionName, constraints).AsEnumerable(); + return schema; + } + finally + { + if (!ConnectionProvider.IsSharedConnection()) + { + cn.Close(); + cn.Dispose(); + } } } @@ -89,8 +100,13 @@ public IEnumerable GetParameters(Procedure storedProcedure) { // GetSchema does not return the return value of e.g. a stored proc correctly, // i.e. there isn't sufficient information to correctly set up a stored proc. - using (var connection = (SqlConnection)ConnectionProvider.CreateConnection()) + var connection = (SqlConnection) ConnectionProvider.CreateConnection(); + + try { + if (!ConnectionProvider.IsSharedConnection()) + connection.Open(); + using (var command = connection.CreateCommand()) { command.CommandType = CommandType.StoredProcedure; @@ -104,6 +120,14 @@ public IEnumerable GetParameters(Procedure storedProcedure) yield return new Parameter(p.ParameterName, SqlTypeResolver.GetClrType(p.DbType.ToString()), p.Direction, p.DbType, p.Size); } } + finally + { + if (!ConnectionProvider.IsSharedConnection()) + { + connection.Close(); + connection.Dispose(); + } + } } public Key GetPrimaryKey(Table table) @@ -208,8 +232,12 @@ private EnumerableRowCollection GetForeignKeys(string tableName) private DataTable SelectToDataTable(string sql, params SqlParameter[] parameters) { var dataTable = new DataTable(); - using (var cn = ConnectionProvider.CreateConnection() as SqlConnection) + var cn = ConnectionProvider.CreateConnection() as SqlConnection; + try { + if (!ConnectionProvider.IsSharedConnection()) + cn.Open(); + using (var adapter = new SqlDataAdapter(sql, cn)) { adapter.SelectCommand.Parameters.AddRange(parameters); @@ -217,6 +245,14 @@ private DataTable SelectToDataTable(string sql, params SqlParameter[] parameters } } + finally + { + if (!ConnectionProvider.IsSharedConnection()) + { + cn.Close(); + cn.Dispose(); + } + } return dataTable; }