Compare child property with parent property? - c#

I have these 2 classes:
public class Master
{
public int? TotalAmount;
public ICollection<Detail> Details;
}
public class Detail
{
public int Amount;
}
I'm trying to create a rule so that the details collection's total amount is equal to the master's total amount property.
I'm trying the following rule but I can't access the master's property:
RuleFor(x => x.Details)
.Must(coll => coll.Sum(item => item.Amount) == x.TotalAmount)
.When(x => x.Details != null)
.When(x => x.TotalAmount.HasValue);
What is the correct way to implement this kind of rule?

You can just use another overload of Must, like this:
RuleFor(x => x.Details)
.Must((master, details, ctx) => master.TotalAmount == details.Sum(r => r.Amount))
.When(x => x.Details != null)
.When(x => x.TotalAmount.HasValue);
However note that, as already pointed in comments, you should not use validation for consistency checks. You just have one piece of data (sum of details amount). So why just not do like this:
public class Master {
public int? TotalAmount
{
get
{
if (Details == null)
return null;
return Details.Sum(c => c.Amount);
}
}
public ICollection<Detail> Details;
}

The best way I have found to achieve this type of validation is to use a custom validator method like the following:
public class Validator : AbstractValidator<Master>
{
public Validator()
{
RuleFor(master => master)
.Must(CalculateSumCorrectly)
.When(master => master.Details != null)
.When(master => master.TotalAmount.HasValue);
}
private bool CalculateSumCorrectly(Master arg)
{
return arg.TotalAmount == arg.Details.Sum(detail => detail.Amount);
}
}
Notice that the rule here is being described as applying to the entire master object; RuleFor(master => master). This is the trick that allows you to access both properties.

Related

Fluent Validation - How to ensure that a collection Count greater than zero when not null, but can be null

I'm struggling to figure out how to define a rule that allows a collection property to be null, but not empty. Providing null for the collection property being a valid use case, but when the collection is provided, the collection needs to have at least one entry. Hence:
// Valid
{
"codes": null
}
// Invalid
{
"codes": []
}
// Valid
{
"codes": ["Pass"]
}
I've been playing around and can't seem to find anything that works:
public class UpdateCodesRequest
{
public IEnumerable<string> Codes { get; set; }
}
public class UpdateCodesRequestValidator : AbstractValidator<UpdateCodesRequest>
{
public UpdateCodesRequestValidator()
{
// none of these will work if Codes is null
RuleFor(x => x.Codes.Count()).GreaterThan(0).When(x => x != null);
RuleFor(x => x.Codes).Must(x => x.Any()).When(x => x != null);
RuleFor(x => x.Codes).Must(x => x != null && x.Any()).When(x => x != null);
}
}
How about this one?
RuleFor(x => x.Codes).Must(x => x == null || x.Any());

AutoMapper - What's difference between Condition and PreCondition

Suppose a mapping using AutoMapper like bellow:
mapItem.ForMember(to => to.SomeProperty, from =>
{
from.Condition(x => ((FromType)x.SourceValue).OtherProperty == "something");
from.MapFrom(x => x.MyProperty);
});
What's difference of substitute Condition by PreCondition:
from.PreCondition(x => ((FromType)x.SourceValue).OtherProperty == "something");
What's the practical difference between this two methods?
The diference is that PreCondition is executed before acessing the source value and also the Condition predicate, so in this case, before get the value from MyProperty the PreCondition predicate will run, and then the value from property is evaluated and finally Condition is executed.
In the following code you can see this
class Program
{
static void Main(string[] args)
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Person, PersonViewModel>()
.ForMember(p => p.Name, c =>
{
c.Condition(new Func<Person, bool>(person =>
{
Console.WriteLine("Condition");
return true;
}));
c.PreCondition(new Func<Person, bool>(person =>
{
Console.WriteLine("PreCondition");
return true;
}));
c.MapFrom(p => p.Name);
});
});
Mapper.Instance.Map<PersonViewModel>(new Person() { Name = "Alberto" });
}
}
class Person
{
public long Id { get; set; }
private string _name;
public string Name
{
get
{
Console.WriteLine("Getting value");
return _name;
}
set { _name = value; }
}
}
class PersonViewModel
{
public string Name { get; set; }
}
The output from this program is:
PreCondition
Getting value
Condition
Because the Condition method contains a overload that receives a ResolutionContext instance, that have a property called SourceValue, the Condition evaluate the property value from source, to set the SourceValue property on ResolutionContext object.
ATTENTION:
This behavior work properly until version <= 4.2.1 and >= 5.2.0.
The versions between 5.1.1 and 5.0.2, the behavior is not working properly anymore.
The output in those versions is:
Condition
PreCondition
Getting value

FakeItEasy - faking setter behaviour to store a value and then getter

