Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Object-Entity Mapping to DbfRecords #22

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions dBASE.NET.Tests/EntityMapperTests.cs
Original file line number Diff line number Diff line change
@@ -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<TestEntity>(dbf.GetEntities<TestEntity>());
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<TestEntity>();
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<TestEntity>(check.GetEntities<TestEntity>());

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);
}
}
}
4 changes: 4 additions & 0 deletions dBASE.NET.Tests/dBASE.NET.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<Compile Include="Encoders\NumericEncoderTests.cs" />
<Compile Include="FoxProWithMemo.cs" />
<Compile Include="DbfRecordTests.cs" />
<Compile Include="EntityMapperTests.cs" />
<Compile Include="VisualFoxPro.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="VisualFoxProWithAI.cs" />
Expand Down Expand Up @@ -265,6 +266,9 @@
<Content Include="fixtures\fields\POST986.dbf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="fixtures\mapper\mapper.dbf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\dBASE.NET\dBASE.NET.csproj">
Expand Down
Binary file added dBASE.NET.Tests/fixtures/mapper/mapper.dbf
Binary file not shown.
44 changes: 44 additions & 0 deletions dBASE.NET/Dbf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,50 @@ public DbfRecord CreateRecord()
return record;
}

public DbfRecord CreateRecord<T>(T entity)
{
var record = CreateRecord();
record.FromEntity(entity);
return record;
}

/// <summary>
/// Add a list of entities to the DBF.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entities"></param>
/// <returns></returns>
public IEnumerable<DbfRecord> AddEntities<T>(IEnumerable<T> entities)
{
var records = new List<DbfRecord>();

foreach (var entity in entities)
{
records.Add(CreateRecord(entity));
}

return records;
}

/// <summary>
/// Get records from the DBF mapped into entities.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public IEnumerable<T> GetEntities<T>()
{
var entities = new List<T>();

foreach (var record in Records)
{
var entity = (T) Activator.CreateInstance(typeof(T));
record.ToEntity(entity);
entities.Add(entity);
}

return entities;
}

/// <summary>
/// Opens a DBF file, reads the contents that initialize the current instance, and then closes the file.
/// </summary>
Expand Down
31 changes: 31 additions & 0 deletions dBASE.NET/DbfFieldAttribute.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Defines a field within an entity
/// </summary>
public class DbfFieldAttribute : Attribute
{
/// <summary>
/// Name of the property in the DBF
/// </summary>
public string Name { get; private set; }

/// <summary>
/// Create an attribute to be able to bind to a DBF field.
/// </summary>
/// <param name="name">
/// The name of the field. Can be omitted, in that case it takes the name of the property.
/// </param>
public DbfFieldAttribute([CallerMemberName] string name = null)
{
Name = name;
}
}
}
111 changes: 109 additions & 2 deletions dBASE.NET/DbfRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

/// <summary>
Expand Down Expand Up @@ -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];
}
Expand All @@ -74,12 +75,118 @@ public object this[DbfField field]
{
get
{
int index = fields.IndexOf(field);
int index = GetFieldIndex(field);
if (index == -1) return null;
return Data[index];
}
}

/// <summary>
/// Return the index of the field by its name.
/// </summary>
/// <param name="fieldName">Name of the field</param>
/// <returns></returns>
public int GetFieldIndex(string fieldName)
{
return fields.FindIndex(x => x.Name.ToLower().Equals(fieldName.ToLower()));
}

/// <summary>
/// Return the index of a field
/// </summary>
/// <param name="field">The field</param>
/// <returns></returns>
public int GetFieldIndex(DbfField field)
{
return fields.IndexOf(field);
}

/// <summary>
/// Return the list of fields in the current record.
/// </summary>
public List<DbfField> Fields
{
get
{
return fields;
}
}

/// <summary>
/// Fill values of the record from an entity.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
public void FromEntity<T>(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);
}
}
}

/// <summary>
/// Write values of the record into an entity.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
public void ToEntity<T>(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<PropertyInfo>();

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();
}

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
Expand Down