From 2d3151eebb9e2bbd18fae75af9a5e87550efbe83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1rdi=20Mih=C3=A1ly?= Date: Mon, 14 Jun 2021 13:57:17 +0200 Subject: [PATCH 1/5] Added entity casting to DbfRecord. Added attribute that allows us to map a property to a field. --- dBASE.NET/DbfFieldAttribute.cs | 31 ++++++++++ dBASE.NET/DbfRecord.cs | 101 ++++++++++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 dBASE.NET/DbfFieldAttribute.cs diff --git a/dBASE.NET/DbfFieldAttribute.cs b/dBASE.NET/DbfFieldAttribute.cs new file mode 100644 index 0000000..02039b1 --- /dev/null +++ b/dBASE.NET/DbfFieldAttribute.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace dBASE.NET +{ + /// + /// Defines a field within an entity + /// + public class DbfFieldAttribute : Attribute + { + /// + /// Name of the property in the DBF + /// + public string Name { get; private set; } + + /// + /// Create an attribute to be able to bind to a DBF field. + /// + /// + /// The name of the field. Can be omitted, in that case it takes the name of the property. + /// + public DbfFieldAttribute([CallerMemberName] string name = null) + { + Name = name; + } + } +} diff --git a/dBASE.NET/DbfRecord.cs b/dBASE.NET/DbfRecord.cs index 551a0ab..e9734e6 100644 --- a/dBASE.NET/DbfRecord.cs +++ b/dBASE.NET/DbfRecord.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; + using System.Reflection; using System.Text; /// @@ -64,7 +65,7 @@ public object this[string name] { get { - int index = fields.FindIndex(x => x.Name.Equals(name)); + int index = GetFieldIndex(name); if (index == -1) return null; return Data[index]; } @@ -74,12 +75,108 @@ public object this[DbfField field] { get { - int index = fields.IndexOf(field); + int index = GetFieldIndex(field); if (index == -1) return null; return Data[index]; } } + /// + /// Return the index of the field by it's name. + /// + /// Name of the field + /// + public int GetFieldIndex(string fieldName) + { + return fields.FindIndex(x => x.Name.Equals(fieldName)); + } + + /// + /// Return the index of a field + /// + /// The field + /// + public int GetFieldIndex(DbfField field) + { + return fields.IndexOf(field); + } + + /// + /// Return the list of fields in the current record. + /// + public List Fields + { + get + { + return fields; + } + } + + public void FromEntity(T obj) + { + var properties = GetDecoratedProperties(obj); + + foreach (var property in properties) + { + var attribute = property.GetCustomAttribute(typeof(DbfFieldAttribute)) as DbfFieldAttribute; + + if (attribute == null) + { + throw new InvalidOperationException( + $"Property {property.Name} does not have the DbfField attribute!" + ); + } + + if (property.CanRead) + { + Data[GetFieldIndex(attribute.Name)] = property.GetValue(obj); + } + } + } + + public void ToEntity(T obj) + { + var properties = GetDecoratedProperties(obj); + + foreach (var property in properties) + { + var attribute = property.GetCustomAttribute(typeof(DbfFieldAttribute)) as DbfFieldAttribute; + + if(attribute == null) + { + throw new InvalidOperationException( + $"Property {property.Name} does not have the DbfField attribute!" + ); + } + + if(property.CanWrite) + { + property.SetValue(obj, Data[GetFieldIndex(attribute.Name)]); + } + } + } + + internal PropertyInfo[] GetDecoratedProperties(object obj) + { + var decoratedProperties = new List(); + + var allProperties = obj.GetType().GetProperties(); + + foreach (var property in allProperties) + { + var attributes = property.GetCustomAttributes(); + foreach (var attr in attributes) + { + if(attr is DbfFieldAttribute) + { + decoratedProperties.Add(property); + } + } + } + + return decoratedProperties.ToArray(); + } + /// /// Returns a string that represents the current object. /// From 110b23185b6e90eee78b63e46f5b9213ca265416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1rdi=20Mih=C3=A1ly?= Date: Mon, 14 Jun 2021 14:06:55 +0200 Subject: [PATCH 2/5] Added entity list mapping to Dbf. --- dBASE.NET/Dbf.cs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/dBASE.NET/Dbf.cs b/dBASE.NET/Dbf.cs index fb66fa7..f55484e 100644 --- a/dBASE.NET/Dbf.cs +++ b/dBASE.NET/Dbf.cs @@ -60,6 +60,43 @@ public DbfRecord CreateRecord() return record; } + /// + /// Add a list of entities to the DBF. + /// + /// + /// + /// + public IEnumerable AddEntities(IEnumerable entities) + { + var records = new List(); + + foreach (var entity in entities) + { + var record = CreateRecord(); + record.FromEntity(entity); + } + + return records; + } + + /// + /// Get records from the DBF mapped into entities. + /// + /// + /// + public IEnumerable GetEntities() + { + var entities = new List(); + + foreach (var record in Records) + { + var entity = (T) Activator.CreateInstance(typeof(T)); + record.ToEntity(entity); + } + + return entities; + } + /// /// Opens a DBF file, reads the contents that initialize the current instance, and then closes the file. /// From 22bbf7672f3f1be0a57be7c0202f0ddd556fee7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1rdi=20Mih=C3=A1ly?= Date: Mon, 14 Jun 2021 15:10:05 +0200 Subject: [PATCH 3/5] Added tests --- dBASE.NET.Tests/EntityMapperTests.cs | 125 +++++++++++++++++++++ dBASE.NET.Tests/dBASE.NET.Tests.csproj | 4 + dBASE.NET.Tests/fixtures/mapper/mapper.dbf | Bin 0 -> 511 bytes dBASE.NET/Dbf.cs | 11 +- dBASE.NET/DbfRecord.cs | 2 +- 5 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 dBASE.NET.Tests/EntityMapperTests.cs create mode 100644 dBASE.NET.Tests/fixtures/mapper/mapper.dbf diff --git a/dBASE.NET.Tests/EntityMapperTests.cs b/dBASE.NET.Tests/EntityMapperTests.cs new file mode 100644 index 0000000..0e490da --- /dev/null +++ b/dBASE.NET.Tests/EntityMapperTests.cs @@ -0,0 +1,125 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Text; + +namespace dBASE.NET.Tests +{ + internal class TestEntity + { + [DbfField] + public int ID { get; set; } + + [DbfField("FULLNAME")] + public string Name { get; set; } + + [DbfField("AGE")] + public int Age { get; set; } + + public bool Something { get; set; } + } + + [TestClass] + public class EntityMapperTests + { + internal Dbf CreateDbf() + { + var dbf = new Dbf(Encoding.GetEncoding(1250)); + var idField = new DbfField("ID", DbfFieldType.Integer, 4); + var nameField = new DbfField("FULLNAME", DbfFieldType.Character, 50); + var ageField = new DbfField("AGE", DbfFieldType.Integer, 4); + dbf.Fields.Add(idField); + dbf.Fields.Add(nameField); + dbf.Fields.Add(ageField); + return dbf; + } + + [TestMethod] + public void RecordFromEntity() + { + var entity = new TestEntity + { + ID = 1, + Age = 30, + Name = "John Doe", + Something = true + }; + + var dbf = CreateDbf(); + + var record = dbf.CreateRecord(entity); + + Assert.AreEqual(1, record["ID"]); + Assert.AreEqual(30, record["AGE"]); + Assert.AreEqual("John Doe", record["FULLNAME"]); + Assert.AreEqual(null, record["Something"]); + Assert.AreEqual(null, record["SOMETHING"]); + } + + [TestMethod] + public void EntityFromRecord() + { + var dbf = CreateDbf(); + var record = dbf.CreateRecord(); + record.Data[0] = 2; + record.Data[1] = "Jane Doe"; + record.Data[2] = 25; + + var entity = new TestEntity(); + record.ToEntity(entity); + + Assert.AreEqual(2, entity.ID); + Assert.AreEqual("Jane Doe", entity.Name); + Assert.AreEqual(25, entity.Age); + } + + [TestMethod] + public void EntityFromDbf() + { + var dbf = new Dbf(); + dbf.Read("fixtures/mapper/mapper.dbf"); + var entites = new List(dbf.GetEntities()); + Assert.AreEqual(2, entites.Count); + Assert.AreEqual(1, entites[0].ID); + Assert.AreEqual("John Doe", entites[0].Name); + Assert.AreEqual(30, entites[0].Age); + Assert.AreEqual(2, entites[1].ID); + Assert.AreEqual("Jane Doe", entites[1].Name); + Assert.AreEqual(25, entites[1].Age); + } + + [TestMethod] + public void EntityToDbf() + { + var dbf = CreateDbf(); + var entities = new List(); + entities.Add(new TestEntity + { + ID = 1, + Age = 12, + Name = "Bobby Doe" + }); + + entities.Add(new TestEntity + { + ID = 2, + Age = 14, + Name = "Stacy Doe" + }); + dbf.AddEntities(entities); + dbf.Write("mapper-test.dbf"); + + var check = new Dbf(); + check.Read("mapper-test.dbf"); + var entitiesFromDBf = new List(check.GetEntities()); + + Assert.AreEqual(2, entitiesFromDBf.Count); + Assert.AreEqual(1, entitiesFromDBf[0].ID); + Assert.AreEqual("Bobby Doe", entitiesFromDBf[0].Name); + Assert.AreEqual(12, entitiesFromDBf[0].Age); + Assert.AreEqual(2, entitiesFromDBf[1].ID); + Assert.AreEqual("Stacy Doe", entitiesFromDBf[1].Name); + Assert.AreEqual(14, entitiesFromDBf[1].Age); + } + } +} diff --git a/dBASE.NET.Tests/dBASE.NET.Tests.csproj b/dBASE.NET.Tests/dBASE.NET.Tests.csproj index 2f8d54b..6501f9c 100644 --- a/dBASE.NET.Tests/dBASE.NET.Tests.csproj +++ b/dBASE.NET.Tests/dBASE.NET.Tests.csproj @@ -70,6 +70,7 @@ + @@ -265,6 +266,9 @@ PreserveNewest + + Always + diff --git a/dBASE.NET.Tests/fixtures/mapper/mapper.dbf b/dBASE.NET.Tests/fixtures/mapper/mapper.dbf new file mode 100644 index 0000000000000000000000000000000000000000..bef761f390de853a215184a3ff2bdfac51de3dd9 GIT binary patch literal 511 zcmXpIW#eOFU|{HAv<8xB-~d0YYTp7V6{U=jiJS6mw<;5=O|9K(?bh xND2%*&4CoUeqN~LNT(D)f$No@k*DC2pQ=DKkOS&c0L7nIVqPj~S|x$HqyU8^7l;4= literal 0 HcmV?d00001 diff --git a/dBASE.NET/Dbf.cs b/dBASE.NET/Dbf.cs index f55484e..d03d90c 100644 --- a/dBASE.NET/Dbf.cs +++ b/dBASE.NET/Dbf.cs @@ -60,6 +60,13 @@ public DbfRecord CreateRecord() return record; } + public DbfRecord CreateRecord(T entity) + { + var record = CreateRecord(); + record.FromEntity(entity); + return record; + } + /// /// Add a list of entities to the DBF. /// @@ -72,8 +79,7 @@ public IEnumerable AddEntities(IEnumerable entities) foreach (var entity in entities) { - var record = CreateRecord(); - record.FromEntity(entity); + records.Add(CreateRecord(entity)); } return records; @@ -92,6 +98,7 @@ public IEnumerable GetEntities() { var entity = (T) Activator.CreateInstance(typeof(T)); record.ToEntity(entity); + entities.Add(entity); } return entities; diff --git a/dBASE.NET/DbfRecord.cs b/dBASE.NET/DbfRecord.cs index e9734e6..ed63742 100644 --- a/dBASE.NET/DbfRecord.cs +++ b/dBASE.NET/DbfRecord.cs @@ -82,7 +82,7 @@ public object this[DbfField field] } /// - /// Return the index of the field by it's name. + /// Return the index of the field by its name. /// /// Name of the field /// From c6e1acc6f651588f4b300d00fb80967a7a6fdea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1rdi=20Mih=C3=A1ly?= Date: Mon, 14 Jun 2021 15:11:14 +0200 Subject: [PATCH 4/5] Added summaries. --- dBASE.NET/DbfRecord.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dBASE.NET/DbfRecord.cs b/dBASE.NET/DbfRecord.cs index ed63742..8b9ded0 100644 --- a/dBASE.NET/DbfRecord.cs +++ b/dBASE.NET/DbfRecord.cs @@ -112,6 +112,11 @@ public List Fields } } + /// + /// Fill values of the record from an entity. + /// + /// + /// public void FromEntity(T obj) { var properties = GetDecoratedProperties(obj); @@ -134,6 +139,11 @@ public void FromEntity(T obj) } } + /// + /// Write values of the record into an entity. + /// + /// + /// public void ToEntity(T obj) { var properties = GetDecoratedProperties(obj); From c6cd28cff03fa6ea3011726fdb08a90bb66edacd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1rdi=20Mih=C3=A1ly?= Date: Mon, 14 Jun 2021 15:11:46 +0200 Subject: [PATCH 5/5] Make field name case insensitive --- dBASE.NET/DbfRecord.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dBASE.NET/DbfRecord.cs b/dBASE.NET/DbfRecord.cs index 8b9ded0..edcfb62 100644 --- a/dBASE.NET/DbfRecord.cs +++ b/dBASE.NET/DbfRecord.cs @@ -88,7 +88,7 @@ public object this[DbfField field] /// public int GetFieldIndex(string fieldName) { - return fields.FindIndex(x => x.Name.Equals(fieldName)); + return fields.FindIndex(x => x.Name.ToLower().Equals(fieldName.ToLower())); } ///