From 0f335617b4fe045b31bd3be105259377e9b2a6a7 Mon Sep 17 00:00:00 2001 From: Thibaud Date: Tue, 9 Jun 2020 00:12:01 +0200 Subject: [PATCH] first commit --- ConsoleApp1.sln | 16 ++ ConsoleApp1/Model/Person.cs | 13 ++ ConsoleApp1/Model/PersonMap.cs | 15 ++ ConsoleApp1/Model/PersonMapTest.cs | 23 +++ ConsoleApp1/Model/PersonTest.cs | 23 +++ ConsoleApp1/ORM/BulkQuery.cs | 173 ++++++++++++++++++ ConsoleApp1/ORM/NHibernateHelper.cs | 48 +++++ ConsoleApp1/ORM/SchemaTest.cs | 17 ++ ConsoleApp1/Repository/PersonRepository.cs | 87 +++++++++ .../Repository/PersonRepositoryTest.cs | 92 ++++++++++ ConsoleApp1/hibernate.cfg.xml | 9 + ...CoreApp,Version=v3.1.AssemblyAttributes.cs | 4 + .../netcoreapp3.1/ConsoleApp1.AssemblyInfo.cs | 22 +++ 13 files changed, 542 insertions(+) create mode 100644 ConsoleApp1.sln create mode 100644 ConsoleApp1/Model/Person.cs create mode 100644 ConsoleApp1/Model/PersonMap.cs create mode 100644 ConsoleApp1/Model/PersonMapTest.cs create mode 100644 ConsoleApp1/Model/PersonTest.cs create mode 100644 ConsoleApp1/ORM/BulkQuery.cs create mode 100644 ConsoleApp1/ORM/NHibernateHelper.cs create mode 100644 ConsoleApp1/ORM/SchemaTest.cs create mode 100644 ConsoleApp1/Repository/PersonRepository.cs create mode 100644 ConsoleApp1/Repository/PersonRepositoryTest.cs create mode 100644 ConsoleApp1/hibernate.cfg.xml create mode 100644 ConsoleApp1/obj/Debug/netcoreapp3.1/.NETCoreApp,Version=v3.1.AssemblyAttributes.cs create mode 100644 ConsoleApp1/obj/Debug/netcoreapp3.1/ConsoleApp1.AssemblyInfo.cs diff --git a/ConsoleApp1.sln b/ConsoleApp1.sln new file mode 100644 index 0000000..869a842 --- /dev/null +++ b/ConsoleApp1.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "ConsoleApp1\ConsoleApp1.csproj", "{C1AF7B05-8631-49ED-88F5-946446E9169F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C1AF7B05-8631-49ED-88F5-946446E9169F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1AF7B05-8631-49ED-88F5-946446E9169F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1AF7B05-8631-49ED-88F5-946446E9169F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1AF7B05-8631-49ED-88F5-946446E9169F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/ConsoleApp1/Model/Person.cs b/ConsoleApp1/Model/Person.cs new file mode 100644 index 0000000..c62e0e6 --- /dev/null +++ b/ConsoleApp1/Model/Person.cs @@ -0,0 +1,13 @@ +using System; + +namespace ConsoleApp1.Model +{ + public class Person + { + public virtual Guid Id { get; set; } + public virtual string FirstName { get; set; } + public virtual string LastName { get; set; } + + public virtual string GetFullName() => $"{FirstName} {LastName}"; + } +} \ No newline at end of file diff --git a/ConsoleApp1/Model/PersonMap.cs b/ConsoleApp1/Model/PersonMap.cs new file mode 100644 index 0000000..19d01f1 --- /dev/null +++ b/ConsoleApp1/Model/PersonMap.cs @@ -0,0 +1,15 @@ +using NHibernate.Mapping.ByCode; +using NHibernate.Mapping.ByCode.Conformist; + +namespace ConsoleApp1.Model +{ + public class PersonMap : ClassMapping + { + public PersonMap() + { + Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + Property(x => x.FirstName); + Property(x => x.LastName); + } + } +} \ No newline at end of file diff --git a/ConsoleApp1/Model/PersonMapTest.cs b/ConsoleApp1/Model/PersonMapTest.cs new file mode 100644 index 0000000..12681bf --- /dev/null +++ b/ConsoleApp1/Model/PersonMapTest.cs @@ -0,0 +1,23 @@ +using System; +using System.Xml.Serialization; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace ConsoleApp1.Model +{ + [TestFixture] + public class PersonMapTest + { + [Test] + public void CanGenerateXmlMapping() + { + var mapper = new ModelMapper(); + mapper.AddMapping(); + + var mapping = mapper.CompileMappingForAllExplicitlyAddedEntities(); + var xmlSerializer = new XmlSerializer(mapping.GetType()); + + xmlSerializer.Serialize(Console.Out, mapping); + } + } +} \ No newline at end of file diff --git a/ConsoleApp1/Model/PersonTest.cs b/ConsoleApp1/Model/PersonTest.cs new file mode 100644 index 0000000..0cab9b4 --- /dev/null +++ b/ConsoleApp1/Model/PersonTest.cs @@ -0,0 +1,23 @@ +using NUnit.Framework; + +namespace ConsoleApp1.Model +{ + [TestFixture] + public class PersonTest + { + [Test] + public void GetFullNameTest() + { + var person = new Person + { + FirstName = "Test", + LastName = "Kees" + }; + + Assert.AreEqual("Test", person.FirstName); + Assert.AreEqual("Kees", person.LastName); + + Assert.AreEqual("Test Kees", person.GetFullName()); + } + } +} \ No newline at end of file diff --git a/ConsoleApp1/ORM/BulkQuery.cs b/ConsoleApp1/ORM/BulkQuery.cs new file mode 100644 index 0000000..128bf5a --- /dev/null +++ b/ConsoleApp1/ORM/BulkQuery.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; +using ConsoleApp1.Model; + +namespace ConsoleApp1.ORM +{ + public class BulkQuery + { + public static void BulkInsert(IEnumerable entries) + { + var entityType = typeof(TEntity); + var properties = entityType.GetProperties().Select(p => p.Name); + BuildBulkInsertQuery(entityType.Name, properties, entries); + } + + private static void GetParametersValues(string tableName, IEnumerable propertyNames, IEnumerable entries) + { + static object GetPropertyValue(object o, string propertyName) + { + //Debug.Assert(propertyName != null, nameof(propertyName) + " != null"); + var type = o.GetType(); + var property = type.GetProperty(propertyName); + //Debug.Assert(property != null, nameof(property) + " != null"); + var propertyValue = property?.GetValue(o); + return propertyValue; + } + + var fields = propertyNames as string[] ?? propertyNames.ToArray(); + var i = 0; + foreach (var entry in entries) + { + ++i; + Console.WriteLine($"Entry number {i}"); + foreach (var propertyName in fields) + { + var propertyValue = GetPropertyValue(entry, propertyName); + Console.WriteLine($"Property name={propertyName}, value={propertyValue}"); + } + Console.WriteLine(); + } + } + + private static void BuildBulkInsertQuery(string tableName, + IEnumerable propertyNames, + IEnumerable entries, + int maxBatchSize = 5) + { + using var session = NHibernateHelper.OpenSession(); + var cmd = session.Connection.CreateCommand(); + var columns = propertyNames as string[] ?? propertyNames.ToArray(); + var queryString = $"INSERT INTO {tableName} ({string.Join(',', columns)}) VALUES\n"; + var sb = new StringBuilder(); + + DbParameter CreateDbParameter(object value) + { + var pm = cmd.CreateParameter(); + pm.Value = value; + return pm; + } + + static object GetPropertyValue(object o, string propertyName) => o.GetType().GetProperty(propertyName)?.GetValue(o); + + var batchSize = 0; + var p = 0; + foreach (var entry in entries) + { + // Build the names of the parameters + var row = new StringBuilder("("); + for (var ii = 0; ii < columns.Length; ii++) + { + row.Append($"@p{p + ii}"); + if (ii < columns.Length - 1) row.Append(","); + + // Set parameter value + var value = GetPropertyValue(entry, columns[ii]); + cmd.Parameters.Add(CreateDbParameter(value)); + } + p += columns.Length; + row.Append(")"); + + // Add the row to our running SQL batch + if (batchSize > 0) sb.AppendLine(","); + sb.Append(row); + batchSize += 1; + + if (batchSize >= maxBatchSize) + { + var queryStringComplete = queryString + sb + ";"; + cmd.CommandText = queryStringComplete; + cmd.ExecuteNonQuery(); + cmd.Parameters.Clear(); + sb.Clear(); + batchSize = 0; + p = 1; + } + } + + // handle the last few stragglers + if (batchSize > 0) + { + var queryStringComplete = queryString + sb + ";"; + cmd.CommandText = queryStringComplete; + cmd.ExecuteNonQuery(); + } + } + + public static void BulkInsert1(IEnumerable persons) + { + using var session = NHibernateHelper.OpenSession(); + var cmd = session.Connection.CreateCommand(); + + var queryString = "INSERT INTO Person (Id, FirstName, LastName) VALUES\n"; + var sb = new StringBuilder(); + + var batchSize = 0; + var p = 1; //the current paramter name (i.e. "@p1") we're going to use + + DbParameter CreateDbParameter(object value) + { + var pm = cmd.CreateParameter(); + pm.Value = value; + return pm; + } + + foreach (var person in persons) + { + //Build the names of the parameters + var pId = $"@p{p}"; //the "Id" parameter name (i.e. "p1") + var pFirstName = $"@p{p + 1}"; + var pLastName = $"@p{p + 2}"; + p += 3; + + //Build a single "(p1, p2)" row + var row = $"({pId}, {pFirstName}, {pLastName})"; //a single values tuple + + //Add the row to our running SQL batch + if (batchSize > 0) sb.AppendLine(","); + sb.Append(row); + batchSize += 1; + + // Add the parameter values for this row + cmd.Parameters.AddRange(new[] { + CreateDbParameter(person.Id), + CreateDbParameter(person.FirstName), + CreateDbParameter(person.LastName) + }); + + if (batchSize >= 5) + { + var queryStringComplete = queryString + sb; + cmd.CommandText = queryStringComplete; + cmd.ExecuteNonQuery(); + cmd.Parameters.Clear(); + sb.Clear(); + batchSize = 0; + p = 1; + } + } + + //handle the last few stragglers + if (batchSize > 0) + { + var queryStringComplete = queryString + sb; + cmd.CommandText = queryStringComplete; + cmd.ExecuteNonQuery(); + } + } + } +} \ No newline at end of file diff --git a/ConsoleApp1/ORM/NHibernateHelper.cs b/ConsoleApp1/ORM/NHibernateHelper.cs new file mode 100644 index 0000000..03d6bc7 --- /dev/null +++ b/ConsoleApp1/ORM/NHibernateHelper.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using ConsoleApp1.Model; +using NHibernate; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; + +namespace ConsoleApp1.ORM +{ + public static class NHibernateHelper + { + private static ISessionFactory _sessionFactory; + private static Configuration _configuration; + private static HbmMapping _mapping; + + public static ISession OpenSession() + { + //Open and return the nhibernate session + return SessionFactory.OpenSession(); + } + + public static ISessionFactory SessionFactory => _sessionFactory ??= Configuration.BuildSessionFactory(); + + public static Configuration Configuration => _configuration ??= CreateConfiguration(); + + public static HbmMapping Mapping => _mapping ??= CreateMapping(); + + private static Configuration CreateConfiguration() + { + var configuration = new Configuration(); + //Loads properties from hibernate.cfg.xml + configuration.Configure(); + //Loads nhibernate mappings + configuration.AddDeserializedMapping(Mapping, null); + + return configuration; + } + + private static HbmMapping CreateMapping() + { + var mapper = new ModelMapper(); + //Add the person mapping to the model mapper + mapper.AddMappings(new List { typeof(PersonMap) }); + //Create and return a HbmMapping of the model mapping in code + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + } +} \ No newline at end of file diff --git a/ConsoleApp1/ORM/SchemaTest.cs b/ConsoleApp1/ORM/SchemaTest.cs new file mode 100644 index 0000000..1e1feb8 --- /dev/null +++ b/ConsoleApp1/ORM/SchemaTest.cs @@ -0,0 +1,17 @@ +using System; +using NHibernate.Tool.hbm2ddl; +using NUnit.Framework; + +namespace ConsoleApp1.ORM +{ + [TestFixture] + public class SchemaTest + { + [Test] + public void CanGenerateSchema() + { + var schemaUpdate = new SchemaUpdate(NHibernateHelper.Configuration); + schemaUpdate.Execute(Console.WriteLine, true); + } + } +} \ No newline at end of file diff --git a/ConsoleApp1/Repository/PersonRepository.cs b/ConsoleApp1/Repository/PersonRepository.cs new file mode 100644 index 0000000..a8f48fd --- /dev/null +++ b/ConsoleApp1/Repository/PersonRepository.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using ConsoleApp1.Model; +using ConsoleApp1.ORM; + +namespace ConsoleApp1.Repository +{ + public interface PersonRepository + { + /// + /// Get person entity by id + /// + /// id + /// person + Person Get(Guid id); + + /// + /// Save person entity + /// + /// person + void Save(Person person); + + /// + /// Update person entity + /// + /// person + void Update(Person person); + + /// + /// Delete person entity + /// + /// person + void Delete(Person person); + + /// + /// Row count person in db + /// + /// number of rows + long RowCount(); + + void Save(IEnumerable persons); + } + + public class PersonRepositoryNHibernate : PersonRepository + { + public void Save(IEnumerable persons) + { + BulkQuery.BulkInsert(persons); + } + + public void Save(Person person) + { + using var session = NHibernateHelper.OpenSession(); + using var transaction = session.BeginTransaction(); + session.Save(person); + transaction.Commit(); + } + + public Person Get(Guid id) + { + using var session = NHibernateHelper.OpenSession(); + return session.Get(id); + } + + public void Update(Person person) + { + using var session = NHibernateHelper.OpenSession(); + using var transaction = session.BeginTransaction(); + session.Update(person); + transaction.Commit(); + } + + public void Delete(Person person) + { + using var session = NHibernateHelper.OpenSession(); + using var transaction = session.BeginTransaction(); + session.Delete(person); + transaction.Commit(); + } + + public long RowCount() + { + using var session = NHibernateHelper.OpenSession(); + return session.QueryOver().RowCountInt64(); + } + } +} \ No newline at end of file diff --git a/ConsoleApp1/Repository/PersonRepositoryTest.cs b/ConsoleApp1/Repository/PersonRepositoryTest.cs new file mode 100644 index 0000000..7a64a00 --- /dev/null +++ b/ConsoleApp1/Repository/PersonRepositoryTest.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using ConsoleApp1.Model; +using ConsoleApp1.ORM; +using NHibernate.Tool.hbm2ddl; +using NUnit.Framework; + +namespace ConsoleApp1.Repository +{ + [TestFixture] + public class NHibernatePersonRepositoryTest + { + private PersonRepository _personRepo; + + [SetUp] + public void CreateSchema() + { + DeleteDatabaseIfExists(); + + var schemaUpdate = new SchemaUpdate(NHibernateHelper.Configuration); + schemaUpdate.Execute(false, true); + + _personRepo = new PersonRepositoryNHibernate(); + } + + [Test] + public void CanSavePerson() + { + _personRepo.Save(new Person()); + Assert.AreEqual(1, _personRepo.RowCount()); + } + + [Test] + public void CanGetPerson() + { + var person = new Person(); + _personRepo.Save(person); + Assert.AreEqual(1, _personRepo.RowCount()); + + person = _personRepo.Get(person.Id); + Assert.IsNotNull(person); + } + + [Test] + public void CanUpdatePerson() + { + var person = new Person(); + _personRepo.Save(person); + Assert.AreEqual(1, _personRepo.RowCount()); + + person = _personRepo.Get(person.Id); + person.FirstName = "Test"; + _personRepo.Update(person); + + Assert.AreEqual(1, _personRepo.RowCount()); + Assert.AreEqual("Test", _personRepo.Get(person.Id).FirstName); + } + + [Test] + public void CanDeletePerson() + { + var person = new Person(); + _personRepo.Save(person); + Assert.AreEqual(1, _personRepo.RowCount()); + + _personRepo.Delete(person); + Assert.AreEqual(0, _personRepo.RowCount()); + } + + [Test] + public void TestBulkInsert() + { + static IEnumerable GeneratePersons(int num = 3) + { + var ret = new Person[num]; + for (var ii = 0; ii < num; ii++) ret[ii] = new Person {Id = Guid.NewGuid(), FirstName = "bac", LastName = "fif"}; + return ret; + } + + _personRepo.Save(GeneratePersons(10)); + } + + [TearDown] + public void DeleteDatabaseIfExists() + { + if (File.Exists("test.db")) + File.Delete("test.db"); + } + } +} \ No newline at end of file diff --git a/ConsoleApp1/hibernate.cfg.xml b/ConsoleApp1/hibernate.cfg.xml new file mode 100644 index 0000000..9393dfc --- /dev/null +++ b/ConsoleApp1/hibernate.cfg.xml @@ -0,0 +1,9 @@ + + + + NHibernate.Driver.SQLite20Driver + Data Source=test.db;Version=3;New=True + NHibernate.Dialect.SQLiteDialect + true + + \ No newline at end of file diff --git a/ConsoleApp1/obj/Debug/netcoreapp3.1/.NETCoreApp,Version=v3.1.AssemblyAttributes.cs b/ConsoleApp1/obj/Debug/netcoreapp3.1/.NETCoreApp,Version=v3.1.AssemblyAttributes.cs new file mode 100644 index 0000000..ad8dfe1 --- /dev/null +++ b/ConsoleApp1/obj/Debug/netcoreapp3.1/.NETCoreApp,Version=v3.1.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v3.1", FrameworkDisplayName = "")] diff --git a/ConsoleApp1/obj/Debug/netcoreapp3.1/ConsoleApp1.AssemblyInfo.cs b/ConsoleApp1/obj/Debug/netcoreapp3.1/ConsoleApp1.AssemblyInfo.cs new file mode 100644 index 0000000..2899ba6 --- /dev/null +++ b/ConsoleApp1/obj/Debug/netcoreapp3.1/ConsoleApp1.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("ConsoleApp1")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("ConsoleApp1")] +[assembly: System.Reflection.AssemblyTitleAttribute("ConsoleApp1")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Généré par la classe MSBuild WriteCodeFragment. +