views:

72

answers:

2

I use Sharp Architecture 1.6 and run in to an issue with EntityDuplicateChecker.

I have an Entity with 2 properties as domain signature, (int) CustomerId and a ValueObject that represents a week(consist of year and a weeknumber).

So in DB terms there is a domain signature on 3 columns, CustomerId, Year and WeekNumber.

EntityDuplicateChecker only support references to Entity types, value types, string, dates and enums.

Is there a good practice to handle this scenario?

Any ideas are welcome.

+1  A: 

Can you put the DomainSignature attribute on a date field that stores the first Monday of that week (ex when Year=2010, WeekNumber=40 then the date is 2010-10-04)? You then need logic in your property setters for Year and WeekNumber to change the date field as needed, as well as logic in the date property setter to change the Year and WeekNumber fields.

http://msdn.microsoft.com/en-us/library/system.globalization.calendar.getweekofyear.aspx

Dan
Well that might work for this specific problem, but I see more ValueObjects taking part in DomainSignatures so I'm more interested in a generic solution.
HAXEN
A: 

I have ended up doing my own DuplicateEntityChecker and have changed ComponentRegistrar to use that instead of SharpArch one.

My class looks like this.

using NHibernate;
using SharpArch.Core.DomainModel;
using System.Reflection;
using System;
using NHibernate.Criterion;
using SharpArch.Core;
using SharpArch.Core.PersistenceSupport;
using System.Linq;
using SharpArch.Data.NHibernate;

namespace your.namespace.Data.NHibernate
{
public class EntityDuplicateChecker : IEntityDuplicateChecker
{
    /// <summary>
    /// Provides a behavior specific repository for checking if a duplicate exists of an existing entity.
    /// </summary>
    public bool DoesDuplicateExistWithTypedIdOf<IdT>(IEntityWithTypedId<IdT> entity) {
        Check.Require(entity != null, "Entity may not be null when checking for duplicates");

        ISession session = GetSessionFor(entity);

        FlushMode previousFlushMode = session.FlushMode;

        // We do NOT want this to flush pending changes as checking for a duplicate should 
        // only compare the object against data that's already in the database
        session.FlushMode = FlushMode.Never;

        Criteria = session.CreateCriteria(entity.GetType())
            .Add(Expression.Not(Expression.Eq("Id", entity.Id)))
            .SetMaxResults(1);

        AppendSignaturePropertyCriteriaTo<IdT>(Criteria, entity);
        bool doesDuplicateExist = Criteria.List().Count > 0;
        session.FlushMode = previousFlushMode;
        return doesDuplicateExist;
    }

    public ICriteria Criteria { get; protected set; }

    private void AppendSignaturePropertyCriteriaTo<IdT>(ICriteria criteria, IEntityWithTypedId<IdT> entity) {
        foreach (PropertyInfo signatureProperty in entity.GetSignatureProperties()) {
            Type propertyType = signatureProperty.PropertyType;
            object propertyValue = signatureProperty.GetValue(entity, null);

            if (propertyType.IsEnum) {
                criteria.Add(
                    Expression.Eq(ResolvePropertyName(string.Empty, signatureProperty), (int)propertyValue));
            }
            else if (propertyType.GetInterfaces()
                .Any(x => x.IsGenericType && 
                     x.GetGenericTypeDefinition() == typeof(IEntityWithTypedId<>))) {
                AppendEntityCriteriaTo<IdT>(criteria, signatureProperty, propertyValue);
            }
            else if (propertyType == typeof(DateTime)) {
                AppendDateTimePropertyCriteriaTo(criteria, string.Empty, signatureProperty, propertyValue);
            }
            else if (propertyType == typeof(String)) {
                AppendStringPropertyCriteriaTo(criteria, string.Empty, signatureProperty, propertyValue);
            }
            else if (propertyType.IsValueType) {
                AppendValuePropertyCriteriaTo(criteria, string.Empty, signatureProperty, propertyValue);
            }
            else if(typeof(ValueObject).IsAssignableFrom(propertyType)) {
                AppendValueObjectSignaturePropertyCriteriaTo(criteria, signatureProperty.Name + ".", propertyValue as ValueObject);
            }
            else {
                throw new ApplicationException("Can't determine how to use " + entity.GetType() + "." +
                    signatureProperty.Name + " when looking for duplicate entries. To remedy this, " +
                    "you can create a custom validator or report an issue to the S#arp Architecture " +
                    "project, detailing the type that you'd like to be accommodated.");
            }
        }
    }


