Fluent validation to compare properties from two list - c#

I'm working lots of complicated Validation Rules. We are using Fluent Validation to help with this and its a wonderful tool. I have a class which contains two list of same type.
public class BookingDto
{
public IEnumerable<Trip> OutTripDetails { get; set; }
public IEnumerable<Trip> InTripDetails{ get; set; }
}
public class Trip
{
public DateTime? TravelDate { get; set; }
public IEnumerable<ClassTst> Stops { get; set; }
}
if (OutTripDetails .TravelDate != null && InTripDetails.TravelDate != null)
{
if (InTripDetails.TravelDate < (DateTime)OutTripDetails .TravelDate)
{
Console.Writeline("Error here with date");
}
}
I want to convert the above validation to use fluentvalidation
RuleForEach(m => m.BookingDto.OutTripDetails )
.ChildRules(r => r.RuleFor(t => t.TravelDate )
// .GreaterThanOrEqualTo(r => r.)
//RuleForEach(m => m.BookingDto.InTripDetails)
.ChildRules(r => r.RuleFor(t => t.TravelDate )
Can I achieve this validation check with fluentvalidation in c#? I hav tried to implemnet with when condition to check if it is null but i couldnt make it work, Can someone hlp me to do this.

Related

How can I map a list of IDs to a list of Entities using AutoMapper.Collection

I'm using Entity Framework Core in a C# WebAPI project. I have the following entities (simplified for the purposes of this example):
public class Division
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int DivisionID { get; set; }
public virtual IEnumerable<CompetitorLevel> CompetitorLevels { get; set; }
}
public class CompetitorLevel
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int CompetitorLevelID { get; set; }
[Required]
public string Name { get; set; }
public virtual IEnumerable<Division> Divisions { get; set; }
}
public class DivisionDTO
{
public int? id { get; set; }
public List<string> competitorLevels { get; set; }
}
The DTO is used to send and receive data to/from my frontend app.
I'm successfully using AutoMapper to map the straightforward properties, and to map from a Division to a DivisionDTO, no problem there. My problem is that I can't get conversion from the list of IDs on the DTO to a list of entities working.
I tried AutoMapper.Collection using every permutation of .EqualityComparison(...) or .MapFrom(...) that I could think of; using lists, selecting IDs, etc.. but I can't get it to work.
My solution right now is to build and map the entities manually in my service class, which is probably fine, but I feel like there may be a more elegant way of doing this in AutoMapper. Here is the solution in my service:
public void Update(DivisionDTO request)
{
if (request.id == null)
throw new ValidationException("The ID attribute is required.");
Division division = _db.Divisions
.Include(d => d.CompetitorLevels)
.FirstOrDefault(d => d.DivisionID == request.id.Value);
if (division == null)
throw new KeyNotFoundException("A division with that ID was not found.");
_mapper.Map(request, division);
UpdateCompetitorLevels(request, division);
_db.SaveChanges();
}
public void UpdateCompetitorLevels(DivisionDTO request, Division division)
{
// Remove competitor levels from division if not present in DTO
foreach (CompetitorLevel competitorLevel in division.CompetitorLevels.ToList())
{
if (!request.competitorLevels.Select(cl => int.Parse(cl)).Contains(competitorLevel.CompetitorLevelID))
{
(division.CompetitorLevels as List<CompetitorLevel>).Remove(competitorLevel);
}
}
// Add competitor level to division if present in DTO
foreach (int competitorLevelID in request.competitorLevels.Select(cl => int.Parse(cl)))
{
if (!division.CompetitorLevels.Any(cl => cl.CompetitorLevelID == competitorLevelID))
{
(division.CompetitorLevels as List<CompetitorLevel>).Add(_db.CompetitorLevels.Find(competitorLevelID));
}
}
}
I tried to create the Map bewteen string and CompetitorLevel as below:
public class DivisionProfile:Profile
{
public DivisionProfile()
{
CreateMap<DivisionDTO, Division>().ForMember(x=>x.CompetitorLevels,y=>y.MapFrom(x=>x.competitorLevels)).ForMember(x=>x.DivisionID,y=>y.MapFrom(x=>x.id));
CreateMap<string, CompetitorLevel>().ForMember(x => x.CompetitorLevelID, y => y.MapFrom(x => int.Parse(x)));
}
}
The result:

