Creating a "Goal" Tracker for an existing project - asp.net

I have a current MVC project that is near enough complete for the basic aspects. The project tracks my progress throughout my running and weight loss as I find a lot of apps out there a bit bloated.
I have some basic models:
Distance:
public class Distance
{
public int Id { get; set; }
[DisplayName("Distance Run")]
public double DistanceRun { get; set; }
[DisplayName("Choose a date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
public DateTime _Date { get; set; }
[DisplayName("Any Addtional Comments")]
public String AdditionalComments { get; set; }
}
Weight:
{
public int Id{ get; set; }
[DisplayName("Current Weight")]
public double CurrentWeight { get; set; }
[DisplayName("Chose a date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
public DateTime _Date { get; set; }
[DisplayName("Any Addtional Comments")]
public String AdditionalComments { get; set; }
}
how would I go about creating a type of Goal creator for each one of these models. When I mean goal I mean "x amount run by y date"
I would try a possible Goal Model that would have fields similar to:
Goal:
public class Goal
{
public int Id { get; set; }
public virtual double goalValue { get; set; }
public DateTime dateTime { get; set; }
}
My issue lies where to differentiate whether the goal is either for DIstance run or Weight Lost/Gained. Am I on the right lines or should I reconsider and not use a model?

I would suggest using inheritance. Create a base class like:
public class Metric
{
[DisplayName("Chose a date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
public DateTime _Date { get; set; }
[DisplayName("Any Addtional Comments")]
public String AdditionalComments { get; set; }
}
Then, have both Distance and Weight inherit from this:
public class Distance : Metric
{
}
public class Weight : Metric
{
}
Then, your Goal can have a foreign key to Metric:
public class Goal
{
...
public virtual Metric Metric { get; set; }
}
Based on how Entity Framework handles inheritance, it will actually instantiate Distance/Weight depending on which one was actually saved. The property value will be upcasted to Metric, though, so you'll need to cast to access the properties on your derived classes:
if (goal.Metric is Distance)
{
var distanceRun = ((Distance)goal.Metric).DistanceRun;
}
You can also simply use as and then check for null:
var distance = goal.Metric as Distance;
if (distance != null)
{
// do something with `distance`
}

Adding an ID to a reference table that differentiates the different types is a solution to the problem. I think from an MVC-side, inheritance isn't going to work because on postback, the framework won't know what type of object to create (Distance or Weight for instance). So while inheritance may work as #Chris suggested, on the MVC side you need a composite object with some identifying value to determine which type of object is. That's where a reference table can come in handy.
OR, create a view model for the MVC side, and not load and save raw EF objects directly. Doing that adds work, but can ensure the UI design doesn't entangle the database design.

Related

ASP.NET MVC Auto generate integer number

Good day, a really newbie developer here.
I Have a form and it have a entity of "QueueNumber" Can someone show me how to code so that when ever i save my form it generates automatically QueueNumber + the Prefix, btw my prefix entity is in another class
public class Queue
{
public int QueueId { get; set; }
[Required]
public string Name { get; set; }
public string QueueNumber
public int ServiceId { get; set; }
public Service Service { get; set; }
}
-
public class Service
{
public int ServiceId { get; set; }
[Display(Name = "Service Name")]
public string ServiceName { get; set; }
[Display(Name = "Service Letter")]
public string ServiceLetter { get; set; }
[Display(Name = "Status")]
public bool? Status { get; set; }
[Display(Name = "Assigned Location")]
public int? LocationId { get; set; }
public virtual Location Location { get; set; }
public virtual ICollection<Customer> Customer { get; set; }
}
Outcome in database :
1. A001
2. A002
3. A003
i just want to be able to generate a queue number automatically and when i save in data base its like A= Service Letter and 001=QueueNumber. Thankyou
If the QueueNumber needs to be persisted to the table, then I would set it up as a calculated column so that the database can manage computing it and updating it if the underlying fields change.
If it is just something that you want to represent in the UI then I would recommend having the view model calculate this.
The entity can calculate something like this with a [NotMapped] attribute. For example:
public class Queue
{
public int QueueId { get; set; }
[Required]
public string Name { get; set; }
[NotMapped]
public string QueueNumber
{
get { return string.Format("{0}{1:000}", Service?.ServiceLetter ?? "?", QueueId);
}
[ForeignKey("Service")]
public int ServiceId { get; set; }
public Service Service { get; set; }
}
The problem with this approach is that to be able to rely on your Queue to reveal a QueueNumber, the Queue must eager load the Service, or you enable lazy loading and risk that performance hit vs. having Service == #null and getting an exception or invalid QueueNumber result. In the above example, if the Service isn't eager loaded you will get back something like "?001".
I prefer to use ViewModels for a number of reasons including performance, security, and handling conditions like this more cleanly.
For example, given a QueueViewModel as such:
[Serializable]
public sealed class QueueViewModel
{
public int QueueId{ get; set; }
public string Name { get; set; }
public string ServiceName { get; set; }
public string ServiceLetter { get; set; }
public string QueueNumber
{
return string.Format("{0}{1:000}", ServiceLetter, QueueId);
}
}
Then when reading the data, we don't pass Entities to the view, we pass our view model...
var viewModel = context.Queues
.Where(x => x.QueueId == queueId)
.Select(x => new QueueViewModel
{
QueueId = x.QueueId,
Name = x.Name,
ServiceName = x.Service.Name,
ServiceLetter = x.Service.ServiceLetter
}).Single();
return viewModel;
The benefits of this approach:
We don't have to worry about eager/lazy loading. The query fetches everything needed, and our view model can compute anything needed from the data loaded. (Queries can compute values as well if you like, but be wary of limitations in that the query has to be able to go to SQL, so no user functions, etc.)
Performance is improved since the query only returns the data needed rather than entire entity graphs, and no rish of lazy load hits.
Security is improved, we expose no more data to the client than is expected/needed, and we don't open the door for "lazy" updates where entities are attached to a context and saved without proper validation.

Is ok to add [BsonAttribute] to POCOs?

I have an application, structured like this:
Application.Domain
Application.Web.Mvc
Application.MongoDb
In Application.Domain i keep all the POCOs of the application (the domain models).
public class Product
{
public string Id { get; set; }
public string ProductName { get; set; }
public DateTime CreatedDate { get; set; }
}
Now, because i am using MongoDb, i also need to use some of the [BsonAttribute], in order to customize the serialization process.
For example:
public class Product
{
[BsonId]
public string Id { get; set; }
public string ProductName { get; set; }
[BsonDateTimeOptions(Kind = DateTimeKind.Local, DateOnly = true)]
public DateTime CreatedDate { get; set; }
}
If i add these attributes, i will need to also add a reference to MongoDB.Bson.Serialization.Attributes in the Application.Domain project, which i want to avoid.
I think the correct way to do this is to create mapping objects in the Application.MongoDb project, and always map them from POCO to MongoObjects and the other way around every time i work with MongoDb repos.
If this is the correct solution, isn't this a bit overkill?

ASP.NET MVC Data Annotation DataType.Date for Array

I am using the DataType(DataType.Date) data annotation in my models so they are rendered as date-only inputs with an appropriate date picker.
public class Model
{
public int Id { get; set; }
[DataType(DataType.Date)]
public DateTime Date { get; set; }
}
I have a model which contains an array of dates though the data annotation appears to have no effect, is it possible to achieve the same as above?
public class Model
{
public int Id { get; set; }
[DataType(DataType.Date)]
public DateTime[] Dates { get; set; }
}
The comment to my question is my accepted answer
Short answer is no. You could have a property List<DateModel> Dates
where DateModel contains [DataType(DataType.Date)] public DateTime Date { get; set; }

Allow nullable strings and List<string> as datatype in Entity Framework Model in ASP.NET MVC

I have a model class defined as:
public class EventReg
{
public int ID { get; set; }
[Display(Name = "Event Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EventDate { get; set; }
[Display(Name = "Event Time")]
[DataType(DataType.Time)]
[DisplayFormat(DataFormatString = "{0:t}", ApplyFormatInEditMode = true)]
public DateTime EventTime { get; set; }
public List<string> HashTags { get; set; }
public string Category { get; set; }
[Display(Name = "Registered by")]
public string UniqueId { get; set; }
public float Latitude { get; set; }
public float Longitude { get; set; }
public string Description { get; set; }
}
Questions:
Now I don't really know that whether there will be a category and Hash Tags for every event. So, I want to register these fields as Nullable but when I defined these fields as System.Nullable for category or System.Nullable > for HashTags, there was an error saying "The type string/List<string> must be non-nullable value type in order to use it as a parameter 'T' in the generic type or method'System.Nullable<T>'". How to counter this error?
I wanted to be able to update the database schema later so used package manager console(add-migration intial command) to create an *datetime_initial.cs* file which will be executed everytime I call update-database. But due to some reason the field HashTags was not listed in the datetime_initial.cs file created. Why?
Try this:
public class EventReg
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] // !!!
public int ID { get; set; }
[Display(Name = "Event Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime? EventDate { get; set; } // added "?"
[Display(Name = "Event Time")]
[DataType(DataType.Time)]
[DisplayFormat(DataFormatString = "{0:t}", ApplyFormatInEditMode = true)]
public DateTime? EventTime { get; set; } // added "?"
public virtual ICollection<string> HashTags { get; set; } // changing List to ICollection virtual
public string Category { get; set; }
[Display(Name = "Registered by")]
public string UniqueId { get; set; }
public float Latitude { get; set; }
public float Longitude { get; set; }
public string Description { get; set; }
}
Also, feel free to get rid of EventDate and EventTime, because DateTime is holding date and time:
public DateTime? EventDateStamp { get; set; }
You can define your own read-only properties if you need a time or data. But it's not recommended to do this in model, because it's considered as a good practice if you keep your model consistent, and when your model is not dependent on how are you going to show it to the user. You can use view models for this purpose and use Automapper tool for that.
One more advice is to get rid of DateTime and use DateTimeOffset:
public DateTimeOffset? EventDateStampUtc { get; set; }
with universal date and time:
model.EventDateStampUtc = DateTimeOffset.Utc;
The main benefit of using UTC time is that you don't need to think about date and time if you have multiple servers, located in other countries, with different times and timezones. Also, you and your customers are not dependent on the server's timezone.
And since this question is about ASP.NET MVC, I can suggest you may want to display this date and time later in your view. I recommend to use wonderful moment.js library on the client side for this purpose.
...which will be executed everytime I call update-database...
Use Seed method in your Migrations\Configuration.cs for this purposes:
protected override void Seed(MyContext context)
{
context.MyEntities.AddOrUpdate(
e => e.Key,
new MyEntity { aa = 1, bb = 2, key = "unique-001" },
new MyEntity { aa = 11, bb = 22, key = "unique-002" }
);
}
Some useful options in Configuration constructor:
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true; // take care!
}

Is it OK to declare a DBSet in the context for both a base table and a derived table?

I have a SalesOrder table which inherits from a SalesDocument table using Table Per Type Inheritance
The ( simplified) table classes are;
[Table("SalesDocumentHeaders")]
public abstract class SalesDocumentHeader
{
[ForeignKey("CreatedByUserId")]
public virtual User CreatedBy { get; set; }
[Required]
public int CreatedByUserId { get; set; }
[Required]
public virtual DateTime? DocumentDate { get; set; }
[Required]
public String ReferenceNumber { get; set; }
}
[Table("SalesOrders")]
public class SalesOrder : SalesDocumentHeader
{
[Required]
public String CustomerOrderNumber { get; set; }
public DateTime? DeliverBy { get; set; }
public virtual SortableBindingList<SalesOrderLine> Lines { get; set; }
}
The context contains
public DbSet<SalesOrder> SalesOrders { get; set; }
public DbSet<SalesDocumentHeader> SalesDocumentHeaders { get; set; }
It doesn't strictly need the SalesOrders DBSet, since SalesOrder inherits from SalesDocumentHeader however I find it convenient.
It seems to work OK, but I am worried that there are 2 ways of reaching the same record , am I doing something wrong?
Usually you only need to keep the DBSet for the base table. This helps when you have multiple derived tables (call them A and B) and you need to decide the actual type dynamically.
For example if you have another entity which references type A or B (like a user can have different types of contact information), you can reference the base table and EF will resolve the correct concrete type at runtime. Though of course this adds some extra casting code.

Resources