I have a User table and an Avatar table. One User can have many avatars (or null). But I need to mark which avatar is the current, so I have an Avatar_Id in User table that is the current avatar. And a ForeignKey User_Id in Avatar to tell me which User is the owner.
Trying to do that is generating me a lot of errors and headaches when I try to populate some data in order to test the relationship.
public class User
{
[Key]
public int Id { get; set; }
public Avatar Avatar { get; set; }
public virtual ICollection<Avatar> Avatars { get; set; }
}
public class Avatar
{
[Key, ForeignKey("User")]
public int Id { get; set; }
public User User { get; set; }
}
Test part:
var user = new User();
var avatar = new Avatar()
{
User = user
};
// user.Avatar = avatar; // <- this gives [a circular] error; without this I have null.
db.Users.Add(user);
db.Avatars.Add(avatar);
db.SaveChanges();
This is resulting me with Avatar_Id = NULL within User table, and User_Id = NULL in Avatar table. I expected these fields filled (well, Avatar_Id can be null).
Its better to make boolean field 'IsDefault' in table with avatar and check while add/update avatars that no more default avatars for this user. Also you can add same property in avatar class.
#Fabricio I can't test this code before post, but I'm pretty convinced it will work.
public class User
{
[Key]
public int UserId { get; set; }
public int AvatarId { get; set; }
[ForeignKey("AvatarId")]
public Avatar Avatar { get; set; }
public ICollection<Avatar> Avatars { get; set; }
}
public class Avatar
{
[Key]
public int AvatarId { get; set; }
[ForeignKey("User")]
public int UserId { get; set; }
public User User { get; set; }
}
The problem is when you put two foreign keys merge like one. Now you have a foreign key in Avatar table and other in User table, each one represents one mode of relationship.
The foreign key "AvatarId" represents a special form of foreign key, a unique + foreign key, (a second form to build the one to one relationship). You can read more about this in here: http://weblogs.asp.net/manavi/archive/2011/05/01/associations-in-ef-4-1-code-first-part-5-one-to-one-foreign-key-associations.aspx
I've given this a bit of though because I modeled a similar case once and didn't mind to reevaluate the options.
Look closely at your premises:
One User can have many avatars (or null)
This short sentence implies that the one-to-one association User-Avatar must be optional both ways, because a User without Avatars can’t possibly refer to one its own avatars, and when a user has more than one avatar only one of them can refer to User as being the user's default. (They all refer to user as owner).
So you can only model it as a 0..1 – 0..1 association. So Avatar’s primary key can't be a foreign key to user. (It couldn't anyway, otherwise a user could only have one avatar).
Maybe this could have been done by Jonny Piazzi's model if this wouldn't throw the infamous "may cause cycles or multiple cascade paths" exception. Both user and Avatar refer to one another and you have to tell EF explicitly which of the FKs is not cascading. This can only be done by fluent mapping:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasOptional(u => u.Avatar)
.WithOptionalDependent()
.Map(m => m.MapKey("AvatarId"))
.WillCascadeOnDelete(false);
...
}
This puts a nullable, non-cascading FK column AvatarId in User (that's why User is the dependent of Avatar).
Now your second issue, the chicken-eg problem when populating the model.
This can only be done when you call SaveChanges twice and wrap these calls in a transaction scope. For example:
using (var tran = new TransactionScope())
{
var user = new User();
var avatar = new Avatar();
user.Avatars = new HashSet<Avatar>();
user.Avatars.Add(avatar);
user.Avatars.Add(new Avatar());
user.Avatars.Add(new Avatar());
db.Users.Add(user);
db.SaveChanges();
user.Avatar = avatar; // set FK
db.SaveChanges();
tran.Complete();
}
Now EF can decide which key to generate first (User's) before referring to it by foreign keys. Subsequently you set the FK in User.
But... is this the best model?
Maybe, maybe not.
The issue is that your model does not enforce the business rule that a user can only have one of its own avatars as default avatar. User.AvatarId can refer to any avatar. So you have to write business logic to enforce the business rule.
With YD1m's solution (no User.AvatarId, but a column Avatar.IsDefault) this business rule is enforced implicitly. But now you have to write business logic to enforce that only one avatar is the default.
It's up to you to decide what you think is more feasible.
(for the record: way back, I took the latter option)
Related
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);
}
// ...
}
}
I have problem with updating entites that have many-to many relationship. Below my User and category class:
public class User : IEntity
{
[Key]
public virtual long Id { get; set; }
private ICollection<Category> _availableCategories;
public virtual ICollection<Category> AvailableCategories
{
get { return _availableCategories ?? (_availableCategories = new List<Category>()); }
set { _availableCategories = value; }
}
}
public class Category : IEntity
{
[Key]
public long Id { get; set; }
/// <summary>
/// Full name or description of a category
/// </summary>
[StringLength(255)]
public string FullName { get; set; }
}
This is code snippet from my repository
public override void Edit(User user)
{
var dbUser = _context.Users.Include(x => x.AvailableCategories)
.Single(x => x.Id == user.Id);
var categories = _context.Categories;
dbUser.AvailableCategories.Clear();
foreach (var cat in user.AvailableCategories)
{
dbUser.AvailableCategories.Add(cat);
}
_context.Entry(dbUser).State = EntityState.Modified;
}
However the categories don't get updated. What EF does is insert empty rows into category table and sets relations to this new rows with user.
How can I update User so that I change only categories that already exist in the database?
User that I pass to Edit method has AvailableCategories with only Ids set (rest of properties are empty).
When you're doing something like posting back M2M relationships, you either must post the full object, as in every single property on those objects, or simply post a list of ids and then use those to query the associated objects back from the database. Otherwise, Entity Framework understands your purpose to be to update the properties on the objects as well, in this case with empty values.
Obviously the first option is quite unwieldy, so the second way is the preferred and standard way. Generally, for this, you'd want to use a view model so you could have a property like the following, that you would post into:
public List<long> SelectedCategories { get; set; }
But, if you insist on using the entity directly, you can get much the same result by simply doing:
var selectedCategories = user.AvailableCategories.Select(m => m.Id)
Once you have the ids:
var newAvailableCategories = _context.Categories.Where(m => selectedCategories.Contains(m.Id));
And then finally set that on your user:
dbUser.AvailableCategories = newAvailableCategories;
I notice you are also adding the user.AvailableCategories directly into dbUser.AvailableCategories. I've noticed when binding back complex objects from an MVC view that DB Entities are no longer attached to the DbContext. If you look at the entity, you can verify by checking dbContext.Entry(cat).State is "detached" (or something unexpected) I believe.
You must query those entities back out of the dbContext (possibly by using the returned cat.Id's). Or otherwise manually set the entities as "unchanged". And then add those "non-detached" items into dbUser.AvailableCategories. Please see Chris's answer as it shows with specific code how to get this done.
Also, I might use a linking entity. Possibly something like this:
public class UserCategory
{
public User User {get;set;}
public Category Category {get;set;}
}
And add it to DB context. Also, drop the linking lists in your current User and Category class. This way you can manipulate the UserCategory class (and DbSet) to manage your many-to-many relationship.
I've created my incredibly simplistic model:
public class ImageModel
{
public int Id { get; set; }
public string FileName { get; set; }
}
And now I want to store the logged-in user with the record. Presumably, I would do this by adding another property:
public User User { get; set; }
Which Visual Studio is telling me is telling me is in
using System.Web.Providers.Entities;
Assuming that's the right User class that corresponds with the presently authenticated user, how do I save that with my model?
My Create action looks like this:
[HttpPost]
public ActionResult Create(ImageModel imagemodel)
{
if (ModelState.IsValid)
{
db.ImageModels.Add(imagemodel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(imagemodel);
}
I imagine I would want to add something like
imagemodel.User = User;
Just above db.ImageModels.Add, but those appear to be two different types. The User object in Controller appears to be an IPrincipal which really doesn't seem to hold much information at all.
How do I get the rest of the user data for the authenticated user? What do I need to assign imagemodel.User to? Is that even the way to do it, or do I need to explicitly tell it I just want to save the User ID (I'm assuming this much it could figure out) -- and if so, how do I keep a User object on my model such that it points to the real User object (not just an ID), and how do I get the User ID for the currently logged in user?
It is dependent upon what technology you are using to manage logons or sessions.
Inside the controller method, you probably just want to set your model property to the value in 'User.Identity.Name', which is a string value.
That assumes the user is logged in and that you have forms authentication configured. You've probably previously authenticated the user, and given them a token (basically just an encrypted cookie) containing the value of '.Name', via the FormsAuthentication.SetAuthCookie method.
So, to keep things very simple, your Image model should probably just have a Username string property. If you have access to the user identity table, you might want to store a reference to the related user instead.
public class ImageModel
{
public int Id { get; set; }
public string FileName { get; set; }
public string Username { get; set; }
}
Controller...
[HttpPost]
public ActionResult Create(ImageModel imagemodel)
{
if (ModelState.IsValid && User.Identity.IsAuthenticated)
{
imagemodel.Username = User.Identity.Name;
db.ImageModels.Add(imagemodel);
db.SaveChanges();
return RedirectToAction("Index");
}
The interfaces exposed without knowing what provider you are using are very minimal.
IPrinicipal User
bool IsInRole(string role)
IIdentity Identity
string AuthenticationType
bool IsAuthenticated
string Name
That's it.
Once you select a provider or decide to implement a custom one there's a whole range of SO articles that will fall into your lap.
You may also be better served looking for ASP.NET references than MVC references when researching this topic.
I have a user class in EF Code First that contains a lot of properties, and each user has a collection of "Contacts" which are other users as a subset of the total user population. The other collection "ContactOfOthers" is just the reverse showing who has this user as a contact as this is a many-to-many relationship.
public class User {
[Key]
public string UserName { get; set; }
// Lots of other properties not relevant to this question...
[NotMapped]
public bool IsMyContact { get; set; }
public virtual ICollection<User> Contacts { get; set; }
public virtual ICollection<User> ContactOfOthers { get; set; }
}
I introduced a not-mapped (not mapped to DB) property called IsMyContact. This is for cases when the user queries for a bunch of users and I need to show in the View which users are already in their contacts list. So this property should be true if the User is part of their "Contacts" collection. It shouldn't be saved to the DB since it can be different for the same user, depending on the user doing the query.
Is there a nice way to do this in a query from the context? It could of course be brute-forced by doing two queries then iterating through the main one, looking for matches to the user's Contacts collection, but I'm wondering if there's a more elegant way to do this from one query, projecting a run-time computed column onto this property?
I don't know a way how to populate the IsMyContact property in the User directly within the query. But an alternative approach could be to introduce a ViewModel which wraps the User and has in addition the IsMyContact flag:
public class UserViewModel
{
public bool IsMyContact { get; set; }
public User User { get; set; }
}
(The class User would not have the IsMyContact flag anymore.)
You could then project into this type when you run your query:
string me = "That's me"; // name of the user who is selecting
List<UserViewModel> list = context.Users
.Where(u => ...some filter...)
.Select(u => new UserViewModel
{
IsMyContact = u.ContactOfOthers.Any(c => c.UserName == me),
User = u
})
.ToList();
The benefits would be: You need only one round trip and you are not forced to load the whole collection of Contacts to determine the IsMyContactFlag (but you can if you want to).
The drawback: You need this additional ViewModel type.
It is possible to do this but it will be far from a "nice way" because you cannot return instances of your User type. You must write custom linq-to-entities query and you must solve two problems:
You cannot project to mapped types in linq-to-entities
You cannot access non mapped properties in linq-to-entities
So my high level untested idea about doing this is:
var query = from u in ctx.Users
where u.Id != id // don't include current user - you can add other condition
join c in ctx.Users
.Where(x => x.Id == id) // current user
.SelectMany(x => x.Contacts)
on u.Id equals c.Id into leftJoin
from y in leftJoin.DefaultIfEmpty()
select new
{
UserName = u.UserName,
IsMyContact = y != null
};
This should be a query which will load pairs of UserName and information if the user is contact or not. If you want User instance instead you must do something like this:
var users = query.AsEnumerable()
.Select(new User
{
// Project to list in linq-to-objects
});
I am trying to figure out something with EF4 Code Only. If i use TPH and i wanted to change a saved Person to Instructor or vice versa, how would i accomplish this. My POCO classes:
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Instructor : Person
{
public DateTime? HireDate { get; set; }
}
public class Student : Person
{
public DateTime? EnrollmentDate { get; set; }
}
public class Admin : Person
{
public DateTime? AdminDate { get; set; }
}
public class PersonConfiguration : EntityConfiguration<Person>
{
public PersonConfiguration()
{
this.HasKey(u => u.PersonId).Property(u => u.PersonId).IsIdentity();
MapHierarchy()
.Case<Person>(p => new
{
p.PersonId,
p.FirstName,
p.LastName,
PersonCategory = 0
})
.Case<Instructor>(i => new
{
i.HireDate,
PersonCategory = 1
})
.Case<Student>(s => new
{
s.EnrollmentDate,
PersonCategory = 2
})
.Case<Admin>(a => new
{
a.AdminDate,
PersonCategory = 3
}).ToTable("Person");
}
}
Lets say i have a person:
var person1 = new Person { FirstName = "Bayram", LastName = "Celik" };
context.People.Add(person1);
context.SaveChanges();
Later on i want to make this person an admin. How would i accomplish this.
var person = context.People.FirstOrDefault();
Admin test = person as Admin; // wont work
following will change the HireDate column but my discriminator field PersonCategory is still 0. So it is still not an Admin type as far as EF concerns
Admin admin = new Admin();
admin.PersonId = person.PersonId;
admin.AdminDate = DateTime.Now;
context.ObjectContext.Detach(person);
context.People.Attach(admin);
var customerEntry = context.ObjectContext.ObjectStateManager.GetObjectStateEntry(admin);
customerEntry.SetModified();
customerEntry.SetModifiedProperty("AdminDate");
You can't ever change the type of an object. You can't do it in C#, and the EF doesn't work around this. This is a fundamental principle of OOP.
Instead, you need to fix your model design. As I wrote a while back:
One of the mental barriers that you have to get over when designing a good object relational mapping is the tendency to think primarily in object oriented terms, or relational terms, whichever suits your personality. A good object relational mapping, though, incorporates both a good object model and a good relational model. For example, let’s say you have a database with a table for People, and related tables for Employees and Customers. A single person might have a record in all three tables. Now, from a strictly relational point of view, you could construct a database VIEW for employees and another one for customers, both of which incorporate information from the People table. When using a one VIEW or the other, you can temporarily think of an individual person as "just" an Employee or "just" a Customer, even though you know that they are both. So someone coming from this worldview might be tempted to do an OO mapping where Employee and Customer are both (direct) subclasses of Person. But this doesn’t work with the data we have; since a single person has both employee and customer records (and since no Person instance can be of the concrete subtype Employee and Customer simultaneously), the OO relationship between Person and Employee needs to be composition rather than inheritance, and similarly for Person and Customer.