I have the following situation:
A domain model that has a property BithDay.
I want to be able to verify that the age (that will be computed accordingally to the birthday) is lower than 150 years.
Can I do that by using the built in validtors or I have to build my own?
Can someoane provide me an example of DomainValidator?
You can use a RelativeDateTimeValidator to validate an age based on a Birth Date. For example:
public class Person
{
[RelativeDateTimeValidator(-150, DateTimeUnit.Year, RangeBoundaryType.Inclusive,
0, DateTimeUnit.Year, RangeBoundaryType.Ignore,
MessageTemplate="Person must be less than 150 years old.")]
public DateTime BirthDate
{
get;
set;
}
}
// 150 Year old person
Person p = new Person() { BirthDate = DateTime.Now.AddYears(-150) };
var validator = ValidationFactory.CreateValidator<Person>();
ValidationResults vrs = validator.Validate(p);
foreach (ValidationResult vr in vrs)
{
Console.WriteLine(vr.Message);
}
This will print: "Person must be less than 150 years old."
You can try something like this:
public class Person
{
public DateTime BirthDate { get; set; }
[RangeValidator(0, RangeBoundaryType.Inclusive, 150, RangeBoundaryType.Exclusive,
MessageTemplate="Person must be less than 150 years old.")]
public int Age
{
get { return (DateTime.Now - this.BirthDate).Days / 365; }
}
}
Related
I have an event entity with relation to 1+ rating entities. I need to query my database to get only events with (for example) an average of 2 stars rating on the total of ratings of this specific event. Rate1, 2, 3, 4 can have values between [0,1,2,3,4,5] stars.
public class Event
{
public int Id { get; set; }
public string Title { get; set; }
public virtual List<Rating> Ratings { get; set; }
}
public class Rating
{
public int Id { get; set; }
public int Rate1 { get; set; }
public int Rate2 { get; set; }
public int Rate3 { get; set; }
public int Rate4 { get; set; }
public virtual Event Event { get; set; }
}
As an example: 1 event with 3 ratings related like this:
Id: 1
Rate1: 3
Rate2: 2
Rate3: 5
Rate4: 1
Id: 2
Rate1: 4
Rate2: 1
Rate3: 0
Rate4: 1
Id: 3
Rate1: 3
Rate2: 1
Rate3: 3
Rate4: 2
The average value is calculated as follow: ( (3+2+5+1) + (4+1+0+1) + (3+1+3+2) ) / 12 = 2,16 (rounded to 2)
I am wondering if this is possible to write a Linq query to get all the events which are rated 2 ?
Yes, technically.
var eventsWithRatingOfExactlyTwo = Events
.Where(e => (e.Ratings.Average(r => r.Rate1 + r.Rate2 + r.Rate3 + r.Rate4) / 4) == 2);
I doubt the utility of looking for an exact rating, though: you'll probably want to look for items where the rating is in a given range instead.
I'd encourage you to re-think your domain model, too. What makes those four "Rate"s so special that you're unlikely to ever add another Rate per rating? Those should probably be represented as rows in another table, rather than columns on this table.
I have the following entities:
User is:
public String Id { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
Assessment is:
public int Id { get; set; }
public int SymptomId { get; set; }
public string UserId { get; set; }
public DateTime CreatedDate { get; set; }
A User can have 0, 1 or more Assessments.
I have written the following LINQ:
// Get Paged Users Recent Assessment List:
var _usersWithRecentAssessment =
from U in _context.Users
join A in _context.Assessments on U.Id equals A.UserId
group A by A.UserId into uaGroup
select uaGroup.OrderByDescending(a => a.CreatedDate).FirstOrDefault();
_usersWithRecentAssessment = _usersWithRecentAssessment.OrderByDescending(ua => ua.CreatedDate);
which returns the most recent symptom assessment for all Users that have completed an Assessment (and orders the assessment list in descending order of Assessment CreatedDate) as follows:
[
{
"id": 1052,
"symptomId": 44,
"userId": "b978d113-7da7-4b7f-a121-9dd71e158dd4",
"createdDate": "2019-11-16T12:50:05.2175621"
},
{
"id": 1051,
"symptomId": 44,
"userId": "5230f4b7-bf2a-46b0-88a0-6f13fa5caa91",
"createdDate": "2019-11-03T14:46:21.6598763"
}
]
I would like to return the following AssessmentDTO
where AssessmentDTO is:
public int Id { get; set; }
public int SymptomId { get; set; }
public string UserId { get; set; }
public string UserFirstName { get; set; }
public string UserLastName { get; set; }
public DateTime CreatedDate { get; set; }
which contains the additional attributes UserId, UserFirstName, and UserLastName from the User entity.
I have tried unsuccessfully to add 'select new AssessmentDTO() { }' at the end of the LINQ.
Can someone please help me?
A User has zero or more Assessments, every Assessment belongs to exactly one User. This is a standard one-to-many relation with a foreign key.
For your problem, you can use one of the overload of Queryable.GroupBy. First get all Users with their assessments and as a result keep only the most recent assessment:
var UsersWithMostRecentAssessment = dbContext.Users
.GroupBy(dbContext.Assessments, // GroupJoin Users and Assessments
user => user.Id, // From every User take the Id
assessment => assessment.UserId, // From every Assessment take the UserId
// ResultSelector: take each User with its zero or more Assessments to make one new:
(user, assessmentsOfThisUser) => new
{
User = user,
MostRecentAssessment = assessmentsOfThisUser
.OrderByDescending(assessment => assessment.CreatedDate)
.FirstOrDefault(),
// might be null if this User has not assessments at all
})
// Now get the User with its MostRecentAssessment (or null) to make one new:
.Select(userAssessMent => new
{
// Values from the most recent assessment:
Id = userAssessment.MostRecentAssessment.Id,
SymptomId = userAssessment.MostRecentAssessment.SymptomId,
...
// Values from the User:
UserId = userAssessment.User.Id,
FirstName = userAssessment.User.FirstName,
LastName = userAssessment.User.LastName,
...
})
Note: this will go wrong if there are users without assessments, because they won't have a most recent assessment. You can omit these users before the final Select:
.Where(userAssessment => userAssessment.MostRecentAssessment != null)
Or if you want these Users in your endresult:
// Values from the most recent assessment or default if null
Id = userAssessment.MostRecentAssessment.Id ?? 0,
SymptomId = userAssessment.MostRecentAssessment.SymptomId ?? 0,
Using EF Core 2.2.2, I have a table in my database which is used to store notes for many other tables. In other words, it's sortof like a detail table in a master-detail relationship, but with multiple master tables. Consider this simplified EF Model:
public class Person
{
public Guid PersonID { get; set; }
public string Name { set; set; }
}
public class InvoiceItem
{
public Guid InvoiceItemID { get; set; }
public Guid InvoiceID { get; set; }
public string Description { get; set; }
}
public class Invoice
{
public Guid InvoiceID { get; set; }
public int InvoiceNumber { get; set; }
public List<Item> Items { get; set; }
}
public class Notes
{
public Guid NoteID { get; set; }
public Guid NoteParentID { get; set; }
public DateTime NoteDate { get; set; }
public string Note { get; set; }
}
In this case, Notes can store Person notes or Invoice notes (or InvoiceItem notes, though let's just say that the UI doesn't support that).
I have query methods set up like this:
public IQueryable<PersonDTO> GetPersonQuery()
{
return from p in Context.People
select new PersonDTO
{
PersonID = p.PersonID,
Name = p.Name
};
}
public List<PersonDTO> GetPeople()
{
return (from p in GetPersonQuery()
return p).ToList();
}
public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
return from p in Context.Invoices
select new InvoiceDTO
{
InvoiceID = p.InvoiceID,
InvoiceNumber = p.InvoiceNumber
};
}
public List<InvoiceDTO> GetInvoices()
{
return (from i in GetInvoiceQuery()
return i).ToList();
}
These all work as expected. Now, let's say I add InvoiceItems to the Invoice query, like this:
public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
return from p in Context.Invoices
select new InvoiceDTO
{
InvoiceID = p.InvoiceID,
InvoiceNumber = p.InvoiceNumber,
Items = (from ii in p.Items
select new ItemDTO
{
ItemID = ii.ItemID,
Description = ii.Description
}).ToList()
};
}
That also works great, and issues just a couple queries. However, the following:
public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
return from p in Context.Invoices
select new InvoiceDTO
{
InvoiceID = p.InvoiceID,
InvoiceNumber = p.InvoiceNumber,
Items = (from ii in p.Items
select new ItemDTO
{
ItemID = ii.ItemID,
Description = ii.Description
}).ToList(),
Notes = (from n in Context.Notes
where i.InvoiceID = n.NoteParentID
select new NoteDTO
{
NoteID = n.NoteID,
Note = n.Note
}).ToList(),
};
}
sends a separate query to the Note table for each Invoice row in the Invoice table. So, if there are 1,000 invoices in the Invoice table, this is sending something like 1,001 queries to the database.
It appears that the Items subquery does not have the same issue because there is an explicit relationship between Invoices and Items, whereas there isn't a specific relationship between Invoices and Notes (because not all notes are related to invoices).
Is there a way to rewrite that final query, such that it will not send a separate note query for every invoice in the table?
The problem is indeed the correlated subquery versus collection navigation property. EF Core query translator still has issues processing such subqueries, which are in fact logical collection navigation properties and should have been processed in a similar fashion.
Interestingly, simulating collection navigation property with intermediate projection (let operator in LINQ query syntax) seems to fix the issue:
var query =
from i in Context.Invoices
let i_Notes = Context.Notes.Where(n => i.InvoiceID == n.NoteParentID) // <--
select new InvoiceDTO
{
InvoiceID = i.InvoiceID,
InvoiceNumber = i.InvoiceNumber,
Items = (from ii in i.Items
select new ItemDTO
{
ItemID = ii.ItemID,
Description = ii.Description
}).ToList(),
Notes = (from n in i_Notes // <--
select new NoteDTO
{
NoteID = n.NoteID,
Note = n.Note
}).ToList(),
};
I want to specify when a cost is first incurred and a repeat period in years that means the cost is incurred again and again. So I have created a Cost model that looks like this:
public class Cost
{
public Cost()
{
Year = 1;
}
public decimal Amount { get; set; }
public int AnsweredQuestionID { get; set;}
public virtual AnsweredQuestion AnsweredQuestion {get; set;}
public int? RepeatPeriod { get; set; }
}
Now I want to return the costs incurred between two dates, preferably using Linq.
Edit I oversimplified my question. I have PropertyCosts that are incurred on a specific Date and again after a period of time. The date the cost is first incurred is calculated from the date the property was surveyed. The costs model stores the RepeatPeriod and is related to a specific Question/Answer. The cost is incurred if a question has been answered in a specific way for a property. So I've got code that looks a bit like this (still trying to simplify here) but at the moment I'm only getting the first occurences
public IEnumerable<PropertyCost> GetCosts(DateTime startDate, DateTime endDate)
{
IQueryable<AnsweredQuestion> answeredQuestions =
_Uow.AnsweredQuestionRepository
.All
.Where(x => x.PropertySurvey.PropertyID == id);
IQueryable<Cost> allCosts = UOW.CostRepository.All;
IQueryable<PropertyCost> firstOccurences = from aq in answeredQuestions
from c in costs
where aq.ID == c.AnsweredQuestionID
select new PropertyCost
{
QuestionText = aq.Question.Text,
AnswerText = aq.Answer.Text,
UnitCost = c.Amount,
Date = aq.PropertySurvey.Survey.StartDate,
RepeatYears = c.RepeatPeriod
});
//but now I need to insert PropertyCosts for recurring costs that occur when RepeatPeriod is not null
}
How about this....
var costs = firstOccurences.SelectMany (p =>
from x in Enumerable.Range(0, (p.RepeatYears ?? 0) > 0
? ((endDate.Year - p.Date.Year)+1)/p.RepeatYears.Value
: 1)
let date = p.Date.AddYears(x * p.RepeatYears??0)
where startDate <= date && endDate >= date
select new PropertyCost {
QuestionText=p.QuestionText,
AnswerText=p.AnswerText,
UnitCost = p.UnitCost,
Date = date,
RepeatYears = p.RepeatYears
}
)
You may need to convert firstOccurences to an Enumerable first due to translations functions
eg
IEnumerable<PropertyCost> firstOccurences = (from aq in answeredQuestions
from c in costs
where aq.ID == c.AnsweredQuestionID
select new PropertyCost
{
QuestionText = aq.Question.Text,
AnswerText = aq.Answer.Text,
UnitCost = c.Amount,
Date = aq.PropertySurvey.Survey.StartDate,
RepeatYears = c.RepeatPeriod
}).AsEnumerable();
It's a quick fix and the initial 0 from the Enumerable.Range could be replaced with a calculation.
First of all, I have no idea why would you like to return collection of Cost items. You should create another class to handle that data, e.g.:
public class CostOccurrence
{
public decimal Amount { get; set; }
public DateTime Occurrence { get; set; }
}
And then implement your method:
public IEnumerable<CostOccurrence> GetCosts(DateTime startDate, DateTime endDate)
{
DateTime current = FirstIncurred;
while (current < startDate)
current = current.AddYears(RepeatPeriod);
while (current >= startDate && current < endDate)
{
yield return new CostOccurrence { Amount = Amount, Occurrence = current };
current = current.AddYears(RepeatPeriod);
}
}
i wnat to validate the datetime, My Code is:
[Range(typeof(DateTime),
DateTime.Now.AddYears(-65).ToShortDateString(),
DateTime.Now.AddYears(-18).ToShortDateString(),
ErrorMessage = "Value for {0} must be between {1} and {2}")]
public DateTime Birthday { get; set; }
but i get the error:
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
please help me?
This means the values for the Range attribute can't be determined at some later time, it has to be determined at compile time. DateTime.Now isn't a constant, it changes depending on when the code runs.
What you want is a custom DataAnnotation validator. Here's an example of how to build one:
How to create Custom Data Annotation Validators
Put your date validation logic in IsValid()
Here's an implementation. I also am using DateTime.Subtract() as opposed to negative years.
public class DateRangeAttribute : ValidationAttribute
{
public int FirstDateYears { get; set; }
public int SecondDateYears { get; set; }
public DateRangeAttribute()
{
FirstDateYears = 65;
SecondDateYears = 18;
}
public override bool IsValid(object value)
{
DateTime date = DateTime.Parse(value); // assuming it's in a parsable string format
if (date >= DateTime.Now.AddYears(-FirstDateYears)) && date <= DateTime.Now.AddYears(-SecondDateYears)))
{
return true;
}
return false;
}
}
Usage is:
[DateRange(ErrorMessage = "Must be between 18 and 65 years ago")]
public DateTime Birthday { get; set; }
It's also generic so you can specify new range values for the years.
[DateRange(FirstDateYears = 20, SecondDateYears = 10, ErrorMessage = "Must be between 10 and 20 years ago")]
public DateTime Birthday { get; set; }