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

Annotations extensions #636

Open
wants to merge 7 commits into
base: main
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Runtime.CompilerServices;
using FluentNHibernate.Automapping.Alterations;
using FluentNHibernate.Automapping.Steps;
Expand Down Expand Up @@ -27,6 +29,10 @@ public virtual bool ShouldMap(Type type)

public virtual bool IsId(Member member)
{
if (member.MemberInfo.GetCustomAttribute<KeyAttribute>() != null)
{
return true;
}
return member.Name.Equals("id", StringComparison.InvariantCultureIgnoreCase);
}

Expand Down
6 changes: 6 additions & 0 deletions src/FluentNHibernate/Automapping/Steps/IdentityStep.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using FluentNHibernate.Mapping;
using FluentNHibernate.MappingModel;
using FluentNHibernate.MappingModel.ClassBased;
Expand Down Expand Up @@ -35,6 +37,8 @@ public void Map(ClassMappingBase classMap, Member member)
idMapping.Member = member;
idMapping.Set(x => x.Generator, Layer.Defaults, GetDefaultGenerator(member));

columnMapping.TryApplyAttributesFrom(member.MemberInfo,isIdColumn:true);

SetDefaultAccess(member, idMapping);

((ClassMapping)classMap).Set(x => x.Id, Layer.Defaults, idMapping);
Expand Down Expand Up @@ -67,6 +71,8 @@ GeneratorMapping GetDefaultGenerator(Member property)
else
defaultGenerator.Assigned();

generatorMapping.TryApply(property.MemberInfo.GetCustomAttribute<DatabaseGeneratedAttribute>());

return generatorMapping;
}
}
2 changes: 2 additions & 0 deletions src/FluentNHibernate/Automapping/Steps/PropertyStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ private PropertyMapping GetPropertyMapping(Type type, Member property)
mapping.Set(x => x.Name, Layer.Defaults, mapping.Member.Name);
mapping.Set(x => x.Type, Layer.Defaults, GetDefaultType(property));

columnMapping.TryApplyAttributesFrom(property.MemberInfo,isIdColumn:false);

SetDefaultAccess(property, mapping);

return mapping;
Expand Down
7 changes: 2 additions & 5 deletions src/FluentNHibernate/FluentNHibernate.csproj
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net461;netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net461;netcoreapp2.0;net6.0</TargetFrameworks>
<PlatformTarget>AnyCpu</PlatformTarget>
<OutputType>Library</OutputType>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../FluentKey.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NHibernate" Version="5.3.3" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, but I don't think I can accept this as a dependency.

</ItemGroup>

<ItemGroup>
<None Include="..\FluentKey.snk">
<Link>FluentKey.snk</Link>
</None>
</ItemGroup>

</Project>
197 changes: 197 additions & 0 deletions src/FluentNHibernate/FluentNHibernateAnnotationsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using System.Text;
using FluentNHibernate.MappingModel;
using FluentNHibernate.MappingModel.Identity;
using FluentNHibernate.Utils;
using NHibernate;
using NHibernate.Hql.Ast.ANTLR.Tree;
using NHibernate.Id;

namespace FluentNHibernate;

