views:

145

answers:

4

I'm trying to get this Generic DuplicateValidationRule working, which basically checks a Collection for duplicates (based on a generic business object type passed in). Lets take the IBankAccount Business Object for example:

public interface IBankAccount : IMyBusinessObjectBase
{
 IBank Bank
 {
  get;
  set;
 }

 IBankAccountType BankAccountType
 {
  get;
  set;
 }

 string AccountName
 {
  get;
  set;
 }

 string AccountNumber
 {
  get;
  set;
 }

 DateTime EffectiveDate
 {
  get;
  set;
 }
}

Lets say I have the following collection of IBankAccount

IBankAccount acc1 = new BankAccount();
acc1.AccountName = "Account1";
acc1.AccountNumber = "123456";
acc1.Bank = FetchBusinessObjectByID(1); //Fetch Bank 1

IBankAccount acc2 = new BankAccount();
acc2.AccountName = "Account2";
acc2.AccountNumber = "654321";
acc2.Bank = FetchBusinessObjectByID(1); //Fetch Bank 1

IBankAccount acc3 = new BankAccount();
acc3.AccountName = "Account3";
acc3.AccountNumber = "123456";
acc3.Bank = FetchBusinessObjectByID(2); //Fetch Bank 2

IBankAccount acc4 = new BankAccount();
acc4.AccountName = "Account3";
acc4.AccountNumber = "123456";
acc4.Bank = FetchBusinessObjectByID(1); //Fetch Bank 2

ICollection<IBankAccount> bankAccounts = new List<IBankAccount>();
bankAccount.Add(acc1);
bankAccount.Add(acc2);
bankAccount.Add(acc2);
bankAccount.Add(acc4);

Parameters:

T = BusinessObject Type Class (Person,Address,BankAccount,Bank,BankBranch etc) string[] entityPropertyName = Array of properties to include when checking for duplicates.

Now lets use the following senario: Lets say I want to check for duplicate BankAccounts, by checking the on the account number, and the bank. The Account Number is unique within a bank, but can exist in another bank. So if you look at the list above, Acc1,Acc2 and Acc3 is valid. Acc4 is invalid because that account number for that Bank already exist.

So, the call to validate would look like this.

DuplicateValidationRule.Validate(newBankAccountEntry,new string[] {"AccountNumber","Bank.Name"});

The code above passes in the the business entity to check duplicates for, and the property array which includes the properties to check against.

    public ValidationError Validate<T>(T entityProperty, string[] entityPropertyName)
    {          
        ICollection<T> businessObjectList = FetchObjectsByType<T>();

        bool res = true; 
        for (int i = 0; i < entityPropertyName.Length; i++)
        {
            object value = getPropertyValue(entityProperty, entityPropertyName[i]);

//By Using reflection and the getPropertyValue method I can substitute the properties to //compare.
            if (businessObjectList.Any(x => getPropertyValue(x, entityPropertyName[i]).Equals(value) && 
                                       x.GetType().GetProperty("ID").GetValue(x,null).ToString()                                           
                                       != ((IBusinessObjectBase)entityProperty).ID.ToString()))
                res &= true;
            else
                res &= false;             
        }

        if (res)
            return new ValidationError(_DuplicateMessage);
        else
            return ValidationError.Empty;
    }

This method is used to get the actual object to check the duplicates on:

    private static object getPropertyValue(object obj, string propertyName)
    {
        string[] PropertyNames = propertyName.Split('.');

        object propertyValue = obj;

        for (int i = 0; i < PropertyNames.Length; i++)
        {
            propertyValue = propertyValue.GetType().GetProperty(PropertyNames[i], BindingFlags.Public |
                    BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy).
                    GetValue(propertyValue, null);
        }

        if (propertyValue.GetType() == typeof(string))
            return propertyValue.ToString().ToLower(CultureInfo.CurrentCulture);
        else
            return propertyValue;
    }

The above code works, but it has a flaw: The code above will only work if you check a single property for a duplicate match:

DuplicateValidationRule.Validate<IBankAccount>(newBankAccountEntry,new string[] {"AccountNumber"});

When you add another property to check against, for example:

DuplicateValidationRule.Validate<IBankAccount>(newBankAccountEntry,new string[] {"AccountNumber","Bank.Name"});

then the code will check the second property, on the next occurange in the list. It should test all the properties in the .Any for the currently object being compared. Hope this makes sence, maybe someone can give me some advice on how to fix this! Thanks :)

+1  A: 

Hi If I'm not wrong right now you are checking the unique existence of every single property not the combination of them.

Instead of using reflection can't you use the Equals method or an interface (eg. IMyComparable) like this:

public interface IMyComparable<T>
{
   bool MyComparableMethod(T account);
}

public interface IBankAccount : IMyBusinessObjectBase,  IMyComparable<T>
{
...
   public bool MyComparableMethod(IBankAccount account)
   {
       return this.AccountNumber == account.AccountNumber && 
               this.Bank.Name == account.Bank.Name &&
               this.Id != account.Id;
   }
}

