Selecting Data directly vs using navigation property in EF - asp.net-mvc

Hi there in a MVC application I have the following sample POCO classes(Actual application has 10-15 screens for employee data). Manager has many employees and he can update their personal and contact details. I am considering 3 approaches to retrieve the current employee data from database using EF.
1) When manager select an employee, store EmployeeId in Session (Session["currentEmp"]) and use this EmployeeId to get the data for the current employee.
like:
int employeeId = (int)Session["currentEmp"];
EmployeeContactDetails1 empCtDet1 = ctx.EmployeeContactDetails1.Find(employeeId);
then
EmployeeContactDetails2 empCtDet2 = ctx.EmployeeContactDetails2.Find(employeeId);
2) Have a discriminator property such as "unlocked" on Employee class and when manager select the employee then mark it unlocked = true and update the column in database and the do something like this for further data retrieval
EmployeeContactDetails1 empCtDet1 = ctx.Employees.Where(e => e.unlocked == true).EmployeeContactDetails1;
EmployeeContactDetails2 empCtDet2 = ctx.Employees.Where(e => e.unlocked == true).EmployeeContactDetails2;
3) Or
EmployeeContactDetails1 empCtDet1 = ctx.EmployeeContactDetails1.Where(e => e.Employee.unlocked == true).FirstOrDefault();
EmployeeContactDetails2 empCtDet2 = ctx.EmployeeContactDetails2.Where(e => e.Employee.unlocked == true).FirstOrDefault();
I would like to ask you which one is better keeping in mind security and performance.
public class Manager{
public int ManagerId{get;set;}
public string ManagerName{get;set;}
public string someMoreProp{get;set;}
public ICollection<Employee>Employee{get;set;}
}
public class Employee{
public int EmployeeId{get;set;}
public virtual EmployeePersonalDetails EmployeePersonalDetails{get;set;}
public virtual EmployeeContactDetails EmployeeContactDetails{get;set;}
public int ManagerId{get;set;}
public virtual Manager Manager{get;set;}
}
public class EmployeePersonalDetails{
public int EmployeeId{get;set;}
public string Name{get;set;}
public string Age{get;set;}
public Address AddressOne{get;set;}
public Employee Employee{get;set;}
}
public class EmployeeContactDetails1{
public int EmployeeId{get;set;}
public string Line1{get;set;}
public string Line2{get;set;}
public Employee Employee{get;set;}
}
public class EmployeeContactDetails2{
public int EmployeeId{get;set;}
public string Line1{get;set;}
public string Line2{get;set;}
public Employee Employee{get;set;}
}
public class EmployeeContactDetails3{
public int EmployeeId{get;set;}
public string Line1{get;set;}
public string Line2{get;set;}
public Employee Employee{get;set;}
}
public class EmployeeContactDetails4{
public int EmployeeId{get;set;}
public string Line1{get;set;}
public string Line2{get;set;}
public Employee Employee{get;set;}
}

First, don't use Session. The mentioned way you're considering is a completely unacceptable use of sessions. A well-designed web app will as much as feasibly possible avoid sessions like the plague. The notable exception is of course authentication, which can't be achieved in a user-friendly way by any other means.
Second, in general, you seem to be making this far too complicated. It's not entirely clear what you're trying to achieve, but it sounds like you simply want the manager to be able to do something like click on an employee and then be taken to a view where they can edit that employee's details. The correct way to do that is to have a dedicated URI, consisting of the employee id or some other uniquely identifying token. For example:
/employees/{id}/edit
Then, in the action that that URI routes to, you would use the id param to look up the employee:
var employee = db.Employees.Find(id);
if (employee == null)
{
return new HttpNotFoundResult();
}
Since, presumably, you would only want the manager of that employee to be able to edit the employee, you would simply do object-level authorization by including the manager's id in the query:
var managerId = User.Identity.GetUserId(); // currently logged in manager
var employee = db.Employees.SingleOrDefault(m => m.EmployeeId == id && m.ManagerId == managerId);

