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
Related
I have list of Countries and inside it have list of Places.
// ...
public IList<ICountriesDTO> Countries { get; set; }
public class CountriesDTO: ICountriesDTO
{
public IEnumerable<IPlacesDTO> Places { get; set;
}
I am trying to get list of Places that are not null.
allPlacesDTO.World.Countries
.SelectMany(x => x.Places == null ? null : x.Places)
.ToList();
But I receive a null exception when Places are null for their Countries object.
How can I do a null check for Places and just use return statement instead of doing select to null object, similar to what I have below?
if (allPlacesDTO.World.Countries.Places == null)
{
return;
}
Update:
My requirement was if there is no places in any of the countries just use the return statement to exit the current function without proceeding further. That was achieved by the accepted answer and Count function.
var lstAllPlaces = allPlacesDTO.World.Countries
.Where(x => x.Places != null)
.SelectMany(x => x.Places)
.ToList();
if (lstAllPlaces.Count() == 0)
{
return;
}
You can do the condition in where clause
allPlacesDTO.World.Countries.Where(x => x.Places != null)
.SelectMany(x => x.Places).ToList();
Or change the ternary operator to return new List() (it can be greedy)
I am building a web API that is suppose to populate data from a linked child table using a where clause.
I have attempted using include() with where() as per eager loading but without success.
public IQueryable<Market> getAllActive()
{
return db.Markets.Where(c => c.IsActive == true).Include(d => d.TravelCentres.Where(e => e.IsActive == true));
}
On researching, there are recommendations that I use explicit loading but it keeps error about the need to cast the data type. I am lost of ideas at the moment and will appreciate any help. Here is my code:
private TravelCentresDbContext db = new TravelCentresDbContext();
public IQueryable<Market> getAllActive()
{
//return db.Markets.Where(c => c.IsActive == true).Include(d => d.TravelCentres);
var result = db.Markets
.Where(c => c.IsActive == true)
.Select(p => new
{
Market = p.MarketId,
TravelCentres = p.TravelCentres.Where(x => x.IsActive == true)
});
return (IQueryable<Market>)result;
}
I get this exception message Unable to cast object of type
'System.Data.Entity.Infrastructure.DbQuery1[<>f__AnonymousType42[System.String,System.Collections.Generic.IEnumerable1[TravelCentres.Models.TravelCentre]]]'
to type 'System.Linq.IQueryable1[TravelCentres.Models.Market]'.
Blockquote
result is not an IQuerytable<Market>, it's an IQueryable of an anonymous type with properties Market and TravelCenters. So (IQueryable<Market>)result is an invalid cast. It would be advisable to create a model with Market and TravelCenters properties and then return that.
public class MyModel
{
public int MarketId { get; set; }
public IEnumerable<TravelCentre> TravelCentres { get; set; }
}
.
var result = db.Markets
.Where(c => c.IsActive == true)
.Select(p => new MyModel()
{
Market = p.MarketId,
TravelCentres = p.TravelCentres.Where(x => x.IsActive == true)
});
return (IQueryable<MyModel>)result;
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.
I was wondering what was the best approach to compare multiple objects that are created and having the state of the objects changed to Inactive (Deleted), while creating history and dependencies.
This also means im comparing past and present objects inside a relational table (MarketCookies).
Id | CookieID | MarketID
The ugly solution i found was calculating how many objects had i changed.
For this purpose lets call the items of the Past: ListP
And the new items: ListF
I divided this method into three steps:
1 - Count both lists;
2 - Find the objects of ListP that are not present in List F and change their state to Inactive and update them;
3 - Create the new Objects and save them.
But this code is very difficult to maintain.. How can i make an easy code to maintain and keep the functionality?
Market Modal:
public class Market()
{
public ICollection<Cookie> Cookies {get; set;}
}
Cookie Modal:
public class Cookie()
{
public int Id {get;set;}
//Foreign Key
public int CookieID {get;set}
//Foreign Key
public int MarketID {get;set;}
}
Code:
public void UpdateMarket (Market Market, int Id)
{
var ListP = MarketCookiesRepository.GetAll()
.Where(x => x.MarketID == Id && Market.State != "Inactive").ToList();
var ListF = Market.Cookies.ToList();
int ListPCount = ListP.Count();
int ListFCount = ListF.Count();
if(ListPCount > ListFCount)
{
ListP.Foreach(x =>
{
var ItemExists = ListF.Where(y => y.Id == x.Id).FirstOrDefault();
if(ItemExists == null)
{
//Delete the Object
}
});
ListF.Foreach(x =>
{
var ItemExists = ListP.Where(y => y.Id == x.Id).FirstOrDefault();
if(ItemExists == null)
{
//Create Object
}
});
}
else if(ListPCount < ListFCount)
{
ListF.Foreach(x =>
{
var ItemExists = ListP.Where(y => y.Id == x.Id).FirstOrDefault();
if(ItemExists == null)
{
//Create Objects
}
});
ListP.Foreach(x =>
{
var ItemExists = ListF.Where(y => y.Id == x.Id).FirstOrDefault();
if(ItemExists == null)
{
//Delete Objects
}
});
}
else if(ListPCount == ListFCount)
{
ListP.Foreach(x =>
{
var ItemExists = ListF.Where(y => y.Id == x.Id).FirstOrDefault();
if(ItemExists == null)
{
//Delete Objects
}
});
ListF.Foreach(x =>
{
var ItemExists = ListP.Where(y => y.Id == x.Id).FirstOrDefault();
if(ItemExists == null)
{
//Create Objects
}
});
}
}
Without a good, minimal, complete code example that clearly illustrates the question, it's hard to know for sure what even a good implementation would look like, never mind "the best". But, based on your description, it seems like the LINQ Except() method would actually serve your needs reasonably well. For example:
public void UpdateMarket (Market Market, int Id)
{
var ListP = MarketCookiesRepository.GetAll()
.Where(x => x.MarketID == Id && Market.State != "Inactive").ToList();
var ListF = Market.Cookies.ToList();
foreach (var item in ListP.Except(ListF))
{
// set to inactive
}
foreach (var item in ListF.Except(ListP))
{
// create new object
}
}
This of course assumes that your objects have overridden Equals() and GetHashCode(). If not, you can provide your own implementation of IEqualityComparer<T> for the above. For example:
// General-purpose equality comparer implementation for convenience.
// Rather than declaring a new class for each time you want an
// IEqualityComparer<T>, just pass this class appropriate delegates
// to define the actual implementation desired.
class GeneralEqualityComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _equals;
private readonly Func<T, int> _getHashCode;
public GeneralEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
{
_equals = equals;
_getHashCode = getHashCode;
}
public bool Equals(T t1, T t2)
{
return _equals(t1, t2);
}
public int GetHashCode(T t)
{
return _getHashCode(t);
}
}
Used like this:
public void UpdateMarket (Market Market, int Id)
{
var ListP = MarketCookiesRepository.GetAll()
.Where(x => x.MarketID == Id && Market.State != "Inactive").ToList();
var ListF = Market.Cookies.ToList();
IEqualityComparer<Cookie> comparer = new GeneralEqualityComparer<Cookie>(
(t1, t2) => t1.Id == t2.Id, t => t.Id.GetHashCode());
foreach (var item in ListP.Except(ListF, comparer))
{
// set to inactive
}
foreach (var item in ListF.Except(ListP, comparer))
{
// create new object
}
}
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);