Why result of IQueryable type converts to empty object?

public IHttpActionResult GetAllCollections(CollectionsDTO collectionsDTO)
{
if (!ModelState.IsValid)
return BadRequest();
try
{
var collectionsSectionRolesFlatDTO = (from c in db.Collections
join sr in db.SectionRole
on c.SectionRoleId equals sr.Id
select new CollectionsSectionRolesFlatDTO
{
Collections = new CollectionsDTO
{
CollectionTitleAr = c.CollectionTitleAr,
CollectionTitleEn = c.CollectionTitleEn,
CoverImagePath = c.CoverImagePath,
SectionRoleId = c.SectionRoleId,
},
SectionRole = new SectionRoleDto
{
NameAr = sr.NameAr,
NameEn = sr.NameEn
}
})
.AsQueryable();
var collectionsDto = Mapper.Map<List<CollectionsSectionRolesDTO>>(collectionsSectionRolesFlatDTO);
return Ok(collectionsDto);
}
catch (Exception ex)
{
return BadRequest("GetAllCollection: "+ ex.ToString());
}
}
DTOs:
public class CollectionsDTO
{
public int Id { get; set; }
public string CollectionTitleEn { get; set; }
public string CollectionTitleAr { get; set; }
public string CoverImagePath { get; set; }
public int? SectionRoleId { get; set; }
}
public class CollectionsSectionRolesFlatDTO
{
public SectionRoleDto SectionRole { get; set; }
public CollectionsDTO Collections { get; set; }
}
public class CollectionsSectionRolesDTO
{
public int Id { get; set; }
public string CollectionTitleEn { get; set; }
public string CollectionTitleAr { get; set; }
public string CoverImagePath { get; set; }
public int? SectionRoleId { get; set; }
//section role
public string NameAr { get; set; }
public string NameEn { get; set; }
}
public class SectionRoleDto
{
public int Id { get; set; }
public string NameEn { get; set; }
public string NameAr { get; set; }
}
Automapper:
CreateMap<Collections, CollectionsDTO>();
CreateMap<CollectionsSectionRolesFlatDTO, CollectionsSectionRolesDTO>();
Now the problem is mindboggling.
this line
var collectionsDto = Mapper.Map<List<CollectionsSectionRolesDTO>>(collectionsSectionRolesFlatDTO);
is supposed to return the data but instead it returns what I have passed in the function param.
Why is this happening? I have spent literally hours but nothing. I checked all the automapper setting but this doesn't work.
I am new to c# so any help regarding it would be appreciated. thanks.
Why would you be going through the trouble of double projection? Just configure Automapper with the information to get a desired DTO out of the object graph. Ensure you have navigation properties set up for your relationships to make querying against the object graph a lot simpler. Linq & EF does not need to be written as a substitute for SQL. (explicit joins) Navigation properties allow EF to provide those joins entirely behind the scenes for simpler querying.
First, the mapping:
CreateMap<Collections, CollectionsSectionRolesDTO>()
// Id, NameAr, and NameEn should auto-resolve.
.ForMember(x => x.CollectionTitleEn, opt => opt.MapFrom(src => src.Collection.CollectionTitleEn))
.ForMember(x => x.CollectionTitleAr, opt => opt.MapFrom(src => src.Collection.CollectionTitleAr));
// Continue for fields coming from Collection...
then to query using the automapper config. (config)
var results = db.Collections
.ProjectTo<CollectionsSectionRolesDTO>(config)
.ToList();
Done and dusted. No need to select and flatten data first into memory then use Automapper to create a new in-memory collection of the desired view model. Just project down to the desired view model directly within the query.
Double-projection is useful where you need to flatten data down first in order to perform conversions or transforms that cannot be converted in SQL. The first projection (Select) would typically be to an anonymous type or a DTO using ProjectTo, then fed into code that would provide the necessary transformations to produce the end result view models/DTOs.