    private void AppendValueObjectSignaturePropertyCriteriaTo(ICriteria criteria, string parentPropertyName, ValueObject valueObject)
    {
        if(valueObject == null)
            return;

        foreach (PropertyInfo signatureProperty in valueObject.GetSignatureProperties())
        {
            Type propertyType = signatureProperty.PropertyType;
            object propertyValue = signatureProperty.GetValue(valueObject, null);

            if (propertyType.IsEnum)
            {
                criteria.Add(
                    Expression.Eq(ResolvePropertyName(parentPropertyName, signatureProperty), (int)propertyValue));
            }
            else if (propertyType == typeof(DateTime))
            {
                AppendDateTimePropertyCriteriaTo(criteria, parentPropertyName, signatureProperty, propertyValue);
            }
            else if (propertyType == typeof(String))
            {
                AppendStringPropertyCriteriaTo(criteria, parentPropertyName, signatureProperty, propertyValue);
            }
            else if (propertyType.IsValueType)
            {
                AppendValuePropertyCriteriaTo(criteria, parentPropertyName, signatureProperty, propertyValue);
            }
            else if (typeof(ValueObject).IsAssignableFrom(propertyType))
            {
                AppendValueObjectSignaturePropertyCriteriaTo(criteria, ResolvePropertyName(parentPropertyName, signatureProperty), propertyValue as ValueObject);
            }
            else
            {
                throw new ApplicationException("Can't determine how to use " + valueObject.GetType() + "." +
                    signatureProperty.Name + " when looking for duplicate entries. To remedy this, " +
                    "you can create a custom validator or report an issue to the S#arp Architecture " +
                    "project, detailing the type that you'd like to be accommodated.");
            }
        }
    }

    private void AppendStringPropertyCriteriaTo(ICriteria criteria, string parentPropertyName,
        PropertyInfo signatureProperty, object propertyValue)
    {
        var propertyName = ResolvePropertyName(parentPropertyName, signatureProperty);

        if (propertyValue != null)
        {
            criteria.Add(
                Expression.InsensitiveLike(propertyName, propertyValue.ToString(), MatchMode.Exact));
        }
        else
        {
            criteria.Add(Expression.IsNull(propertyName));
        }
    }

    private void AppendDateTimePropertyCriteriaTo(ICriteria criteria, string parentPropertyName,
        PropertyInfo signatureProperty, object propertyValue)
    {

        var propertyName = ResolvePropertyName(parentPropertyName, signatureProperty);

        if ((DateTime) propertyValue > UNINITIALIZED_DATETIME)
        {
            criteria.Add(Expression.Eq(propertyName, propertyValue));
        }
        else
        {
            criteria.Add(Expression.IsNull(propertyName));
        }
    }

    private void AppendValuePropertyCriteriaTo(ICriteria criteria, string parentPropertyName,
        PropertyInfo signatureProperty, object propertyValue) {

        var propertyName = ResolvePropertyName(parentPropertyName, signatureProperty);

        if (propertyValue != null) {
            criteria.Add(Expression.Eq(propertyName, propertyValue));
        }
        else {
            criteria.Add(Expression.IsNull(propertyName));
        }
    }

    private static void AppendEntityCriteriaTo<IdT>(ICriteria criteria, 
        PropertyInfo signatureProperty, object propertyValue) {
        if (propertyValue != null) {
            criteria.Add(Expression.Eq(signatureProperty.Name + ".Id",
                ((IEntityWithTypedId<IdT>)propertyValue).Id));
        }
        else {
            criteria.Add(Expression.IsNull(signatureProperty.Name + ".Id"));
        }
    }

    private static string ResolvePropertyName(string parentPropertyName, PropertyInfo signatureProperty)
    {
        if (string.IsNullOrEmpty(parentPropertyName))
            return signatureProperty.Name;

        if (parentPropertyName.EndsWith(".") == false)
            parentPropertyName += ".";

        return string.Format("{0}{1}", parentPropertyName, signatureProperty.Name);
    }

    private static ISession GetSessionFor(object entity)
    {
        var factoryKey = SessionFactoryAttribute.GetKeyFrom(entity);
        return NHibernateSession.CurrentFor(factoryKey);
    }

    private readonly DateTime UNINITIALIZED_DATETIME = default(DateTime);
}
}

Hope this helps anyone with the same problem.

HAXEN
This could be a really nice feature. Is there a reason why you created a public ICriteria property? Do you need access to the criteria from outside the class?
Dan
Ahh, yes I used it for testing purposes but it turned out I couldn't use it. So no need for it to be public.
HAXEN
I think others would appreciate this. You should fork the project on GitHub and add this code to the EntityDuplicateChecker class and send a pull request.
Dan