Ultimately I want to have an internal interface with a setter and a public one with a getter. The code that replicates this scenario is roughed below:
[TestMethod]
public void TestMethod3()
{
var fake1 = A.Fake<IInterface1>(a => a.Implements(typeof(IInterface2)));
string backingString = null;
IInterface2 fake2 = (IInterface2)fake1;
A.CallTo(fake1)
.Where(a => a.Method.Name.Equals("set_Property"))
.Invokes((string param) => { backingString = param; });
A.CallTo(fake1)
.Where(a => a.Method.Name.Equals("get_Property"))
.WithReturnType<string>().Returns(backingString); //doesn't work
A.CallTo(fake2)
.Where(a => a.Method.Name.Equals("set_Property"))
.Invokes((string param) => { backingString = param; });
A.CallTo(fake2)
.Where(a => a.Method.Name.Equals("get_Property"))
.WithReturnType<string>().Returns(backingString); //doesn't work
fake1.Property = "asdf";
Assert.AreEqual("asdf", fake1.Property); //fails -> fake1.Property is null
Assert.AreEqual(fake1.Property, fake2.Property); //fails -> fake2.Property is null
}
}
public interface IInterface1
{
string Property { get; set; }
}
public interface IInterface2
{
string Property { get; }
}
I could get as far as using backingString to store the setter, but when setting up the getter it doesn't work as I wanted it to.
I also tried something in the line of A.CallTo(() => fake1.Property).Returns(backingString) to no avail.
Would appreciate assistance of them experts :)
When you set up your
A.CallTo(fake1)
.Where(a => a.Method.Name.Equals("get_Property"))
.WithReturnType<string>().Returns(backingString);
(and similarly for fake2),
the value of backingString is null, so that's what's returned later on when you access the Property getter.
In order to return the value of backingString at the time the Property getter is called, you want ReturnsLazily.
Make this change in each place and the tests pass:
A.CallTo(fake1)
.Where(a => a.Method.Name.Equals("get_Property"))
.WithReturnType<string>().ReturnsLazily(() => backingString);

Fluent validation and Must custom validation

I have a class and my validation looks like this:
public ValidationResult Validate(CustomerType customerType)
{
CustomerType Validator validator = new CustomerTypeValidator();
validator.RuleFor(x => x.Number).Must(BeUniqueNumber);
return validator.Validate(customerType);
}
public bool BeUniqueNumber(int number)
{
//var result = repository.Get(x => x.Number == customerType.Number)
// .Where(x => x.Id != customerType.Id)
// .FirstOrDefault();
//return result == null;
return true;
}
The CustomerTypeValidator is a basic validator class that validates string properties.
I also add a new rule to check if the number is unique in the db. I do it in this class because there's a reference to the repository. The validator class has no such reference.
The problem here is that the BeUniqueNumber method should have a CustomerType parameter. However when I do this, I get an error on the RuleFor line above because 'Must' needs an int as a parameter.
Is there a way around this?
Can you try this?
public ValidationResult Validate(CustomerType customerType)
{
CustomerTypeValidator validator = new CustomerTypeValidator();
validator.RuleFor(x => x).Must(HaveUniqueNumber);
return validator.Validate(customerType);
}
public bool HaveUniqueNumber(CustomerType customerType)
{
var result = repository.Get(x => x.Number == customerType.Number)
.Where(x => x.Id != customerType.Id)
.FirstOrDefault();
return result == null;
//return true;
}
You should also be able to do this:
public ValidationResult Validate(CustomerType customerType)
{
CustomerTypeValidator validator = new CustomerTypeValidator();
validator.RuleFor(x => x.Number).Must(BeUniqueNumber);
return validator.Validate(customerType);
}
public bool BeUniqueNumber(CustomerType customerType, int number)
{
var result = repository.Get(x => x.Number == number)
.Where(x => x.Id != customerType.Id)
.FirstOrDefault();
return result == null;
//return true;
}
"I also add a new rule to check if the number is unique in the db. I do
it in this class because there's a reference to the repository."
Well, why can't you give your validator a reference to the repository too?
CustomerTypeValidator validator = new CustomerTypeValidator(repository);

Better way to check duplicates in the list

I am checking if there are any duplicates while posting list of objects from view to controller by using validation attribute. It works but I would like to know if there is any better approach to follow (may be adding client side validation). Any feedback appreciated.
[AttributeUsageAttribute(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class DuplicateObjectAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value != null)
{
if (value.GetType() == typeof(List<OrdersVM>))
{
List<OrdersVM> objOrdersList = (List<OrdersVM>)value;
if (objOrdersList != null && objOrdersList.Count > 0)
{
if (objOrdersList.Select(p => p.OrderId).Distinct().Count() != objOrdersList.Select(p => p.OrderId).Count())
return false;
}
}
}
return true;
}
}
Is the way you are doing not good enough? I do something similar:
var duplicates = listOfItems
.GroupBy(i => i)
.Where(g => g.Count() > 1)
.Select(g => g.Key);
foreach (var d in duplicates)
;//dosomething
Which is based on the MSDN entry, Find Duplicates using LINQ

Categories

Resources