Query a parent-children entities with one-many relationships and retrieve children records based on values in the parent record using LINQ to Entities

I have a Asp.net MVC application which has 3 classes; Event, Modification, FieldHistory. Events has a one-many with Modifications, and Modifications also has one-many relationship with FieldHistory.
Event class:
public class Event
{
public int EventId { get; set; }
public string EventName { get; set; }
public DateTime? EventDate { get; set; }
}
Modification class:
public class Modification
{
public int ModificationId { get; set; }
public int EventId { get; set; }
public virtual Event Event { get; set; }
public string ModificationStatus { get; set; }
}
FieldHistory class:
public class FieldHistory
{
public int FieldHistoryId { get; set; }
public int ModificationId { get; set; }
public virtual Modification Modification { get; set; }
public int PropValId { get; set; }
public int KeyFieldId { get; set; }
public string FieldName { get; set; }
public string Instructions { get; set; }
public string Format { get; set; }
}
In one of the Index actions I am returning an IEnumerable of Modifications to the view and present them as list items in the index page.
public async Task<ActionResult> Index()
{
var modifications = _applicationDbContext.Modifications.Include(m
=> m.Event)
.Include(m => m.ItemType)
.Include(m => m.ModificationType)
.OrderByDescending(m => m.CurrentUserId);
return View(await modifications.ToListAsync());
}
I would like to extend this query so that now I will also show Modifications together with one of its child records based on the ModificationStatus field of the Modification entity. For example if the modificationstatus is "Creating" or "Created" then load the FieldHistory that has PropValId of 1, otherwise if status is "Processing" or "Approved" then load FieldHistory of PropValId of 2 as its child. So in a list item I should have all 3 entities in a record: Event, Modification, FieldHistory
I am a just a beginner in Linq and hope that this can be another big step for me.
Not sure if there is a direct way to tie in the ModificationStatus with PropValId, so instead just using what you conveyed above and obviously you'd have to supply the additional rules if there are more... also only going to take the First one of other object types but you can change that if you need all associated ones...
I'm picking up from your variable modifications as defined above, and of course assuming the names of the tables in your DbContext containing the other objects:
var trios = modifications.Select(m => new { Modification = m, // select the modification obj itself
FieldHistory = _applicationDbContext.FieldHistories.FirstOrDefault(fh =>
fh.ModificationId == m.ModificationId && // I am assuming this is required
fh.PropValId == (new List<string> {"Creating", "Created"}.Contains(m.ModificationStatus) ? 1 : 2)),
Event = _applicationDbContext.Events.FirstOrDefault(e => e.EventId == m.EventId)
}).ToList();
This will return what you're looking for. If you want, you can create a class, e.g.:
public class Trio {
public Modification Modification { get; set; }
public FieldHistory FieldHistory { get; set; }
public Event Event { get; set; }
public Trio (Modification modification, FieldHistory fieldHistory, Event #event)
{
this.Modification = modification;
this.FieldHistory = fieldHistory;
this.Event = #event;
}
}
Then change to:
var trios = modifications.Select(m => new Trio(m, // select the modification obj itself
_applicationDbContext.FieldHistories.FirstOrDefault(fh =>
fh.ModificationId == m.ModificationId && // I am assuming this is required
fh.PropValId == (new List<string> {"Creating", "Created"}.Contains(m.ModificationStatus) ? 1 : 2)),
_applicationDbContext.Events.FirstOrDefault(e => e.EventId == m.EventId)
)).ToList();

Improving efficiency with lazy/eager loading using a virtual (foreign key) reference

I am relatively new to Lambda/Linq, however I want to retrieve all events from a specific calendar where the events are still in the future...
If I use:
EventCalendar eventCalendar;
eventCalendar = db.Events_Calendars.Find(id);
I can get all events and filter by the current date in the view, but I don't think that is the best method.
The Model is as follows:
[Table("Events_Calendars")]
public class EventCalendar
{
public int Id { get; set; }
public string Calendar { get; set; }
public virtual List<Event> Events { get; set; }
}
Event Model is:
public class Event
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public int? Capacity { get; set; }
.
.
.
}
The View is:
#model WebApps.Areas.Events.Models.EventCalendar
#* does some stuff with the EventCalendar *#
#Html.Partial("_Events", Model.Events.ToList())
_Events partial view:
#model IEnumerable<WebApps.Areas.Events.Models.Event>
.
.
.
#foreach (var item in Model)
{
if (item.End > DateTime.Today)
{
#* Do that thing *#
}
}
The above example works, but is putting logic flow in the view. I want to remove the filter logic from the view and make the query do all the work.
The following gives me the calendar only if it has at least one future event which is due to the same where clause conditions. I left it in as a reference of what I need to filter, yet it still returns all events both past and future:
eventCalendar = db.Events_Calendars
.Where(x => x.Events.Any(y => y.End >= DateTime.Today) && x.Id == id)
.FirstOrDefault();
Is there a way that I can filter the events when I ask for the calendar and still use only one statement?
Note: Eager loading is defaulted to on with this system.
Edit clarification = return "no more than 1 calendar with all associated future events";
The Events.Any will filter the Events_Calendars that have at least one event where End >= DateTime.Today. It will not filter the Events_Calendar.Events themselves.
The following will return the Events.End >= DateTime.Today for the matching Events_Calendar only.
var events = db.Events_Calendars
.Where(x => x.Id == id)
.SelectMany(x => x.Events)
.Where(x => x.End >= DateTime.Today);
If you add a Events_Calendar property to the Events, you'll still be able to display the calendar.
Alternatively, you can remap the Events_Calendar, for example by using a factory method:
var newCal = Events_Calendars.WithFutureEvents(db.Events_Calendars.Find(id))
class Events_Calendar
{
public static Events_Calendar WithFutureEvents(Events_Calendar cal)
{
return new Events_Calendar()
{
Id = cal.Id,
Calendar = cal.Calendar,
Events = cal.Events.Where(x => x.End >= DateTime.Today).ToList()
};
}
}
Note that the code example means the new Events_Calendar has reference copies for the Events.
Another possibility is a covenience method on Events_Calendar
class Events_Calendar
{
public IEnumerable<Event> FutureEvents => Events.Where(e => e.End >= DateTime.Today);
}