Related

How to cache and get properties from extended Identity, AspUser in ASP Identity 2.2

To support tenan/companies, I added/extended a new property to the AspUser Table called OrgId in ASP MVC 5, Identity 2.2 role management, I added the corresponding OrgId to some other tables, looked here but no answers
During User Login() and the UserSession
how do I cache, configure & retrieve the OrgId, so that I can perform DBConext filtering/CRUD of table for Org specific records?
Advice: is better to save this in the Claims, FormsToken or Session - and
how to set the tenanId context in session?
I know how to get user, but not the extended Org
ApplicationUser user = System.Web.HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>().FindById(System.Web.HttpContext.Current.User.Identity.GetUserId());
Your customized user class should be like this:
public class CustomizedUser : IdentityUser
{
public int OrgId {get; set;}
public DateTime JoinDate { get; set; }
//...
// and other simple fields
//Fields related to other tables
public virtual ICollection<Article> Articles { get; set; } = new List<Article>();
//...
}
And your CustomizedApplicationDbContext class
public class CustomizedApplicationDbContext : IdentityDbContext<CustomizedUser>, IApplicationDbContext
{
public CustomizedApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static CustomizedApplicationDbContext Create()
{
return new CustomizedApplicationDbContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//your entity configurations go here if you have any
base.OnModelCreating(modelBuilder);
}
//These fields are neccessary in order to maintain the power of base class
public DbChangeTracker Changetracker => base.ChangeTracker;
public Database DatabBase => base.Database;
//your own dbsets go here except CustomizedUser, because the base class IdentityDbContext<CustomizedUser> handles it
public IDbSet<Article> Articles { get; set; }
//...
}
Now, Remember to replace every ApplicationDbContext references with CustomizedApplicationDbContext and every IdentityUser refrences with CustomizedUser in your code (specially in your ManageController created by mvc).
From now on, you can easily access users table just like other tables:
var db = new CustomizedApplicationDbContext();
var user = db.CustomizedUsers.First(x=> x.OrgId == 10 || Email == "sth#yahoo.com");
And to get the current user OrgId, you can use something like this(without querying db):
var currentUserOrgId = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>().FindById(User.Identity.GetUserId()).OrgId;
Hope this was helpful
You can get the current user in ASP.NET Identity as shown below:
ApplicationUser user = System.Web.HttpContext.Current.GetOwinContext()
.GetUserManager<ApplicationUserManager>()
.FindById(System.Web.HttpContext.Current.User.Identity.GetUserId());
//If you use int instead of string for primary key, use this:
ApplicationUser user = System.Web.HttpContext.Current.GetOwinContext()
.GetUserManager<ApplicationUserManager>()
.FindById(Convert.ToInt32(System.Web.HttpContext.Current.User.Identity.GetUserId()));
For getting custom properties from AspNetUsers table:
ApplicationUser user = UserManager.FindByName(userName);
string name = user.Name;
Hope this helps...

Editing some properties of View Model in ASP.NET MVC

