I have a class like the following:
public sealed class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; private set; }
[Required] public string GroupId { get; set; }
[Required] public bool IsAdmin { get; set; }
}
And another:
public sealed class Group
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; private set; }
[ForeignKey("GroupId")]
public List<User> Users { get; set; }
}
The IsAdmin property of the User class signifies whether or not the user is an admin. What I want to do is add a new property to Group:
public List<User> Admins { get; set; }
This new list will contain all users that are admins of their group, meaning that their property IsAdmin has value true. I have considered using a custom getter for this property, like so:
public List<User> Admins
{
get
{
return this.Users.Where(user => user.IsAdmin);
}
}
However, I would like to know if Entity Framework can take care of this for me. In my head I can imagine it using IsAdmin in a way similar to how GroupId is used for the Users list, where every user that has User.GroupId = "foo" is included in the Users list of the group with Group.Id = "foo".
So my question is, how do I tell EF to use the IsAdmin property of users as a foreign key to populate Admins?
So firstly, using the term "Foreign Key" in this context is wrong. IsAdmin is not a foreign key, at best it is a discriminator.
Secondly, you can use the [NotMapped] attribute like this
[NotMapped]
public List<User> Admins
{
get
{
return this.Users.Where(user => user.IsAdmin);
}
}
so that EF ignores that property and doesn't try to create any relationships with it, That way you will get the values you want lazyloaded when you access Admins.
Finally, I think you have your data structure all wrong. Unless a User could only ever be a member of one group OR being an admin in one group made them admins across all groups that they were a member of, then your structure would make sense from a domain perspective but still be wrong from a data perspective.
What I advise that you do is view the Admin <-> User relationship as Many to many and introduce an intersect object GroupAdmins which would have the Id of the group and the Id of the user. You can have that intersect table created automatically by EF giving you a simpler domain model or you could do it manually, See article for the former here.
Re-reading, your question, the above doesn't apply, however, I'll leave it here in case someone with a similar situation happens upon this answer.
Related
I am working on a basic group chat system, for which I created these classes:
public class Role
{
public Guid Id { get; set; };
public string Username { get; set; }
}
public class Message
{
public Guid Id { get; set; };
public Role Author { get; set; }
public Conversation Conversation { get; set; }
public DateTime Date { get; set; }
public string Text { get; set; }
}
public class Conversation
{
public Guid Id { get; set; };
public IList<ConversationParticipant> ConversationParticipants { get; set; };
public IList<Message> Messages { get; set; };
}
public class ConversationParticipant
{
public Conversation Conversation { get; set; }
public Role Role { get; set; }
}
We are using EF Core 3.1 Code-First with migrations.
I am looking for a way to make Message.Author a required property, which should lead to a column in table Message that is created as AuthorId NOT NULL.
I tried:
public static void Map(this EntityTypeBuilder<Message> builder)
{
builder.HasOne(m => m.Author);
}
As this is applied using Add-Migration and Update-Database, the database column AuthorId is created, but with NULLs allowed.
There does not seem to be a method IsRequired() that I can add after HasOne().
I also tried:
public static void Map(this EntityTypeBuilder<Message> builder)
{
builder.Property(m => m.Author).IsRequired();
}
but that fails saying
The property 'Message.Author' is of type 'Role' which is not supported by current database provider. Either change the property CLR type or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Doing .HasOne(...) followed by .Property(...).IsRequired() also does not work:
'Author' cannot be used as a property on entity type 'Message' because it is configured as a navigation.
I managed to make Message.Conversation required through this:
public static void Map(this EntityTypeBuilder<Conversation> builder)
{
builder.HasMany(c => c.Messages) // A conversation can have many messages
.WithOne(e => e.Conversation) // Each message belongs to at most 1 conversation
.IsRequired(); // A message always has a conversation
}
However I'd rather not make Role aware of Messages, as I will never want to retrieve Messages directly from a Role (this will happen through Conversations and Participants).
My ultimate question is: Is there a way to make Message.Author required (NOT NULL), without linking Message and Role together in a full 1-to-many relationship with a Messages property in Role?
What about adding Role's foreign key to Message and then requiring that property to not be null? Something like:
// MessageConfiguration.cs
builder.Property(b => b.RoleId).IsRequired()
While the answer by #Ben Sampica was helpful and got me where I needed to be, the comments by #Ivan Stoev provided details and clarity that made me think that a more comprehensive answer would be useful.
There are multiple ways to make a foreign key column required (NOT NULL) in the generated table.
The simplest is to put [Required] on the navigation property:
public class Message
{
// ...
[Required] public Role Author { get; set; }
// ...
}
This will cause EF to create a shadow property AuthorId of type Guid because Message.Author is a Role and Role.Id is of type Guid. This leads to UNIQUEIDENTIFIER NOT NULL in case of SQL Server.
If you omit [Required] then EF will use Guid?, which leads to UNIQUEIDENTIFIER NULL, unless you apply one of the other options.
You can use an explicit Id property with a type that can't be null:
public class Message
{
// ...
public Guid AuthorId { get; set; }
public Role Author { get; set; }
// ...
}
Note (i) - This only works if you follow EF Core shadow property naming rules, which in this case means you must name the Id property nameof(Author) + nameof(Role.Id) == AuthorId.
Note (ii) - This will break if one day you decide to rename Author or Role.Id but forget to rename AuthorId accordingly.
If you can't or don't want to change the Model class, then you can tell EF Core that it needs to treat the shadow property as required:
builder.Property("AuthorId").IsRequired();
The same Notes apply as listed at 2, with the addition that you could now use nameof() to reduce the effort and the risks.
In the end I decided to use the [Required] approach, because
It is simple and descriptive,
No effort needed to think of which shadow property name to use,
No risk of breaking the shadow property name later on.
This may apply sometimes, not always:
Input forms may use the Model class attribute to check if a property is required. However it may be a better approach to build your forms around DTO classes, and then an attribute on an EF Model class may provide no worth for your forms.
I'm new to entity framework and even if i know how to do it in Merise, i can't do it using code first.
In an entity User, i should have a foreign Key 'Promotion_Id'
In an entity Promotion, i should have a foreign key 'Pilote_Id' that points out to the User entity.
Here is the thing : i also have a List in Promotion which is a list of all users in a promotion. Pilote_Id is the Id of the pilote of that formation, who's, of course, a user.
I tried the following :
public class User : EntityWithId
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string Phone { get; set; }
public virtual Promotion Promotion { get; set; }
}
public class Promotion : EntityWithNameAndId
{
//Site is another entity, the place where the promotion is
public virtual Site Site { get; set; }
public List<User> Users { get; set; }
public virtual User Pilote { get; set; }
}
(Note : EntityWithId only contains an Id and EntityWithNameAndId inherits from EntityWithId and only contains a name)
But it only results in having 2 foreign keys in User named Promotion_Id and Promotion_Id1.
I already maked the whole thing work by changing
public virtual User Pilote { get; set; }
with
public virtual Guid PiloteId { get; set; }
But i want some consistency in my entities so.. Is there a correct way to achieve this ?
You will probably need to use explicit mapping to achieve this:
In the OnModelCreating() for your context:
modelBuilder.Entity<User>()
.HasOptional(u => u.Promotion)
.WithRequired(p => p.Pilote)
.Map(u => u.MapKey("PiloteId"); // EF6
// .HasForeignKey("PilotId") // EF Core
This assumes that a user may, or may not have a Promotion, but all promotions have a Pilot.
The Promotion.Users will probably map ok by convention using a UserId on the promotion table, but if there is any issue there:
However, there is a big caveat with this approach which relates to the schema, not EF. There is no restriction/guard that will ensure that the Pilot is one of the Users associated with the promotion. A PiloteId could point to any user, and that user's promotionId may be different.
In any case, the logic around managing who is the pilot will need to be done by code, but this means either checking IDs for valid combinations, or something like:
If a User can only be associated to 1 Promotion, and one user on that promotion can be the Pilot, then you could consider adding a flag to User called "IsPilot".
Then in Promotion:
public virtual ICollection<User> Users { get; set; } = new List<User>();
[NotMapped]
public User Pilote
{
get { return Users.SingleOrDefault(u => u.IsPilote); }
set
{
var newPilote = Users.Single(u => u.UserId == value.UserId); // Ensure the user nominated for Pilote is associated with this Promotion.
var existingPilote = Pilote;
if (existingPilote != null)
existingPilote.IsPilote = false;
newPilote.IsPilote = true;
}
}
If users can be assigned to multiple promotions then you will want to update the schema and mappings to support a many-to-many relationship between user and promotions, such as a UserPromotions table containing UserId and PromotionId. In this case I would consider assigning the IsPilote in this table / linking entity, but again this would need logic to ensure that rules around 1 pilot per promotion, and whether a user can be pilot for more than one promotion.
I have fields that the customer can add or remove in a list on their application and need to therefor add them to the database / context for later use.
For example I have a model first entity context dbContext and the model in question is Customers with properties id, name, accesslevel, ...
What i want to know is if there is a way for me to programmatically add a property to the Entity Model / or context or if I need to take an entirely different approach to dynamic database fields?
Ex: Needing to add a Inactive boolean field to it's Model / and Database table.
No, that's definitely not possible. EF doesn't support it and even if you could, adding new properties to your models would require a database migration every time. If you want the user to be able to add custom data to the Cutomers class, consider something like this:
public class Customers
{
public int Id { get; set; }
public string Name { get; set; }
//... more properties.
public virtual ICollection<UserData> UserData { get; set; }
}
public class UserData
{
public string PropertyName { get; set; }
public string PropertyValue { get; set; }
}
Otherwise, the only way you could do something like this is with a NoSQL database like MongoDB.
I have two entities, Customer and User as follows:
public class User
{
[Key]
public int Id { get; set; }
[StringLength(20)]
public string CustomerId { get; set; }
public string Password { get; set; }
public bool Locked { get; set; }
[ForeignKey("CustomerId")]
public virtual Customer Customer { get; set; }
}
public class Customer
{
[Key]
[Column("Id", TypeName = "nvarchar")]
[StringLength(20)]
public string Id { get; set; } // nvarchar(20)
[Required]
public string GivenName { get; set; } // nvarchar(100)
[Required]
public string Surname { get; set; } // nvarchar(100)
public virtual ICollection<User> Users { get; set; }
}
I have a simple strong typed view for editing a customer, and I want to add to the view a check-box with following logic - the check-box should be selected, when there is at least one user for that customer and the Locked property of the first user is set to false. I just can't find a way to accomplish this. What's the proper way to do this in MVC? And how the processing method (the [HttpPost]Edit) receives the value of this check-box, currently it simply gets the Customer object? Should I create an additional model for this view? Or there is another way?
Anticipating this question I should say that I'm taking care that there wont be more than one user for a customer.
Updates:
I've added a view model for customer and updated the edit view and the controller to work with this model:
public class CustomerViewModel
{
public Model.Data.Customer BaseCustomer { get; set; }
public bool HasActiveUser { get; set; }
}
My edits saving method looks now like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(ViewModel.Data.Customer customer)
{
if (ModelState.IsValid)
{
//db.Entry(customer.BaseCustomer).Collection("Users").Load();
db.Entry(customer.BaseCustomer).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CustomerTypeId = new SelectList(db.CustomerTypes, "Id", "Name", customer.BaseCustomer.CustomerTypeId);
return View(customer);
}
The only question remains is how do I access the Users navigation property which is null, I've tried to reload it but got an InvalidOperationException with error that reads Member 'Load' cannot be called for property 'Users' because the entity of type 'Customer' does not exist in the context. To add an entity to the context call the Add or Attach method of DbSet<Customer>. I've also tried to get the Customer again with Customer baseCustomer = db.Customers.Find(customer.Id); but then I can't set db.Entry(customer.BaseCustomer).State = EntityState.Modified; since it tells me that An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. Any ideas, please?
I decided to go with creating a dedicated view model that will contain the domain model object(as suggested as second pattern ASP.NET MVC View Model Patterns by Steve Michelotti) and an additional property for binding to my check-box. Then in the controller I handle all the logic regarding when to show the check-box selected, and when to create a new user if one not exists. I've encountered several problems so I want to post my solutions, maybe they are far from best-practices but I think they might be of use to beginners, and I definitely would like to see comments or other solutions.
Objects aren't persisted if they don't participate in view since EF recreates them on post-back from the data received from the view. This prevented me from adding a User property that will be an accessor for the first User in navigation collection Users property of Customer (when I added it, it couldn't access Users since this property is null after post-back, as I understood this is because of the recreated Customer is detached from context).
In order to be able to use navigation properties I had to attach the recreated(by EF, as explained earlier) Customer object to the context by setting db.Entry(customer.BaseCustomer).State = EntityState.Modified;(thanks to Using DbContext in EF 4.1 Part 4: Add/Attach and Entity States ยง Attaching an existing but modified entity to the context) and to reload the collection by calling db.Entry(customer.BaseCustomer).Collection("Users").Load();
I am trying to model a sort of "multiple inheritence" relationship with EF 4.1 Code First. Here is an example what I am trying to do.
Let's say I am attempting to model the way a user interacts with my application using a "User" object. This, being the base class, is used to describe the current user when they aren't doing anything in particular (such as visiting the homepage). It may look like this:
public class User
{
public Guid ID { get; set; } // Just use the forms authentication user ID
public string FirstName { get; set; }
public string LastName { get; set; }
}
Now, if I want to create a representation of that same user but in a different portion of the site, say, as a shopper, it may look like this:
public class Shopper : User
{
public virtual ICollection<Orders> Orders { get; set; }
}
And so on, and so forth. When I go to insert a Shopper that has a pre-existing User entry, it throws an exception because the PK is already taken in the User table.
Is there any way to model this (IsA) relationship with EF Code First? Or am I going to be stuck with something like this?
public class Shopper
{
public Guid UserID { get; set; }
public virtual User User { get; set; }
public virtual ICollection<Order> Orders { get; set; }
public string FirstName
{
get { return User.FirstName; }
set { User.FirstName = value; }
}
// yada, yada, yada...
}
I would like to stick with Code First and model the relationships right in my DbContext, but I can't figure out quite how to do something like this. Thanks!
EDIT:
So, I am trying to do something like this:
public void SomeMethod ()
{
var UserID = Guid.NewGuid ();
var MyUser = new User () { ID = UserID };
SaveUserToDatabase (MyUser);
var ShopperRepresentation = GetUserAsShopper (UserID);
// Do stuff.
}
Basically like using object-oriented roles, I guess. I want to use the same PK for every represenation of that user, but store all of their basic information in a base class called User. I know this is possible if I write my own SQL, of course, but I want to see if EF Code First can do it, too.
Yes, you can do it the way you describe in your first two code examples.
I think you just need to define a mapping, which you'll want to do in your OnModelCreating function of your DataContext in addition to having your classes set up right. How you do it depends on what mapping scheme you're using. I went for Table-Per-Type (TPT) in my most recent project, so I had something like this:
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<Shopper>().ToTable("Shoppers");
modelBuilder.Entity<OtherUser>().ToTable("OtherUsers");
Let me know if that doesn't work for you and I'll see what I can do.
Edit:
Having seen your clarification below, I can't think of a way to do that. You'd have to keep each objects stored separately (having EF treat a Shopper as just a Shopper, not a Shopper and a User), even though they share common data. That could lead to data mismatches (if, say, Shopper got its LastName updated but User didn't). I think you might be better off going with something like:
public class User
{
public virtual Guid ID { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual ShopperInfo { get; set; }
}
public class ShopperInfo
{
public virtual ICollection<Order> Orders { get; set; }
}
and then when you need to treat User as a Shopper, you just access the ShopperInfo (and if its not there, you create it). EF will be able to properly set that up for you no problem.
Though if you're going to have a lot of types of users, that might get cumbersome. Just a suggestion though - I think its a bit cleaner.