How do you query the database within the model?

NOTE: This is an entire rewrite of the previous question.
I have a model.
class Paragraph
{
public int Id { get; set; }
public int SectionId { get; set; }
public virtual Section Section { get; set; }
public int Major { get; set; }
public int Option { get; set; }
public ICollection<Paragraph> Options
{
get
{
// What I'm trying to return is:
//
// Section.Paragraphs
// .Where(p => p.Major == Major && p.Option != Option)
// .ToList()
}
}
}
It is involved in a one to many relationship; where each Section has many Paragraphs. What I'm trying to return is a list of paragraphs where their Major is the same as the entity's Major and the Option isn't the same. Basically.
Where(p => p.Major == Major && p.Option != Option)
Any advice on how to accomplish this? Thank you.
You want your Section property to be non-null. Presumably you have a Section class that has a property of type List<Paragraph> or the like, since you describe this situation as one model "belonging" to another.
If you are thinking that just because your paragraph is contained in a section, you will get some sort of reciprocity for free, you are mistaken. You will have to see to maintaining this relationship yourself.
I solved it. Woop-woop! All I did was check if Section was null, and if it was I returned a blank List<Paragraph>. So Options becomes.
public ICollection<Paragraph> Options
{
get
{
if (Section != null)
{
return Section.Paragraphs
.Where(p => p.Major == Major && p.Option != Option)
.ToList();
}
else
{
return new List<Paragraph>();
}
}
}

Categories

Resources