public ValidationError Validate<T>(T entityProperty) where T : IMyComparable<T>
{
...
     if (!businessObjectList.Any(x => entityProperty.MyComparableMethod(x))
         return new ValidationError(_DuplicateMessage);
...

than in the Any method you can use the MyComparableMethod method defined in the interface.

Hope this helps :)

crossy
I'm not sure what you are trying to do. You are correct in saying that at the moment I'm checking the unique existence of every single property and not the combination of them, which is my problem. Please provide a full example for what you are trying to say?Remember that it seens to take in any business object type, so the IMyComparable should look something like this:public interface IMyComparable<T>{ bool MyComparableMethod(T entity);}
FaNIX
My compare method is not perfect (e.g. doesn't checks for null) but I think now the code expresses my idea
crossy
+1  A: 

Here's me under the assumption that you have a BusinessObject entity and you want to match ALL properties against another in a list of BusinessObjects:

public ValidationError Validate<T>(T entityProperty, string[] entityPropertyName)
{                  
    ICollection<T> businessObjectList = FetchObjectsByType<T>();
    Hashtable properties = EnumeratePropertyInfo<T>(entityProperty, entityPropertyName);
    return businessObjectList.Any(obj => IsDuplicate<T>(obj, properties)) == true ? new ValidationError(_DuplicateMessage) : ValidationError.Empty;
}


private Hashtable EnumeratePropertyInfo<T>(T entityProperty, string[] entityPropertyName)
{
    Hashtable properties = new Hashtable();
    for (int i = 0; i < entityPropertyName.Length; i++)        
    {            
        object value = getPropertyValue(entityProperty, entityPropertyName[i]);
        properties.Add(entityPropertyName[i], value);
    }
    return properties;
}

// all properties must be matched for a duplicate to be found
private bool IsDuplicate<T>(T entityProperty, Hashtable properties)
{
    foreach(DictionaryEntry prop in properties)
    {
        var curValue = getPropertyValue(entityProperty, prop.Key.ToString());
        if (!prop.Value.Equals(curValue))
        {
            return false;
        }
    }
    return true;
}

If you are perhaps a little concerned about using a Hastable for the property mappings then I would advise you create your own custom type for this instead.

Hope that helps.

James
Looks like your code might work... It seems like a better solution to the one I just found. Let me post my current solution and hopefully some user input will tell me which would be better to use. Thanks a lot!!
FaNIX
A: 

My solution to the problem. No modifications made to getPropertyValue. Please provide your input. Thanks

public ValidationError Validate(T entityProperty, string[] entityPropertyName) {
ICollection businessObjectList = BusinessContextManagerService.Fetch>(Criteria.ActiveAndDormant);

        bool res = true;
        object entityPropertyValue = null;

        // Only Checking one property
        if (entityPropertyName.Length == 1)
        {
            entityPropertyValue = getPropertyValue(entityProperty, entityPropertyName[0]);

            if (businessObjectList.Any(x => getPropertyValue(x, entityPropertyName[0]).Equals(entityPropertyValue) &&
                                       x.GetType().GetProperty("ID").GetValue(x, null).ToString()
                                       != ((IBusinessObjectBase)entityProperty).ID.ToString()))
                res &= true;
            else
                res &= false;
        }
        else
        {
            foreach (object obj in businessObjectList)
            {
                res = true;
                int objID = (Int32)obj.GetType().GetProperty("ID").GetValue(obj, null);

                for (int i = 0; i < entityPropertyName.Length; i++)
                {
                    entityPropertyValue = getPropertyValue(entityProperty, entityPropertyName[i]);
                    object objValue = getPropertyValue(obj, entityPropertyName[i]);

                    if (objValue.Equals(entityPropertyValue) && objID != ((IBusinessObjectBase)entityProperty).ID)
                        res &= true;
                    else
                        res &= false;

                    if (res == false)
                        break;
                }

                if (res == true)
                    break;
            }
        }

        if (res)
            return new ValidationError(_DuplicateMessage);
        else
            return ValidationError.Empty;
    }
FaNIX
Your solution will no doubt do the trick however for readability purposes its a little cluttered. Also there is some duplicated code in there. You should try to refactor it a little and delegate it to generic methods. My solution does this nicely and reduces your Validate method to 3 lines. You could also further improve the duplicate method by just passing in 2 sets of hashtable properties and comparing them using .Equals I will update it later to show you. However, at the end of the day it is your decision what you want to do.
James
A: 

I tested your code with a Timespan and it seems to take the same amount of time, which is fine with me. Your code looks a lot nicer so I will be going with your solution, thank you very much for taking the time to help me.

PS: I had to change the following in order for it to work. All unit tests are passing now :)

    private bool IsDuplicate<T>(T entityProperty, Hashtable properties)
    {
        bool res = true;

        foreach (DictionaryEntry prop in properties)
        {
            var curValue = getPropertyValue(entityProperty, prop.Key.ToString());
            if (prop.Value.Equals(curValue))
            {
                res &= true;
            }
            else
                res &= false;
        }
        return res;
    }
FaNIX
Glad I could help! Btw I just noticed, my code is actually correct for the IsDuplicate method, I just forgot to add a ! operator it should be "if(!prop.Value.Equals(curValue))".
James
having a flag and constantly setting it to true/false is pretty pointless as once it is false, you know the property set are not equal. So you want to jump out the loop ASAP. I have updated my IsDuplicateMethod
James