public static class FluentNHibernateAnnotationsExtensions
{
private static INHibernateLogger Log { get; set; }

private static void UsingLog(Action<INHibernateLogger> log)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be completely unrelated to the proposed changes

{
var currentLog = Log;
if (currentLog == null)
{
currentLog = NHibernateLogger.For(typeof(FluentNHibernateAnnotationsExtensions));
if (currentLog != null)
{
Log = currentLog;
log?.Invoke(currentLog);
}
}
else
{
log?.Invoke(currentLog);
}
}

private static void Debug(string format, params object[] args)
{
UsingLog(log => log.Debug(format, args));
}

/// <summary>
/// Try apply an attribute to target.
/// </summary>
/// <typeparam name="TTarget">Target type</typeparam>
/// <typeparam name="TAttribute">Attribute type</typeparam>
/// <param name="target">Target object</param>
/// <param name="attribute">Attribute object</param>
/// <param name="apply">Change the target object based on an attribute.
/// [Apply] function should return true, only when an attribute be applied or the target object be changed.
/// </param>
/// <returns>
/// Returns false where any arguments is null, otherwise return [Apply] function's result.
/// </returns>
public static bool TryApply<TTarget, TAttribute>(
TTarget target,
TAttribute attribute,
Func<TTarget, TAttribute, bool> apply
)
where TAttribute : Attribute
{
if (target == null || attribute == null || apply == null)
{
return false;
}
return apply.Invoke(target, attribute);
}

/// <summary>
/// Set ColumnMapping.NotNull when [Required] present.
/// <see cref="RequiredAttribute"/>
/// <see cref="ColumnMapping.NotNull"/>
/// </summary>
/// <param name="mapping">ColumnMapping</param>
/// <param name="attribute">RequiredAttribute</param>
/// <returns></returns>
public static bool TryApply(this ColumnMapping mapping, RequiredAttribute attribute)
{
return TryApply(
mapping,
attribute,
(mapping, attribute) =>
{
Debug(
"[{0}] TryApply(mapping={1}, attribute={2}) Set(NotNull=true)",
typeof(FluentNHibernateAnnotationsExtensions),
mapping,
attribute
);

mapping.Set(x => x.NotNull, Layer.Defaults, true);
return true;
}
);
}

/// <summary>
/// Set ColumnMapping.Length when [MaxLength] present.
/// </summary>
/// <see cref="ColumnMapping.Length"/>
/// <param name="mapping">ColumnMapping</param>
/// <param name="attribute">MaxLengthAttribute</param>
/// <returns></returns>
public static bool TryApply(this ColumnMapping mapping, MaxLengthAttribute attribute)
{
return TryApply(
mapping,
attribute,
(mapping, attribute) =>
{
Debug(
"[{0}] TryApply(mapping={1}, attribute={2}) Set(Length={3})",
typeof(FluentNHibernateAnnotationsExtensions),
mapping,
attribute,
attribute.Length
);

mapping.Set(x => x.Length, Layer.Defaults, attribute.Length);
return true;
}
);
}

/// <summary>
/// Configure GeneratorMapping.Class to [assinged] when [DatabaseGenerated(DatabaseGeneratedOption.None)] present.
/// Configure GeneratorMapping.Class to [identity] when [DatabaseGenerated(DatabaseGeneratedOption.Identity)] present.
/// </summary>
/// <see cref="GeneratorMapping.Class"/>
/// <see cref="DatabaseGeneratedAttribute"/>
/// <see cref="DatabaseGeneratedOption"/>
/// <param name="mapping">GeneratorMapping</param>
/// <param name="attribute">DatabaseGeneratedAttribute</param>
/// <returns></returns>
public static bool TryApply(this GeneratorMapping mapping, DatabaseGeneratedAttribute attribute)
{
return TryApply(
mapping,
attribute,
(mapping, attribute) =>
{
var option = attribute.DatabaseGeneratedOption;
if (option == DatabaseGeneratedOption.None)
{
Debug(
"[{0}] TryApply(mapping={1}, attribute={2}) Set(Class={3})",
typeof(FluentNHibernateAnnotationsExtensions),
mapping,
attribute,
"assigned"
);

mapping.Set(x => x.Class, Layer.Defaults, "assigned");
return true;
}
if (option == DatabaseGeneratedOption.Identity)
{
Debug(
"[{0}] TryApply(mapping={1}, attribute={2}) Set(Class={3})",
typeof(FluentNHibernateAnnotationsExtensions),
mapping,
attribute,
"identity"
);

mapping.Set(x => x.Class, Layer.Defaults, "identity");
return true;
}
return false;
}
);
}

public static void TryApplyAttributesFrom(
this ColumnMapping mapping,
MemberInfo memberInfo,
bool isIdColumn
)
{
//Prefer RequiredAttribute.
bool requiredApplied = mapping.TryApply(memberInfo.GetCustomAttribute<RequiredAttribute>());
//GetType().IsNullable.
if ((!requiredApplied) && (!memberInfo.GetType().IsNullable()))
{
RequiredAttribute requiredAttribute = new ();
Debug(
"[{0}] TryApply(mapping={1}, attribute={2}) Sender=TryApplyAttributesFrom Cause=!GetType().IsNullable()",
typeof(FluentNHibernateAnnotationsExtensions),
mapping,
requiredAttribute
);
mapping.TryApply(requiredAttribute);
}
mapping.TryApply(memberInfo.GetCustomAttribute<MaxLengthAttribute>());
}
}