avoid redundant conditional statements - c#

Using the following code, assume I have 5 different types that I might receive in the variable type. Instead of writing 5 conditional statements, is there a way to write one and use the variable "type" to dictate what the model is, in this case "CommentVote?" Or is this more a deficiency in the way I've designed the data model with each of those 5 things having a "vote" model?
if (type == "comment")
{
CommentVote voteObj = db.CommentVotes
.Where(x => x.UserID == UserID && x.CommentID == id)
.SingleOrDefault();
if (voteObj != null)
{
voteObj.Vote = vote;
db.SaveChanges();
}
else
{
CommentVote c = new CommentVote {
CommentID = id, UserID = UserID, Vote = vote, DateCreated = DateTime.Now
};
db.CommentVotes.Add(c);
db.SaveChanges();
}
count = (db.CommentVotes.Count(x => x.CommentID == id && x.Vote == true) - db.CommentVotes.Count(x => x.CommentID == id && x.Vote == false));
}
Magic Code: The stuff I would love to be able to do.
var modelName = "";
var modelOtherName = "";
if (type == "comment") {
modelName = CommentVote;
modelOtherName = CommentVotes;
}
modelName voteObj = db.modelOtherName
.Where(x => x.UserID == UserID && x.CommentID == id)
.SingleOrDefault();
Update: I'm beginning to think my model may be crap based on some of the reading referenced bellow. So I am including some of that as a reference. Let me know if that's the problem I should be trying to solve.
public class CommentVote
{
public int CommentVoteID { get; set; }
public bool Vote { get; set; }
public DateTime DateCreated { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
public int CommentID { get; set; } //This row changes from model to model
public virtual Comment Comment { get; set; } //This row changes from model to model
}
I have a handful of models that are almost identical.

As I understand you question, it more database architecture-related.
If those kind of votes are not very different from each other (in terms of properties) I woldn't use different tables for them. Instead create one Vote table with Type column and (as in the example you provided) nullable column for CommentID.
Then you can use class inheritance to reflect your votes (Vote base class and CommentedVote child class).
Table Per Hierarchy Inheritance in Entity Framework
Update:
Best is not to repeat the same propertieses in all classes. You just use inharitence like this:
public abstract class Vote
{
public int VoteID { get; set; }
public bool isVote { get; set; }
public DateTime DateCreated { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
public int VoteType { get; set;} //this property specifies type of vote (e.g. VoteType=1 for CommentedVote )
}
public class CommentVote : Vote
{
public int CommentID { get; set; }
public virtual Comment Comment { get; set; }
}
public class OtherVote : Vote
{
public int OtherID { get; set; }
public virtual Other Other { get; set; }
}
In this very good blog post you can find all possible approches. The one I'm writing about is called Table per Hierarchy (TPH).

You can absolutely reduce the code to a single statement assuming that you perform the same actions and set the same data. In this case, you should have an interface that contains the common actions and data and an object factory to instantiate the correct object based on the type.

You could do it if you implement the Factory pattern with reflection, a very basic example is shown here.
In a nutshell what you do is this: Since you have 5 different types that it could be, you would make 5 different classes that each implement a specific interface. You then create the factory class to use reflection to grab the class that is the most appropriate for your situation (be it with a straight-up class name, like in the example, or with an Attribute over the class, such as here). The factory returns an instance of that interface, which you would then just invoke the exposed method from the interface to do all of this for you.
The best part of this is that if you ever need to make another type, all you'd have to do is add another class with that attribute/name that you would be searching for in the factory. None of your other code would need to be affected, thus making you compliant with the Open/Closed Principle.

Related

What's the correct way to reference tables using Code First with EF.Core for searching efficiently

Fairly new to EF.Core and I'm having some issues as my tables start getting more complex. Here's an example of what I have defined for my classes. Note ... there are many more columns and tables than what I have defined below. I've paired them down for brevity.
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Active { get; set; }
}
Followed by
public class JournalEntry
{
public int Id { get; set; }
public int UserId { get; set; }
public string Details { get; set; }
public DateTime DateEntered { get; set; }
public virtual User User { get; set; }
}
I want to be able to issue the following query and INCLUDE the User Table so that I can then populate a ViewModel with columns from the User Table without having to do another lookup and also to sort the data while retrieving it:
public IQueryable<JournalEntry> GetByUser(int userId)
{
return _DbContext.JournalEntries.Where(j => j.UserId == userId)
.Include(u => u.User)
.OrderBy(u=> u.User.FirstName)
.ThenBy(j => j.DateEntered);
}
My controller would then have something similar to the following:
public IActionResult List(int userId)
{
var journalEntries = new _dbRepository.GetByUser(userId);
var myViewModel = new MyViewModel();
myViewModel.UserName = ($"{journalEntries.User.FirstName} {journalEntries.User.LastName}");
myViewModel.Entries = journalEntries;
etc ....
return View(myViewModel);
}
I'm loading the user's first and last name in the View Model and whatever other attributes from the various tables that are referenced. The problem that I'm having is that I'm getting errors on the Migration creation "Foreign key constraint may cause cycle or multiple cascade paths." And of course, if I remove the line reading public virtual User User { get; set; } from the JournalEntry class then the problem goes away (as one would expect).
I believe that the way I'm doing the models is incorrect. What would be the recommended way that I should code these models? I've heard of "lazy loading". Is that what I should be moving towards?
Thanks a bunch.
--- Val
Your query returns an IQueryable<JournalEntry> not a JournalEntry.
Change the code to get the user details from the first object:
var myViewModel.UserName = ($"{journalEntries.First().User.FirstName} {journalEntries.First().User.LastName}");
In the line above I'm calling First() on your journal entries collection and that would have a User. Then I can access FirstName and LastName.
Also, don't bother with LazyLoading since you are learning. It could cause select n+1 issues if used incorrectly

Conditional Include in asp.net core and Entity Framework

I just started my first project using asp.net core and for the first time I'm gonna use the code repository for my project in C# and VS 2019.
I create a new Model and it called Comment. This Table can save all of the comments on the project, That mean is user comments in POSTS, SOCIALMEDIA, and etc Areas saved in this table
[Key]
public int CommentId { get; set; }
public eTable Table { get; set; }
public int ContentId { get; set; }
[StringLength(1500)]
public string Notes { get; set; }
public virtual AnalysedMarket AnalysedMarket { get; set; }
ContentID is my foreign key, And my eTable enum type is like bellow:
public enum eTable
{
AnalysedMarket,
Blog,
News,
Migration,
}
I created a new class for AnalysedMarket as well to save Users data from our social media area.
[Key]
public int AnalysedMarketId { get; set; }
[StringLength(255)]
public string Images { get; set; }
public int Hits { get; set; }
public string Notes { get; set; }``
Now I created a method in my code repository for extract data using EF and LINQ to get list of AnalysedMarket data but I can't Include my result with Comment table and result of my code repository in the comment section is null always.
public async Task<IEnumerable<AnalysedMarket>> List(int? page, int? perPage, eStatus? status, string userId)
{
var query = _db.AnalysedMarkets.Select(a => a);
if (status.HasValue)
query = query.Where(m => m.Status.Equals(status));
if (!string.IsNullOrEmpty(userId))
query.Where(m => m.CreatedBy.Equals(userId));
query.Include(a => a.Comments.Where(c => c.Table.Equals(eTable.AnalysedMarket) && c.ContentId == a.AnalysedMarketId));
if (page.HasValue && perPage.HasValue)
return await query.OrderBy(a => a.AnalysedMarketId).ToPagedListAsync(page.Value, perPage.Value);
else
return await query.OrderBy(a => a.AnalysedMarketId).ToListAsync();
}
Actually my question is how can I get list of AnalysedMarket data included by Comment data.
And it has a condition and it says include comment if ContentId is equal to AnalysedMarketId and eTable is Table.AnalysedMarket.
I read the articles about conditional Include but I didn't get any thing of them.
Example 1
Example 2
You need to add a reference from AnalysedMarket to comment like this in your AnalysedMarket-Class:
ICollection<Comment> Comments { get; set; }
And then include them while querying your AnalysedMarkets like this:
var query = _db.AnalysedMarkets.Include(c => c.Comments);
/Edit:
Regarding your comment - for this you would need kind of an hierarchy/inheritance structure. It seems to be supported by EfCore and something like this should work:
public class CommentableItem {
ICollection<Comment> Comments {get;set;}
}
public class Comment {
CommentableItem CommentableItem {get;set;}
}
public class AnalysedMarket : CommentableItem {
}
Than you should be able to use the include for each item inheriting from CommentableItem. I did not use the inheritance feature yet (as far as I know this is quite new for EF Core), so for further instructions check the documentation

ViewModel Object Convert to Entity Framework Object

Goal: to save ViewModel object by Entity Framework. I have UserViewModel object which has list of UnitViewModel. Then, I have a UserAdapter class which converts UserViewModel into Entity Framework User object (see Convert()below how).
Now, my question is how do I convert this list of UnitViewModel to its corresponding Entity Framework Unit list? - Do I have to get each object from DB Context by calling something like context.Units.Where(u=>myListofUnitIDs.Contains(u.UnitID))?
public class UserViewModel
{
public Guid? UserID { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public DateTime? CreateTime { get; set; }
public List<UnitViewModel> UserUnits { get; set; }
}
public class UnitViewModel
{
public Guid UnitID { get; set; }
public string Name { get; set; }
public int? SortIndex { get; set; }
public DateTime CreateTime { get; set; }
public bool Assigned { get; set; }
}
public class UserAdapter
{
public static User Convert(UserViewModel userView)
{
User user;
if (userView.UserID.HasValue)
{
using (var provider = new CoinsDB.UsersProvider())
{
user = provider.GetUser(userView.UserID.Value);
}
}
else
{
user = new User();
}
user.FirstName = userView.FirstName;
user.LastName = user.LastName;
user.Password = StringHelper.GetSHA1(userView.Password);
user.UserName = user.UserName;
user.CreateTime = DateTime.Now;
// Problem here :)
// user.Units = userView.UserUnits;
return user;
}
}
UPDATE: The main concern here is that I have to retrieve each Unit from database to match (or map) it with ViewModel.Unit objects, right? Can I avoid it?
For your information, this operation is called as Mapping mainly. So, you want to map your view model object to the entity object.
For this, you can either use already existed 3rd party library as AutoMapper. It will map properties by reflection which have same name. Also you can add your custom logic with After method. But, this approach has some advantages and disadvantages. Being aware of these disadvantages could help you to decide whether you must use this API or not. So, I suggest you to read some articles about advantages and disadvantages of AutoMapper especially for converting entities to other models. One of such disadvantages is that it can be problem to change the name of one property in the view model in the future, and AutoMapper will not handle this anymore and you won't get any warning about this.
foreach(var item in userView.UserUnits)
{
// get the mapped instance of UnitViewModel as Unit
var userUnit = Mapper.Map<UnitViewModel, UserUnit>(item);
user.Units.Add(userUnit);
}
So, I recommend to write your custom mappers.
For example, I have created a custom library for this and it maps objects lik this:
user.Units = userView.UserUnits
.Select(userUnitViewModel => userUnitViewModel.MapTo<UserUnit>())
.ToList();
And I am implementing these mapping functions as:
public class UserUnitMapper:
IMapToNew<UnitViewModel, UserUnit>
{
public UnitViewModel Map(UserUnit source)
{
return new UnitViewModel
{
Name = source.Name,
...
};
}
}
And then in runtime, I am detecting the types of the objects which will be used during mapping, and then call the Map method. In this way, your mappers will be seperated from your action methods. But, if you want it urgently, of course you can use this:
foreach(var item in userView.UserUnits)
{
// get the mapped instance of UnitViewModel as Unit
var userUnit= new UserUnit()
{
Name = item.Name,
...
};
user.Units.Add(userUnit);
}

Entity Framework (5.0) Code First - Insert into Collection within Collection

I've got three classes.
Event > Workshop > Workshop Times
I'm currently looking for best way of inserting records into the Workshop Times, this is running through code first using ICollections.
Looking for something along the lines of this, but I know it doesn't work:
//Create connection
var db = new Context();
var Event = db.Events
.Include("Workshops")
.Include("Workshops.Times")
.Where(ev => ev.GUID == EventGUID).FirstOrDefault();
Event.Workshops.Add(new Workshop
{
Name = tbWorkshopName.Text,
Description = tbWorkshopDescription.Text,
Times.Add(new WorkshopTime{
//Information for times
})
});
db.SaveChanges();
Chopped down classes:
public class Workshops{
public int id { get; set; }
public string name { get; set; }
public ICollection<WorkshopTimes> Times{get;set;}
}
public class Events {
public int id { get; set; }
public string name { get; set; }
public ICollection<Workshops> WorkShops { get; set; }
}
public class WorkshopTimes {
public int id { get; set; }
public DateTime time { get; set; }
}
You are definitely on the right track with your query, however your include statements appear incorrect. From your model I would expect:
var Event = db.Events
.Include("WorkShops")
.Include("WorkShops.events")
.Where(ev => ev.GUID == EventGUID).FirstOrDefault();
Note this uses the property names not the types. This will ensure that the entities in the listed nav properties will be included in the result.
In addition you can use a lambda to do the same thing (but its typesafe)
Check out here for how to do a very similar scenario to yours:
EF Code First - Include(x => x.Properties.Entity) a 1 : Many association
or from rowan miller (from EF team)
http://romiller.com/2010/07/14/ef-ctp4-tips-tricks-include-with-lambda/
And make sure you are using System.Data.Entities for lambda based includes ( Where did the overload of DbQuery.Include() go that takes a lambda? )

Validating Against Property of 2nd Model

As part of my objective of learning a new skill at work I am attempting to develop an employee management system in ASP.NET MVC (MVC 4).
I am trying to follow the convention of performing all validation at the model level (not only because this is what I have read is recommended but also as there is talk of a desktop app that may use parts of the model so I want to ensure any constraints are enforced in that app too!).
My issue is, I have some data on the Person class (RemainingHoliday). When create a HolidayRequest I want to ensure that the request is not for a greater number of days than the person has remaining.
How would I go about doing this? I know that I can create my own validation rules by extending the ValidationAttribute, but how would I get from the HolidayRequest class to the Person class within here?
A snippet of the models:
public class Person
{
public string PersonID { get; set; } // this is populated with Users AD Guid
public string HolidayEntitlement { get; set; }
public virtual ICollection<HolidayRequest> Holidays { get; set; }
public int TotalEntitlement(int year = -1)
{
return this.HolidayEntitlement + this.HolidayAdjustments.Where(a => a.LeaveYear.Year == year).Sum(a => a.Adjustment);
}
public int RemainingHoliday(int year = -1)
{
return this.TotalEntitlement(year) - this.Holidays.Where(h => h.Start.Year == year).Where(h => h.Status != HolidayStatus.Rejected).Sum(h => h.Duration);
}
}
public class HolidayRequest
{
public string HolidayId { get; set; }
public DateTime Start { get; set; }
public DateTime Finish { get; set; }
public int Duration { get; set; } // This cannot be greater than Person.RemainingHoliday
}
I would really appreciate any pointers or samples for this, or perhaps I am trying to be too ideal and this cannot be done in the model?
It seems that you are missing a reference to the person requesting the holiday in the HolidayRequest object. I would have expected to see a Person or PersonId in the HolidayRequest object. Once there, you could compute the difference between Duration and RemainingHoliday.

Categories