// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LLBLGenExtensions.cs" company="Catel development team">
// Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
// </copyright>
// <summary>
// Extension methods for LLBLGen Pro.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Catel.Properties;
using Catel.Reflection;
using log4net;
namespace Catel.LLBLGen
{
/// <summary>
/// Extension methods for LLBLGen Pro.
/// </summary>
/// <remarks>
/// This class extensively use reflection so no actual reference to LLBLGen is required.
/// </remarks>
public static class LLBLGenExtensions
{
#region Variables
/// <summary>
/// The <see cref="ILog">log</see> object.
/// </summary>
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
#endregion
/// <summary>
/// Determines whether the specified object is an LLBLGen type (entity or collection).
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns>
/// <c>true</c> if the specified object is an LLBLGen type; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// This method uses the <see cref="IsEntityCollection"/> and <see cref="IsEntity"/> methods.
/// </remarks>
public static bool IsLLBLGenType(this object entity)
{
return LLBLGenHelper.IsLLBLGenType(entity);
}
/// <summary>
/// Determines whether the specified object is an LLBLGen entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns>
/// <c>true</c> if the specified object is an LLBLGen entity; otherwise, <c>false</c>.
/// </returns>
public static bool IsEntity(this object entity)
{
return LLBLGenHelper.IsEntity(entity);
}
/// <summary>
/// Determines whether the specified object is an LLBLGen entity collection.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns>
/// <c>true</c> if the specified object is an LLBLGen entity collection; otherwise, <c>false</c>.
/// </returns>
public static bool IsEntityCollection(this object entity)
{
return LLBLGenHelper.IsEntityCollection(entity);
}
/// <summary>
/// Validates the entity.
/// </summary>
/// <param name="entity">The entity.</param>
public static void ValidateEntity(this object entity)
{
MethodInfo validateEntity = LLBLGenHelper.GetValidateEntityMethod(entity);
if (validateEntity != null)
{
validateEntity.Invoke(entity, null);
}
}
/// <summary>
/// Saves the fields.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="name">The name of the saved fields.</param>
public static void SaveFields(this object entity, string name)
{
MethodInfo saveFields = LLBLGenHelper.GetSaveFieldsMethod(entity);
if (saveFields != null)
{
saveFields.Invoke(entity, new object[] { name });
}
}
/// <summary>
/// Rollbacks the fields.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="name">The name of the saved fields.</param>
public static void RollbackFields(this object entity, string name)
{
MethodInfo rollbackFieldsMethod = LLBLGenHelper.GetRollbackFieldsMethod(entity);
if (rollbackFieldsMethod != null)
{
rollbackFieldsMethod.Invoke(entity, new object[] { name });
}
}
/// <summary>
/// Rollbacks the fields, and for all the dirty fields that are rolled back, the relations are refetched as well.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="name">The name of the saved fields.</param>
public static void RollbackFieldsAndRelations(this object entity, string name)
{
string[] dirtyFields = entity.GetModifiedFieldsForSavedFields(name);
foreach (string dirtyField in dirtyFields)
{
object relation = entity.GetEntityRelationForField(dirtyField);
if (relation != null)
{
if (!SetRelationToNotFetched(entity, relation))
{
Log.Warn(TraceMessages.FailedToRefetchRelationForField, dirtyField);
}
}
}
entity.RollbackFields(name);
}
/// <summary>
/// Discards the saved fields.
/// </summary>
/// <param name="entity">The entity.</param>
public static void DiscardSavedFields(this object entity)
{
MethodInfo discardSavedFields = LLBLGenHelper.GetDiscardSavedFieldsMethod(entity);
if (discardSavedFields != null)
{
discardSavedFields.Invoke(entity, null);
}
}
/// <summary>
/// Refetches the specified object.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns><c>true</c> if succeeded; otherwise <c>false</c>.</returns>
public static bool Refetch(this object entity)
{
MethodInfo refetch = LLBLGenHelper.GetRefetchMethod(entity);
return (refetch != null) ? (bool)refetch.Invoke(entity, null) : false;
}
/// <summary>
/// Gets the modified fields for saved fields.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="name">The name of the saved fields.</param>
/// <returns>Array of field names that have changed.</returns>
/// <exception cref="ArgumentNullException">when <paramref name="entity"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException">when <paramref name="name"/> is <c>null</c>.</exception>
public static string[] GetModifiedFieldsForSavedFields(this object entity, string name)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
if (name == null)
{
throw new ArgumentNullException("name");
}
List<string> changedFields = new List<string>();
Type entityType = entity.GetType();
FieldInfo savedFieldsFieldInfo = entityType.GetFieldAndAllowPrivateBaseMembers("_savedFields", BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Instance);
if (savedFieldsFieldInfo == null)
{
Log.Error("Entity '{0}' does not contain the '_savedFields' field, this is not supported", entityType.Name);
return changedFields.ToArray();
}
IDictionary savedFields = (IDictionary)savedFieldsFieldInfo.GetValue(entity);
if (!savedFields.Contains(name))
{
Log.Error("Entity '{0}' does not contain the saved fields under '{1}', no changed fields", entityType.Name, name);
return changedFields.ToArray();
}
IList entityFields = (IList)savedFields[name];
foreach (object entityField in entityFields)
{
string fieldName = GetEntityFieldName(entityField);
object savedValue = GetCurrentFieldValue(entityField);
object currentValue = GetCurrentFieldValue(entity, fieldName);
if (savedValue != null)
{
if (!savedValue.Equals(currentValue))
{
changedFields.Add(fieldName);
}
}
else if (currentValue != null)
{
if (!currentValue.Equals(savedValue))
{
changedFields.Add(fieldName);
}
}
}
return changedFields.ToArray();
}
/// <summary>
/// Gets the entity field value.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="fieldName">Name of the field.</param>
/// <returns>Current value of the field.</returns>
/// <exception cref="ArgumentNullException">when <paramref name="entity"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException">when <paramref name="fieldName"/> is <c>null</c>.</exception>
public static object GetCurrentFieldValue(this object entity, string fieldName)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
if (fieldName == null)
{
throw new ArgumentNullException("fieldName");
}
Type entityType = entity.GetType();
MethodInfo getFieldByNameMethod = entityType.GetMethod("GetFieldByName", new[] { typeof(string) });
object entityField = getFieldByNameMethod.Invoke(entity, new object[] { fieldName });
return GetCurrentFieldValue(entityField);
}
/// <summary>
/// Gets the current field value.
/// </summary>
/// <param name="entityField">The entity field.</param>
/// <returns>Current field value.</returns>
/// <exception cref="ArgumentNullException">when <paramref name="entityField"/> is <c>null</c>.</exception>
public static object GetCurrentFieldValue(this object entityField)
{
if (entityField == null)
{
throw new ArgumentNullException("entityField");
}
Type entityFieldType = entityField.GetType();
PropertyInfo currentValuePropertyInfo = entityFieldType.GetProperty("CurrentValue");
return currentValuePropertyInfo.GetValue(entityField, null);
}
/// <summary>
/// Gets the name of the entity field.
/// </summary>
/// <param name="entityField">The entity field.</param>
/// <returns>The entity field name.</returns>
/// <exception cref="ArgumentNullException">when <paramref name="entityField"/> is <c>null</c>.</exception>
public static string GetEntityFieldName(this object entityField)
{
if (entityField == null)
{
throw new ArgumentNullException("entityField");
}
Type entityFieldType = entityField.GetType();
PropertyInfo namePropertyInfo = entityFieldType.GetProperty("Name");
return (string)namePropertyInfo.GetValue(entityField, null);
}
/// <summary>
/// Gets the entity relations.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns>Array of <c>IRelation</c> objects.</returns>
/// <exception cref="ArgumentNullException">when <paramref name="entity"/> is <c>null</c>.</exception>
public static object[] GetEntityRelations(this object entity)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
List<object> relations = new List<object>();
Type type = entity.GetType();
PropertyInfo relationsPropertyInfo = type.GetPropertyCached("Relations");
if (relationsPropertyInfo == null)
{
Log.Warn(TraceMessages.PropertyNotFound, "Relations", type.Name);
return relations.ToArray();
}
object relationsPropertyValue = relationsPropertyInfo.GetValue(entity, null);
if (relationsPropertyValue == null)
{
Log.Warn(TraceMessages.PropertyResultIsNull, "Relations", type.Name);
return relations.ToArray();
}
Type relationsType = relationsPropertyValue.GetType();
MethodInfo getRelationsMethodInfo = relationsType.GetMethodCached("GetAllRelations");
if (getRelationsMethodInfo == null)
{
Log.Warn(TraceMessages.MethodNotFound, "Relations.GetAllRelations", type.Name);
return relations.ToArray();
}
IEnumerable foundRelations = getRelationsMethodInfo.Invoke(relationsPropertyValue, null) as IEnumerable;
if (foundRelations == null)
{
Log.Warn(TraceMessages.MethodResultIsNull, "Relations.GetAllRelations", type.Name);
return relations.ToArray();
}
relations.AddRange(foundRelations.Cast<object>());
Log.Debug(TraceMessages.RelationsFound, relations.Count, type.Name);
return relations.ToArray();
}
/// <summary>
/// Gets the entity relation for the specified field.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="fieldName">Name of the field to find the relation for.</param>
/// <returns>The <c>IRelation</c> or <c>null</c> if no relation exists for the field.</returns>
/// <exception cref="ArgumentNullException">when <paramref name="entity"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException">when <paramref name="fieldName"/> is <c>null</c>.</exception>
public static object GetEntityRelationForField(this object entity, string fieldName)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
if (fieldName == null)
{
throw new ArgumentNullException("fieldName");
}
object[] relations = GetEntityRelations(entity);
foreach (object relation in relations)
{
Type relationType = relation.GetType();
FieldInfo foreignKeyFieldsFieldInfo = relationType.GetFieldAndAllowPrivateBaseMembers("_foreignKeyFields", BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Instance);
if (foreignKeyFieldsFieldInfo == null)
{
Log.Warn(TraceMessages.FieldNotFound, "_foreignKeyFields", relationType.Name);
}
else
{
IList foreignKeyFields = foreignKeyFieldsFieldInfo.GetValue(relation) as IList;
if ((foreignKeyFields != null) && (foreignKeyFields.Count > 0))
{
object foreignKeyField = foreignKeyFields[0];
if (foreignKeyField != null)
{
Type foreignKeyFieldType = foreignKeyField.GetType();
PropertyInfo aliasPropertyInfo = foreignKeyFieldType.GetPropertyCached("Alias");
if (aliasPropertyInfo == null)
{
Log.Warn(TraceMessages.PropertyNotFound, "Alias", foreignKeyFieldType);
}
else
{
string alias = aliasPropertyInfo.GetValue(foreignKeyField, null) as string;
if (!string.IsNullOrEmpty(alias) && string.Compare(alias, fieldName) == 0)
{
Log.Debug(TraceMessages.FoundRelationForFieldOnType, fieldName, entity.GetType());
return relation;
}
}
}
}
}
}
return null;
}
/// <summary>
/// Sets the AlreadyFetched[MappedFieldName] property of the specified relation to false.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="relation">Relation to refetch.</param>
/// <returns>
/// <c>true</c> if the relation is set to not fetched successfully; otherwise <c>false</c>..
/// </returns>
/// <remarks>Call this method only for OneToOne and OneToMany relations.</remarks>
/// <exception cref="ArgumentNullException">when <paramref name="entity"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException">when <paramref name="relation"/> is <c>null</c>.</exception>
public static bool SetRelationToNotFetched(object entity, object relation)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
if (relation == null)
{
throw new ArgumentNullException("relation");
}
Type entityType = entity.GetType();
Type relationType = relation.GetType();
PropertyInfo mappedFieldNamePropertyInfo = relationType.GetPropertyCached("MappedFieldName");
if (mappedFieldNamePropertyInfo == null)
{
Log.Warn(TraceMessages.PropertyNotFound, "MappedFieldName", entityType);
return false;
}
string mappedFieldName = (string)mappedFieldNamePropertyInfo.GetValue(relation, null);
PropertyInfo alreadyFetchedPropertyInfo = entityType.GetPropertyCached("AlreadyFetched" + mappedFieldName);
alreadyFetchedPropertyInfo.SetValue(entity, false, null);
PropertyInfo relationPropertyInfo = entityType.GetPropertyCached(mappedFieldName);
if (relationPropertyInfo == null)
{
Log.Warn(TraceMessages.PropertyNotFound, mappedFieldName, entityType.Name);
return false;
}
relationPropertyInfo.SetValue(entity, null, null);
return true;
}
}
}