Background:
I'm building a system to assign academic advisors to university students based on the student's attributes and advisor's acceptance. Now I want to check to see if every possible student can be assigned an advisor before I start processing students and committing changes to our CRM and Active Directory. There are about 15,000 records, so I don't want to commit changes unless all students have an advisor.
My current system has an 'AdvisorGroup' object to hold a list of 'AdvisorRules.' My 'AdvisorRules' object is populated from CRM:
class AdvisorRule
{
public AdvisorCondition Condition { get; set; }
public AdvisorField Field { get; set; }
public String Value { get; set; }
}
AdvisorCondition can be LessThanEqual, GreaterThanEqual, or Equal. AdivsorField can be Credit(how many credits each advisor will accept), international(if the advisor accepts intentional students), honors(if the advisor accepts honors students or not) and lastName(last names the advisor will accept.)
'Value' is the target that is compared to 'AdvisorField.' For example the list of AdvisorRules in an AdvisorGroup could hold the following rules:
AdvisorCondition = "Equal" AdvisorField = "International" Value = No
AdvisorCondition = "Equal" AdvisorField = "honors" Value = No
AdvisorCondition = "LessThanEqual" AdvisorField = "Credit" Value = 89
AdvisorCondition = "Equal" AdvisorField = "LastName" Value = A
AdvisorCondition = "Equal" AdvisorField = "LastName" Value = B
This advisor would accept a student with a last name starting with A or B, with less than 89 credits, and is not an international or honors student.
Question:
Is there a way to check to make sure every possible student will be assigned an advisor before I start processing? I'm trying not to be extremely explicit in my coding. I was thinking something like this, but would be open to redesigning if I can find a better way:
private bool checkRuleCoverage()
{
//somehow generate list of possible scenarios here
foreach (possible scenario in list of possible scenarios)
{
bool covered = isCovered(possible scenario);
if(!covered) {throw error and return}
}
}
private bool isCovered(List<AdvisorGroup> adGroups, possibleScenario ps)
{
foreach (AdvisorGroup advisor in adGroups)
{
foreach (AdvisorRule rule in advisor.rules)
{
if(advisor.rules == ps)
return true;
}
}
return false;
}
If you have all of your student records in a database, then the solution would be to use set logic with appropriate indexing to get the answer. For example, you could code your own solution such that you sort the student body along each of the possible values that an advisor rule applies to.
Then, without iterating (though you've already iterated in order to sort) you can find all of the students below x or over y on a certain train, and then compare that to the students outside of the range on another stat and so on. Any student outside of the range on all stats like Mao47's comment suggest would be un-advisable.
Of course, in the time it took you to code this in C# you could have just iterated over the advisor rules and found the values of the ranges and applied this to a where clause in a select on SQLServer or MySQL or any DB that's set-based.
Of course, this rough check misses out on the possibility of partial rule-sets, where a student is outside of the range of all but 1 advisor rule, and then that 1 rule is not enough because the advisors also have other requirements. In order to solve that, you will have to have multiple advisor-rule-set-ranges, or rather, you will iterate over all advisor-rules in the worst case. Doing this in the DB will probably still be ok, as long as the student's scores on the rules-properties are properly indexed.
Related
I am looking for an easiest way to assign values to the properties of a class dynamically.
In my WPF App, I have a list header values as shown below :
Name Mark Class --> header values (Name, Mark, Class these are the 3 headers)
---- --- -----
Nick 10 5
Tom 12 7
John 09 4
Sometimes, the header values changes:
Name Mark Percent Phone --> 4 header values
---- ----- ------- -----
So we cannot predict the number of headers. Sometimes it will be 5, sometimes it will be 2,
sometimes the number of headers will be 6, etc
So I created a class Named StudentMark with following properties.
public class StudentMark
{
[Order]
public double? Col1 {get;set;}
[Order]
public double? Col2 {get;set;}
[Order]
public double? Col3 {get;set;}
- - - - -
[Order]
public double? Col10 {get;set;}
}
When I get the list from server, I assign the values to the properties of the student mark class are shown as below code. This is doing for plotting a graph. We don't know the column header values in advance, we do like these.
StudentMark sm = new StudentMark();
var properties = from property in typeof(StudentMark).GetProperties()
where Attribute.IsDefined(property, typeof(OrderAttribute))
orderby ((OrderAttribute)property.GetCustomAttributes(typeof(OrderAttribute), false).Single()).Order
select property;
//PropertyInfo[] properties = typeof(StudentMark).GetProperties();
foreach (string str in HeaderColumns)
{
var d1 = csv.GetField<string>(str); // taking cell value
foreach (PropertyInfo property in properties)
{
if (property.PropertyType == typeof(Double?))
{
if (property.GetValue(sm) == null)
{
property.SetValue(sm, d1);
break;
}
}
}
}
this is working but very very very slow. Is there anyway to do it without using reflection ??
Try to keep typed data separate from arbitrary untyped data. So if you want to represent a table of arbitrary user defined data you would do it something like:
public class MyTable{
public List<string> Headers {get;}
public List<List<string>> Rows {get;}
}
If the only thing you will be doing is presenting it to the user, and reading writing csv-files, such an representation is perfectly appropriate.
It is only when you need to use the data for something you need to use actual types to represent the data. But this can be quite complicated and would involve a lot of data validation to ensure the data is on the correct format.
You could for example have logic to check if each header has some specific value, and then parse the values for each row to a set of predefined types. If either the headers have incorrect values, or the parsing of some value fails, you would give an error and let the user deal with the problem.
If you want minimize the risk of mistakes, you should make them impossible in the first place, or detect them as early as possible, i.e. enforce the correct header names, parse values as soon as they are entered etc.
If you need to combine typed data with untyped data you could for example use a dictionary:
public class MyDataRow{
public string name {get;}
public int Mark {get;}
public Dictionary<string, string> UserDefinedData {get;}
The user defined data would just be treated as an opaque data blob, tracked thru the system, but not used for any computation, or if it is, with a great deal of error checking.
In a previous question I presented these models:
public class Calendar
{
public int ID { get; set; }
public ICollection<Day> Days { get; set; }
}
public class Day
{
public int ID { get; set; }
public DateTime Date { get; set; }
public int CalendarID { get; set; }
}
There is a uniqueness constraint so that you can't have more than one Day with the same Date and CalendarID.
My question now is what if I want to move all days one day into the future (or whatever). The easiest code is just a for loop like
for(Day day in days) {
day.Date = day.Date.AddDays(1);
db.Entry(day).State = EntityState.Modified;
}
await db.SaveChangesAsync();
This will fail, however, because it thinks you are creating duplicates of some of the dates, even though once they're all updated it will work out.
Calling SaveChangesAsync after each day (assuming you process the days in the correct order) would work, but seems massively inefficient for this basic task.
An alternative to updating the Date would be to transfer all the other data of each day to the next one, but this also seems inefficient and could in some cases be undesirable because it means that data is dissociated from the Day's primary key value.
Is there a way to update all the dates while keeping the uniqueness constraint?
The number of SQL UPDATE statements won't change if you call SaveChanges() for each record instead of calling it only once, but at least you'll get the correct order. There's some overhead because of state cleaning and connection management but it's not massively inefficient.
If date shifting is an isolated business transaction you could use a simpler solution instead of fighting with ORM - call a stored procedure or execute SQL directly with something similar to:
var sql = "UPDATE d SET Date = DATEADD(d, 1, Date) FROM (SELECT * FROM Day WHERE CalendarID=#calendarId ORDER BY Date DESC) d";
var updateCnt = db.Database.ExecuteSqlCommand(sql, new SqlParameter("#calendarId", calendar.Id);
if (updateCnt != days.Count)
{
//oops
}
One of the many possible solutions is removing all the records before you do the update.
You can first get your days, store them in memory.
var days = db.Day.Tolist();
Truncate the table, so they won't collide with the new list coming:
db.ExecuteCommand("TRUNCATE TABLE Day");
Do your stuff:
foreach(var day in days)
{
day.Date=day.Date.AddDays(1);
}
Insert your new list.
Now you should be able to save it:
db.SaveChanges();
This should be efficient enough since the quickest way to wipe data is to truncate, and your day objects are child objects.
HOWEVER
If a property is changing a lot, probably it's not a good idea to make it a primary key.
If you find yourself in a conflict with fundamentals, it's quite possible that you made an architectural mistake.
I strongly recommend you to change your primary key to something else, you can even roll a uniqueidentifier column to store Id.
I'm having a really strange problem here, and i dont have any clue why.
I'm supposed to make small localdb console app in C#. The goal is to enter persons (teachers, actually) in the DB, with a certain amount of information.
I have a few classes, but 2 of them are important here: Certification and Notation.
Certifications are, well, certifications of the professors.
The code for these classes is this:
class Certification
{
public int CertificationID { get; set; }
public virtual Teacher Teacher { get; set; }
public virtual Course Course { get; set; }
public string CertificationName { get; set; }
public virtual Notation Notation { get; set; }
}
class Notation
{
public int NotationID {get;set;}
public string Note {get;set;}
}
Nothing too dangerous. Through migrations i made my database, and they look like they should:
Certification:
CertificationID (PK)
CertificationName
Course_CourseID (FK to another class, course)
Notation_NotationID (FK to notations)
Teacher_TeacherID (FK to the teachers)
Notations:
NotationID (PK)
Note
My program allows me to add teachers, with all the informations i need, and for example, their certifications. Here, i made some dummy teacher, with a dummy certification.
If i call SELECT * FROM Certification , i get exactly what i should get, a single line like this:
CertificationID = 6
CertificationName = placeholder
Course_CourseID = 13
Notation_NotationID = 12
Teacher_TeacherID = 5
Everything is correct in this. CourseID links to an actual course in the database, NotationID in an actual note, and Teacher to an actual teacher too. Everything is fine!
Now, i just want to show the certifications of our teacher:
var certifs = from c in db.Certifications where c.Teacher.TeacherID == item.TeacherID select c;
foreach(var v in certifs )
{
var course = (from c in db.Courses where c.CourseID == v.Course.CourseID select c).First();
var note = (from n in db.Notations where n.NotationID == v.Notation.NotationID select n.NotationID).First();
Console.WriteLine("Name: " + v.CertificationName + ", related to the " + course.CourseName + " course, with a note of " + note);
Console.WriteLine("");
}
And it doesn't work. When my foreach loop starts, my first item in the loop doesn't have any reference to a notation. Everything else is fine: the foreign keys for the course and the teachers are here, and valid, but for the notation, i only get a null value. So my certification item looks more like:
CertificationID = 6
CertificationName = placeholder
Course_CourseID = 13
Notation_NotationID = null
Teacher_TeacherID = 5
Basically, if i do a SQL Query, my row in the database is perfectly fine, but calling it through the entity framework (and LINQ) returns a null value for the notation. (which throws an exception when calling var note etc....
Does anybody have an idea about this? I'm really stuck on this.
I'm sorry if my English isn't good enough. If you guys need more information, just ask.
Anwsered by JC:
Lazy loading isnt working properly. Eager loading solves the problem.
The problem is you aren't populating your navigation properties when you retrieve the Certification entities. Then you try to access them and they're null.
You either need to make sure lazy loading is turned on:
Configuration.LazyLoadingEnabled = true; //In your DbContext's constructor
in which case just accessing the Course and Notification references should cause them to be populated in separate database transactions...
...or you need to employ eager loading when querying against the DbSet:
var certifs = from c in db.Certifications.Include(c=>c.Course).Include(c=>c.Notation) where ...
Which will cause Course and Notation to be loaded at the same time Certifications is loaded all in one database transaction.
In your line
var note = (from n in db.Notations
where n.NotationID == v.Notation.NotationID
select n.NotationID).First();
you are selecting n.NotationID only which would return an integer only. Trying changing the select to select n
In order to ask the question let me
write a class which contain my question.
class Student{
public int StudentId;
public string Name;
public int Age;
public string Address;
public void Save(){
if (StudentId == 0){
//// run the insert query here
/*
Insert into Student(Studentid,Name,Age,Address)values(...);
} else {
/// run the update query...
/*Update Student Set Address = #address , Age = #age , Name = #name
where StudentId = #stdid;*/
}
}
Now in order to use that class I will write the following code.
Student Std = new Student{StudentId = 1, Name="xyz", Address = "Home"};
Std.Save(); //// this will insert the value in the database
Now later on.
Student std1 = new Student();
std1.Studentid = 1;
std1.Age= 10;
std1.Save();
Now calling save this time will over write the Address with Null or empty string.
I seek advise on possible solutions to this problem.
There is a possibility that consumer wants to change the address with empty string.
Looking forward to replies.
Regards
K
}
What you are doing is instantiating a new Student object, and simply assigning new values to it. Since you never assign an address, it will be null.
Your insert/update logic is flawed, because it assumes that having a 0 for an ID means that the values have already been persisted (which may or may not be the case - your class design does allow for creation of a Student that is not persisted and give it any old ID). The result is that you are saving the values to the database, including the null address.
Your logic should be:
If student ID is not 0:
Start a transaction
Get student details from DB
Update student object with new details
Save
Commit the transaction
You should also restructure your Student class so the Id can't be simply written to (possibly just in the constructor) and have a way to load a Student from your database.
I think you should setup your structure differently.
Right now you are creating a new Student object fill it with (only) the changed data, and trying to update it by Studentid.
I think you should also create a (static) method like GetStudentById(int id). Let this return the student from the database with all properties filled, make your changes and write it back with the Save(). Now the values won't be empty or reset.
Instead of managing default values in your code, that can vary during a time, can be a lot, it's easier to manage default value setup on DB side. Just set the field of corresponding DB field default value to the value you need and stop carrying about it in your code. Naturally if you have access to DB structure and allowed to change it.
If you don't want reset the valu in a new object (it's not very clear from your post), just use a (say) Clone(..) method of your Student type, and after change only fields that very.
Example:
Student std= new Student{StudentId = 1, Name="xyz", Address = "Home"};
Std.Save();
//after
Student std1 = std.Clone(); //after clone you have Adrress field too in std1
std1.Studentid = 1;
std1.Age= 10;
std1.Save();
There's no magic method you can use for it... You'll simply have to check for null and decide whether to overwrite or not at some point.
There are ORM frameworks you can use like NHibernate or Entity Framework that will do the plumbing like this automatically if you're interested.
I've spent the last 30 mins looking through existing answers for what I think is a common question, but nothing quite hits it for me. Apologies if this is a dupe.
I've got a list of objects.
List<Journey> journeys;
The object has a 'status' property - this is a string.
class Journey
{
public string Name;
public string Status;
}
I want to sort based on this string, however not alphabetically. The status depicts the object's position through a journey, either "Enroute", "Finished", or "Error". When sorted ascending I want them to appear in the following order: "Error", "Enroute", "Finished". (In practice there are more statuses than this so renaming them to fall in alphabetical order isn't an option)
Aside from creating a class for 'status' with value and sort order properties, and then sorting based on that, how do I do this? Or is that the best method?
You can define the you sorting logic inside of custom function which is provided to Comparison delegate:
List<Journey> list = new List<Journey>();
list.Sort(new Comparison<Journey>((Journey source, Journey compare) =>
{
// here is my custom compare logic
return // -1, 0 or 1
}));
Just another thought:
class Journey
{
public enum JourneyStatus
{
Enroute,
Finished,
Error
}
public string Name;
public JourneyStatus Status;
}
Used with OrderBy:
var journeys = new List<Journey>();
journeys.Add(new Journey() { Name = "Test1", Status = Journey.JourneyStatus.Enroute });
journeys.Add(new Journey() { Name = "Test2", Status = Journey.JourneyStatus.Error });
journeys.Add(new Journey() { Name = "Test3", Status = Journey.JourneyStatus.Finished });
journeys.Add(new Journey() { Name = "Test4", Status = Journey.JourneyStatus.Enroute });
journeys = journeys.OrderBy(x => x.Status).ToList();
foreach (var j in journeys)
Console.WriteLine("{0} : {1}", j.Name, j.Status);
Output:
Test1 : Enroute
Test4 : Enroute
Test3 : Finished
Test2 : Error
Or you might modify the lambda passed to OrderBy to map the value of Status string to an int.
In some situations you might want to implement IComparer<T>, like Jon said. It can help keeping the sorting logic and the class definition itself in one place.
You need to create a class that implements IComparer<Journey> and implement the Compare method accordingly.
You don't mention how you are sorting exactly, but pretty much all methods of the BCL that involve sorting have an overload that accepts an IComparer so that you can plug in your logic.
Aside from creating a class for 'status' with value and sort order properties, and then sorting based on that, how do I do this?
As this is some custom order you need creating such a class is a good idea.
Derive from Comparer<T> or StringComparer class, implement class with your custom logic, pass instance of this class to sort method.
One way to do this is to assign an order number to each item. So create a database table to hold your items and their order:
Column Name, Column Order
Enrout , 0
Finished, 1
Error, 2
Then when I populate a drop down I can sort by the Order in my SQL select rather than the name.
Or if you don't want to use a database, you could change Status to include the order and then just parse out the Status name: so Status values might look like this: "0,Enrout","1,Finished","2,Error" then it will naturally sort. Then just use split to separate the order from the name:
string[] statusArr = status.split(',');
string order = statusArr[0];
string statusname = statusArr[1];
There's a lot of ways to skin this cat.