I'm using Entity Framework Database First approach. Let's say I have a model class called Product and that class has a NumberOfViews property. In the Edit page I pass an instance of the product class to the controller.
The problem is I can't add #Html.EditorFor(model => model.NumberOfViews) in the Edit page, because it's supposed that NumberOfViews is updated with every visit to the product page, and NOT by the website Admin.
And I can't add it as #Html.HiddenFor(model => model.NumberOfViews), because if the Admin Inspected the element, he can edit it manually.
Also If I try to programmatically set the value on the server-side (e.g., Product.NumberOfViews = db.Products.Find(Product.Id).NumberOfViews;), I get the following error:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
And if I don't add it to either the view or the controller, the value will be null, thus overriding any previous value.
So what should I do?
I have noticed a lot of people use the same model for their Entity Framework as they do for their MVC Controller. I generally discourage this practice. In my opinion, a database model is not the same as a view model.
Sometimes a view needs less information than what the database model is supplying. For example while modifying account password, view does not need first name, last name, or email address even though they may all reside in the same table.
Sometimes it needs information from more than one database table. For example if a user can store unlimited number of telephone numbers for their profile, then user information will be in user table and then contact information with be in contact table. However when modifying user profile, they may want to add/edit/delete one or more of their numbers, so the view needs all of the numbers along with first name, last name and email address.
This is what I would do in your case:
// This is your Entity Framework Model class
[Table("Product")]
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ProductId { get; set; }
public string Name { get; set; }
public int NumberOfPageViews { get; set; }
}
// This is the model you will use in your Edit action.
public class EditProductViewModel
{
public int ProductId { get; set; }
public string Name { get; set; }
}
public class ProductController : Controller
{
IProductService service;
//...
[HttpGet]
public ActionResult Edit(int productId)
{
var product = service.GetProduct(productId);
var model = new EditProductViewModel()
{
ProductId = product.ProductId,
Name = product.Name
};
return View(model);
}
[HttpPost]
public ActionResult Edit(EditProductViewModel model)
{
if (ModelState.IsValid)
{
var product = service.GetProduct(model.ProductId);
product.Name = model.Name;
service.Update(product);
}
// ...
}
}

How to return value with E.F. IDbSet<T>

I have ASP.NET MVC 5 web project with E.F. 6.1.3.
I use IDbSet and its method Add to insert new data in my database. I also use context to save changes.
protected IDbSet<T> DbSet { get; set; }
public DbContext Context { get; set; }
private void Insert(T item)
{
this.DbSet.Add(item);
Context.SaveChanges();
}
When i insert new item in database
Is there any equivalent way in this interface to Sql Command.ExecuteScalar ?
In other words i need to get the Id of newly inserted item (my Id is first column and first row in current table).
You dont need ExecuteScalar (but you have to create your POCOs thru context.Set<T>().Create() method no, you dont have to, it works without proxy)
class MyPoco
{
[Key]
public int Id {get;set;}
}
...
var myPoco = context.Set<MyPoco>().Add(context.Set<MyPoco>().Create());
context.SaveChanges();
int newId = myPoco.Id;
However, if you have to have some direct store query, you can use context.ExecuteStoreQuery (its not on IDbSet, but on Context class)
var departments = context.ExecuteStoreQuery<string>
("select Name from Department where DepartmentID < #p0", 5);
EDIT (after you added code :) ):
You can add custom interface (or even base class) to your POCO with Id property:
public interface IId
{
int Id {get;}
}
class MyPoco
: IId
{
[Key]
public int Id {get;set;}
}
and update your Insert code like this:
private int Insert(T item)
where T : IId
{
this.DbSet.Add(item);
Context.SaveChanges();
return item.Id;
}
Note this doesnt work when you create your POCO simply by poco = new Poco() - this way, you give up lot of EF functionality (proxies), you have to use IDbSet<T>.Create() method) It work.
Or keep your item and take Id value after you send it to your Insert(item):
var myPoco = context.Set<MyPoco>().Add(context.Set<MyPoco>().Create());
context.Insert(myPoco);
int newId = myPoco.Id;

breezejs: querying on properties of inherited entity

