Skip to content

Commit

Permalink
support mapping fields (not just properties)
Browse files Browse the repository at this point in the history
  • Loading branch information
ptixed committed Feb 6, 2025
1 parent 3d9f182 commit 9320fb1
Show file tree
Hide file tree
Showing 13 changed files with 74 additions and 59 deletions.
3 changes: 3 additions & 0 deletions Ptixed.Sql.Postgres/Ptixed.Sql.Postgres.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Npgsql" Version="8.0.3" />
</ItemGroup>
Expand Down
6 changes: 3 additions & 3 deletions Ptixed.Sql.SqlServer/Ptixed.Sql.SqlServer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<SignAssembly>False</SignAssembly>
<Title>Ptixed.Sql.SqlServer</Title>
<Version>1.0.0</Version>
<Version>1.1.0</Version>
<Description>Library for interacting with Microsoft SQL databases</Description>
<Authors>ptixed</Authors>
<RepositoryUrl>https://github.com/ptixed/Ptixed.Sql</RepositoryUrl>
<PackageTags>ptixed sql tsql database db</PackageTags>
<AssemblyVersion>1.0.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion>
<AssemblyVersion>1.1.0</AssemblyVersion>
<FileVersion>1.1.0</FileVersion>
<Company />
<PackageIconUrl>https://raw.githubusercontent.com/ptixed/Ptixed.Sql/master/logo-nuget.png</PackageIconUrl>
</PropertyGroup>
Expand Down
5 changes: 3 additions & 2 deletions Ptixed.Sql.Tests/Specimen/EnumToStringConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@
using System.Collections.Generic;
using System.Reflection;
using Ptixed.Sql.Meta;
using Ptixed.Sql.Util;

namespace Ptixed.Sql.Tests.Specimen
{
internal class EnumToStringConverter : ISqlConverter
{
public List<ColumnAttribute> GetColumns(PropertyInfo member)
public List<ColumnAttribute> GetColumns(MemberInfo member)
=> null;

public List<ColumnValue> ToQuery(object value, LogicalColumn meta)
=> new List<ColumnValue> { new ColumnValue(meta.PhysicalColumns[0].Name, value.ToString()) };

public object FromQuery(ColumnValueSet columns, LogicalColumn meta, MappingConfig mapping)
=> Enum.Parse(meta.Member.PropertyType, columns[meta.PhysicalColumns[0].Name].ToString());
=> Enum.Parse(meta.Member.GetMemberType(), columns[meta.PhysicalColumns[0].Name].ToString());
}
}
8 changes: 5 additions & 3 deletions Ptixed.Sql.Tests/Specimen/JsonSqlConverter.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Reflection;
using Newtonsoft.Json;
using Ptixed.Sql.Meta;
using Ptixed.Sql.Util;

namespace Ptixed.Sql.Tests.Specimen
{
Expand All @@ -10,13 +12,13 @@ internal class JsonSqlConverter : ISqlConverter
private readonly string _name;
public JsonSqlConverter(string name) => _name = name;

public List<ColumnAttribute> GetColumns(PropertyInfo member)
public List<ColumnAttribute> GetColumns(MemberInfo member)
=> new List<ColumnAttribute> { new ColumnAttribute(_name) };

public List<ColumnValue> ToQuery(object value, LogicalColumn meta)
=> new List<ColumnValue> { new ColumnValue(_name, JsonConvert.SerializeObject(value)) };

public object FromQuery(ColumnValueSet columns, LogicalColumn meta, MappingConfig mapping)
=> JsonConvert.DeserializeObject(columns[_name]?.ToString(), meta.Member.PropertyType);
=> JsonConvert.DeserializeObject(columns[_name]?.ToString(), meta.Member.GetMemberType());
}
}
2 changes: 1 addition & 1 deletion Ptixed.Sql.Tests/Specimen/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal class Model
[SqlConverter(typeof(JsonSqlConverter), "sub")]
public SubModelClass SubModel { get; set; }

public string SomeConstant => "SomeConstantValue";
public string SomeConstant = "SomeConstantValue";
public string DROP => "DROP";

[Column("created")]
Expand Down
2 changes: 1 addition & 1 deletion Ptixed.Sql/ISqlConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Ptixed.Sql
{
public interface ISqlConverter
{
List<ColumnAttribute> GetColumns(PropertyInfo member);
List<ColumnAttribute> GetColumns(MemberInfo member);

List<ColumnValue> ToQuery(object value, LogicalColumn meta);
object FromQuery(ColumnValueSet columns, LogicalColumn meta, MappingConfig mapping);
Expand Down
13 changes: 7 additions & 6 deletions Ptixed.Sql/Impl/CompositeColumnConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@
using System.Linq;
using System.Reflection;
using Ptixed.Sql.Meta;
using Ptixed.Sql.Util;

namespace Ptixed.Sql.Impl
{
public class CompositeColumnConverter : ISqlConverter
{
public List<ColumnAttribute> GetColumns(PropertyInfo member)
public List<ColumnAttribute> GetColumns(MemberInfo member)
{
var equals = member.PropertyType.GetMethod(nameof(Equals), new[] { typeof(object) });
var equals = member.GetMemberType().GetMethod(nameof(Equals), new[] { typeof(object) });
if (equals == null || equals.DeclaringType == typeof(object))
throw PtixedException.MissingImplementation(member.PropertyType, nameof(Equals));
throw PtixedException.MissingImplementation(member.GetMemberType(), nameof(Equals));

return Table.Get(member.PropertyType)
return Table.Get(member.GetMemberType())
.PhysicalColumns
.Select(x => new ColumnAttribute(x.Name)
{
Expand All @@ -24,15 +25,15 @@ public List<ColumnAttribute> GetColumns(PropertyInfo member)

public List<ColumnValue> ToQuery(object value, LogicalColumn meta)
{
return Table.Get(meta.Member.PropertyType)
return Table.Get(meta.Member.GetMemberType())
.LogicalColumns
.SelectMany(x => x.FromEntityToQuery(value))
.ToList();
}

public object FromQuery(ColumnValueSet columns, LogicalColumn meta, MappingConfig mapping)
{
var accessor = Table.Get(meta.Member.PropertyType);
var accessor = Table.Get(meta.Member.GetMemberType());
var ret = accessor.CreateNew();
foreach (var column in accessor.LogicalColumns)
accessor[ret, column] = column.FromQuery(columns, mapping);
Expand Down
2 changes: 1 addition & 1 deletion Ptixed.Sql/Meta/ColumnAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Ptixed.Sql.Meta
{
[AttributeUsage(AttributeTargets.Property)]
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class ColumnAttribute : Attribute
{
public readonly string ColumnName;
Expand Down
30 changes: 20 additions & 10 deletions Ptixed.Sql/Meta/LogicalColumn.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Collections.Generic;
using Ptixed.Sql.Impl;
using Ptixed.Sql.Util;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

Expand All @@ -8,11 +10,11 @@ public class LogicalColumn
{
public readonly Table Table;
public readonly PhysicalColumn[] PhysicalColumns;
public readonly PropertyInfo Member;
public readonly MemberInfo Member;
public readonly ISqlConverter Converter;
public readonly string Name;

private LogicalColumn(Table table, PropertyInfo member, ColumnAttribute column, ISqlConverter converter)
private LogicalColumn(Table table, MemberInfo member, ColumnAttribute column, ISqlConverter converter)
{
Table = table;
Member = member;
Expand All @@ -30,7 +32,7 @@ private LogicalColumn(Table table, PropertyInfo member, ColumnAttribute column,
PhysicalColumns = new[] { new PhysicalColumn(this, column?.ColumnName ?? Name, column?.IsAutoIncrement ?? false) };
}

public static LogicalColumn TryCreate(Table table, PropertyInfo member)
public static LogicalColumn TryCreate(Table table, MemberInfo member)
{
var attrs = member.GetCustomAttributes().ToList();

Expand All @@ -43,11 +45,19 @@ public static LogicalColumn TryCreate(Table table, PropertyInfo member)
return null;

if (column == null)
{
var ispublic = member.GetMethod?.IsPublic == true || member.SetMethod?.IsPublic == true;
if (!ispublic)
return null;
}
switch (member)
{
case PropertyInfo pi:
if (pi.GetMethod?.IsPublic != true && pi.SetMethod?.IsPublic != true)
return null;
break;
case FieldInfo fi:
if (!fi.IsPublic)
return null;
break;
default:
throw PtixedException.InvalidExpression(member);
}

return new LogicalColumn(table, member, column, converter?.CreateConverter());
}
Expand All @@ -64,7 +74,7 @@ public object FromQuery(ColumnValueSet columns, MappingConfig mapping)
{
if (Converter != null)
return Converter.FromQuery(columns, this, mapping);
return mapping.FromDb(Member.PropertyType, columns[PhysicalColumns[0].Name]);
return mapping.FromDb(Member.GetMemberType(), columns[PhysicalColumns[0].Name]);
}

public static bool operator ==(LogicalColumn l, LogicalColumn r) => l?.Equals(r) ?? ReferenceEquals(r, null);
Expand Down
31 changes: 21 additions & 10 deletions Ptixed.Sql/Meta/Relation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@
using System.Reflection;
using System.Reflection.Emit;
using Ptixed.Sql.Impl;
using Ptixed.Sql.Util;

namespace Ptixed.Sql.Meta
{
public class Relation
{
private readonly PropertyInfo _member;
private readonly MemberInfo _member;
private readonly Action<object, List<object>> _setter;

public readonly Type SlotType;
public readonly bool IsCollection;

private Relation(PropertyInfo member)
private Relation(MemberInfo member)
{
_member = member;

var undertype = member.PropertyType.GetInterfaces()
var undertype = member.GetMemberType().GetInterfaces()
.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))
?.GetGenericArguments()
.FirstOrDefault();
Expand All @@ -33,15 +34,15 @@ private Relation(PropertyInfo member)
}
else
{
SlotType = member.PropertyType;
SlotType = member.GetMemberType();
IsCollection = false;

var accessor = TypeAccessor.Get(member.DeclaringType);
_setter = (self, value) => accessor[self, member] = value.SingleOrDefault();
}
}

public static Relation TryCreate(PropertyInfo member)
public static Relation TryCreate(MemberInfo member)
{
var attrs = member.GetCustomAttributes();

Expand All @@ -55,23 +56,33 @@ public static Relation TryCreate(PropertyInfo member)

public void SetValue(object self, List<object> value) => _setter(self, value);

private static Action<object, List<object>> CreateSetter(PropertyInfo property, Type undertype)
private static Action<object, List<object>> CreateSetter(MemberInfo member, Type undertype)
{
var self = OpCodes.Ldarg_0;
var value = OpCodes.Ldarg_1;

var setter = new DynamicMethod(property.DeclaringType.FullName + "_" + property.Name + "_set", null, new[]
var setter = new DynamicMethod(member.DeclaringType.FullName + "_" + member.Name + "_set", null, new[]
{
typeof(object),
typeof(List<object>)
}, property.DeclaringType, true);
}, member.DeclaringType, true);
var il = setter.GetILGenerator();

il.Emit(self);
il.Emit(value);
il.Emit(OpCodes.Call, typeof(Enumerable).GetMethod(nameof(Enumerable.Cast)).MakeGenericMethod(undertype));
il.Emit(OpCodes.Newobj, property.PropertyType.GetConstructor(new [] { typeof(IEnumerable<>).MakeGenericType(undertype) }));
il.Emit(OpCodes.Callvirt, property.SetMethod);
il.Emit(OpCodes.Newobj, member.GetMemberType().GetConstructor(new [] { typeof(IEnumerable<>).MakeGenericType(undertype) }));
switch (member)
{
case FieldInfo fi:
il.Emit(OpCodes.Stfld, fi);
break;
case PropertyInfo pi:
il.Emit(OpCodes.Callvirt, pi.SetMethod);
break;
default:
throw PtixedException.InvalidExpression(member);
}
il.Emit(OpCodes.Ret);

return (Action<object, List<object>>)setter.CreateDelegate(typeof(Action<object, List<object>>));
Expand Down
4 changes: 3 additions & 1 deletion Ptixed.Sql/Meta/Table.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ private Table(Type type)

var lookup = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(x => x.GetIndexParameters().Length == 0)
.Cast<MemberInfo>()
.Concat(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
.Select(x => LogicalColumn.TryCreate(this, x))
.Where(x => x != null)
.ToDictionary<LogicalColumn, LogicalColumn, MemberInfo>(x => x, x => x.Member);
.ToDictionary(x => x, x => x.Member);
_accessor = new Accessor<LogicalColumn>(type, lookup);

var ctor = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
Expand Down
2 changes: 1 addition & 1 deletion Ptixed.Sql/Ptixed.Sql.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<SignAssembly>False</SignAssembly>
<Title>Ptixed.Sql</Title>
<Version>1.0.1</Version>
<Version>1.1.0</Version>
<Description>Library for interacting with SQL databases; base classes</Description>
<Authors>ptixed</Authors>
<RepositoryUrl>https://github.com/ptixed/Ptixed.Sql</RepositoryUrl>
Expand Down
25 changes: 5 additions & 20 deletions Ptixed.Sql/Util/Reflection.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,22 @@
using Ptixed.Sql.Impl;
using System.Linq.Expressions;
using System;
using System.Reflection;

namespace Ptixed.Sql.Util
{
internal static class Reflection
public static class Reflection
{
public static object GetValue(MemberInfo member, object owner)
public static Type GetMemberType(this MemberInfo member)
{
var accessor = TypeAccessor.Get(member.DeclaringType);
switch (member)
{
case FieldInfo fi:
return accessor[owner, fi];
return fi.FieldType;
case PropertyInfo pi:
return accessor[owner, pi];
return pi.PropertyType;
default:
throw PtixedException.InvalidExpression(member);
}
}

public static object Execute(Expression expr)
{
switch (expr)
{
case ConstantExpression ce:
return ce.Value;
case MemberExpression me:
var owner = Execute(me.Expression);
return GetValue(me.Member, owner);
default:
throw PtixedException.InvalidExpression(expr);
}
}
}
}

0 comments on commit 9320fb1

Please sign in to comment.