Let's consider the following classes:
public abstract class BaseEntity
{
public virtual Guid Id { get; set; }
}
public abstract class Resource : BaseEntity
{
public virtual EResourceType ResourceType { get; set; }
}
public class PhysicalFile : Resource
{
public virtual string FileName { get; set; }
}
public class Url : Resource
{
}
public class SourceMaterial : BaseEntity
{
public virtual Guid? ResourceId { get; set; }
public virtual Resource Resource { get; set; }
}
When I query for a SourceMaterial entity, the correct resource entity is created (a PhysicalFile entity or a Url entity, according to the ResourceType.
However if I do this:
var query = entityQuery.from('Requests')
var predicate = new breeze.Predicate('sourceMaterials', 'any', new breeze.Predicate('resource.fileName', 'eq', '.doc'));
(code is truncated for clarity)
then when I execute the query I get the error:
Error: unable to locate property: fileName on entityType: Resource:#CdT.EAI.Business.Entities undefined
That makes sense because the fileName property only exists on the PhysicalFile entity, but does this mean I cannot build such query ?
Breeze does not support this syntax, i.e. restricting a base class based on a subclasses criteria. ( Not sure how it could even be translated into a server query without some additional information).
Providing i'm understanding your schema correctly, I think that your best approach will be to use Breeze's 'withParameters' method in combination with another service endpoint where you handle the subtype manipulation. i.e. something like
[HttpGet]
public IQueryable<Request> RequestsForFilesEndingWith(string fileExtn) {
var requests = ContextProvider.Context.Requests
.Where(r => r.SourceMaterials.OfType<PhysicalFile>
.Any( pf => pf.FileName.EndsWith(fileExtn));
}
And your client query becomes:
var query = entityQuery.from('RequestsForFilesEndingWith')
.withParameters({ fileExtn: ".doc" });
Otherwise, the closest you will be able to get is to accomplish at least partial filtering by restricting on the 'resourceType' property ( which is on the base class) instead of the 'fileName' property
var query = entityQuery.from('Requests')
var predicate = new breeze.Predicate('sourceMaterials', 'any', new breeze.Predicate('sourceMaterial.resourceType', 'eq', ???fileNameResourceType???));

Web API Error: The 'ObjectContent`1' type failed to serialize the response body for content type

I am getting this error when attempting to use a Web API controller.
Web API Error: The 'ObjectContent`1' type failed to serialize the response body for content type
the code in my controller is as follows
public IEnumerable<Student> GetAllStudents()
{
var allstudents = unitOfWork.StudentRepository.Get(includeProperties: "Groups");
return allstudents;
}
public Student GetStudentByID(Guid id)
{
return unitOfWork.StudentRepository.GetByID(id);
}
and my 'Student' class is as follows
public partial class Student
{
public Student()
{
this.Groups = new HashSet<Group>();
}
public System.Guid StudentID { get; set; }
public string Surname { get; set; }
public string FirstName { get; set; }
public byte[] Timestamp { get; set; }
public virtual Course Course { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
Both methods result in the same error.
My inner exception is as follows
Type
'System.Data.Entity.DynamicProxies.Student_4C97D068E1AD0BA62C3C6E441601FFB7418AD2D635F7F1C14B64F4B2BE32DF9A'
with data contract name
'Student_4C97D068E1AD0BA62C3C6E441601FFB7418AD2D635F7F1C14B64F4B2BE32DF9A:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies'
is not expected. Consider using a DataContractResolver or add any
types not known statically to the list of known types - for example,
by using the KnownTypeAttribute attribute or by adding them to the
list of known types passed to DataContractSerializer.
I have a feeling I need to use the KnownType attribute but I'm not exactly sure how to implement it.
Any help would be appreciated
If you don't need the lazy-loaded navigation properties provided by the proxy class (System.Data.Entity.DynamicProxies.Student_4C97D068E1A...), you can disable their generation by setting:
unitOfWork.Configuration.ProxyCreationEnabled = false;
What to do if you need the proxy class is another question.
Follow these links for a good overview of lazy loading and proxies:
Loading Related Entities
Working with Proxies
Should I enable or disable dynamic proxies
I usually disable lazy loading and proxies by default, and enable one or both in specific code blocks that need them.
What is the inner exception message? The inner exception message will be the actual exception that is thrown by the serializer and it should tell us which type is causing the exception.
Let me guess -- Is it any the type Course and the type Group? If so, try putting KnownType attribute on the actual implementation type of your class Student
[KnownType(typeof(GroupA))]
[KnownType(typeof(CourseA))]
public partial class Student
{...}
public class GroupA : Group {...}
public class CourseA : Course {...}
public interface Group {...}
public interface Course {...}

Resources