I want to create a relationship between Users and Notifications which I think is a one-to-many relationship.
I'm having trouble with this and not sure how I am going wrong. Here is the code for the two classes:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public virtual ICollection<Notification> Notifications { get; set; }
}
public class Notification
{
public int NotificationID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime Time { get; set; }
public string ApplicationUserID { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
I also have the following mapping on it:
modelBuilder.Entity<Notification>()
.HasRequired<ApplicationUser>(u => u.ApplicationUser)
.WithMany(u => u.Notifications)
.HasForeignKey(u => u.NotificationID);
I have came across different errors while trying to fix this such as:
Multiplicity is not valid
Multiplicity constraint violated. The role _ of the relationship _ has multiplicity 1 or 0..1
Edit:
The same exception (Multiplicity constraint violated. The role 'Notification_ApplicationUser_Target' of the relationship Notification_ApplicationUser' has multiplicity 1 or 0..1.) is thrown when I try to add notifications for all users as I have it done in this method:
public void AddNotification(Notification n)
{
var roleId = context.Roles.Where(m => m.Name == "User").Select(m => m.Id).SingleOrDefault();
var users = context.Users.Where(u => u.Roles.All(r => r.RoleId == roleId)).ToList();
try
{
foreach(var user in users)
{
user.Notifications.Add(n);
}
context.SaveChanges();
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
I think the issue is coming from the string type of the foreign key. By default strings are nullable at the database level, but a required foreign key cannot be null. Specifying HasRequired only enforces that the relationship is required, not that the foreign key property must be not null. I think if you simply add [Required] to your foreign key property, that will correct the issue:
[Required]
public string ApplicationUserID { get; set; }
Obviously, you will need to do a migration to apply this change to your database.
EDIT
Sorry, I read into the exception wrong. The source of your problem is actually from adding the same notification to each user, as in literally the same notification. EF does object tracking, so after the entity instance referenced by n has been added once, it is being tracked by EF. Then, when you try to add it to another user, it thinks you're trying to do a many-to-many, basically, where that single Notification record will belong to multiple users.
Off the top of my head, I'm not sure what the best method is to fix this confusion in EF, but I have a couple of potential ideas:
You can try to detach the entity after adding it:
user.Notifications.Add(n);
context.Entry(n).State = EntityState.Detached;
However, I'm not sure if that will allow it to be added at all. You'll have to test. If it doesn't create it, you can also try saving before detaching. I think that would work, but it's obviously pretty inefficient committing to the database each iteration.
The safest method would be to create a new instance for each, and simply map over the data from n:
var notification = new Notification
{
Title = n.Title,
Description = n.Description,
Time = n.Time
};
user.Notifications.Add(notification);
That will ensure that every notification you add is a totally separate tracked instance.
Here is the proper configuration of your relationship:
modelBuilder.Entity<Notification>()
.HasRequired(u => u.ApplicationUser)
.WithMany(u => u.Notifications)
.HasForeignKey(u => u.ApplicationUserID);
In HasForeignKey method you need to specify the FK of that relationship, not the PK of Notification entity.
Update
The problem is you have an one to one relationship in your DB and you are trying to configure an one to many in your model. If this last one is what you really wants, then I suggest you to use Migrations to change your DB schema, otherwise you can configure your relationship this way:
modelBuilder.Entity<Notification>()
.HasRequired(u => u.ApplicationUser)
.WithOptional(u => u.Notification)
And change the navigation property in ApplicationUser entity:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public virtual Notification Notification { get; set; }
}
Related
I have a problem, I have to create a model where we have two entities which CAN be linked together but can also exist as stand alone entities.
The model currently looks like this:
public class Authorisation
{
public int AuthorisationID { get; set; }
public virtual Change Change { get; set; }
}
public class Change
{
public int ChangeID{ get; set; }
public virtual Authorisation Authorisation { get; set; }
public int? AuthorisationID{ get; set; }
}
The reason for this is that we can have an authorization record independent of a change, and some changes require authorisation and some dont, so neither side of the relationship is required.
I can configure this with the fluent API like so:
modelBuilder.Entity<Authorisation>()
.HasOptional(t => t.Change)
.WithOptionalPrincipal(t => t.Authorisation);
And alls well, Except that the migration that it creates looks like this
CreateTable(
"dbo.Changes",
c => new
{
ChangeID = c.Int(nullable: false, identity: true),
AuthorisationID = c.Int(),
Authorisation_AuthorisationID = c.Int(),
})
.PrimaryKey(t => t.ChangeID)
.ForeignKey("dbo.Authorisations", t => t.Authorisation_AuthorisationID)
.Index(t => t.Authorisation_AuthorisationID);
EF is deciding that its going to add a new column (Authorisation_AuthorisationID) for me to use as the FK between the two entities, what I really want to be able to do is to use the change.AuthorisationID property as the FK onto the Authorisation, I cannot find a way to configure this at all (Please note that I need the FK to be in the model - for consistency with the rest of the app more than anything else).
To sum up I need to be able to create a relationship between two entities where both sides of the relationship are optional and if possible I want to be able to define the FK column myself.
Am I just approaching this wrong? Ive been staring at the same block of code for so long I could be missing something simple.
Looks like explicit foreign key property is not supported for one-to-one relationships - there is no HasForeignKey Fluent API and also if you put ForeignKey attribute on the navigation property you get exception saying that multiplicity must be *.
So the only choice you have is to remove the explicit Change.AuthorisationID property and work only with navigation properties:
Model:
public class Authorisation
{
public int AuthorisationID { get; set; }
public virtual Change Change { get; set; }
}
public class Change
{
public int ChangeID{ get; set; }
public virtual Authorisation Authorisation { get; set; }
}
Configuration:
modelBuilder.Entity<Authorisation>()
.HasOptional(t => t.Change)
.WithOptionalPrincipal(t => t.Authorisation)
.Map(a => a.MapKey("AuthorisationID"));
I have the exact problem described here:
Register custom UserProfile in ASP.NET MVC4 results in duplicate tables
But the solution doesn't work for me, because it's for MVC4 and I use the MVC5.
The problem is when I try to register a new user, duplicates almost all tables in database for a plural name, eg: Picture (duplicate to) Pictures
Edit:
I have an extended class from IdentityUser, named ApplicationUser, to put in this model additional attributes, like the id of postal code, photo and name:
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public string Name { get; set; }
public string Photo { get; set; }
public int PostalCodeID { get; set; }
[ForeignKey("PostalCodeID")]
public virtual PostalCode PostalCode { get; set; }
public virtual ICollection<Auction> Auctions { get; set; }
public virtual ICollection<Bid> Bids { get; set; }
public virtual ICollection<Message> Messages { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
public virtual ICollection<BlockHistory> Blocks { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> {
public DbSet<PostalCode> PostalCode { get; set; }
public ApplicationDbContext()
// ConnectionString
: base("AuctionsContext", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
}
The PostalCodeID is a foreign key for the table/model "PostalCode". One user can have one postalcode, but one postalcode could have zero or several users.
Here is the model "PostalCode":
public class PostalCode {
public int ID { get; set; }
[RegularExpression(#"\d{4}-\d{3}", ErrorMessageResourceType=typeof(Resources), ErrorMessageResourceName="PostalCodeFormat")]
[Required(ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = "Required")]
[StringLength(8, ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = "Length")]
[Display(Name = "Name_PostalCode", ResourceType = typeof(Resources))]
public string ZipCode { get; set; }
public int LocalityID { get; set; }
public virtual Locality Locality { get; set; }
}
I have the migrations enabled, but not in automatic mode,I think this is happening because in the configuration.cs, i have this code
public Configuration() {
AutomaticMigrationsEnabled = false;
}
When I create the first migration, and updated the database, all tables were correctly created (with their names in singular), except the AspNetxxxx tables, which are created automatically when the first user registration occurs, and that is when most tables are duplicated, and the weblogger gives this error:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.AspNetUsers_dbo.PostalCodes_PostalCodeID". The conflict occurred in database "PSIProject", table "dbo.PostalCodes", column 'ID'.
The statement has been terminated.
System.Data.SqlClient.SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.AspNetUsers_dbo.PostalCodes_PostalCodeID". The conflict occurred in database "PSIProject", table "dbo.PostalCodes", column 'ID'.
The statement has been terminated.
Linha 164: PostalCodeID = model.PostalCodeID
Linha 165: };
Linha 166: var result = await UserManager.CreateAsync(user, model.Password);
Linha 167: if (result.Succeeded)
Linha 168: {
I understand what is happening which leads to this error, in some way, the IdentityUser can't find the PostalCode table, so, it creates the PostalCodes table,
and it's on this table where it's created the relationship between the PostalCodes and AspNetUsers (I've checked, and there is no relation between tables AspNetUsers and PostalCode)
PS:
I've tried to put [Table("PostalCode")] before public class PostalCode, but when registering a user I got an error saying dbo.AspNetUsers is an invalid object so I verified the database and noticed that it wasn't creating those automatic AspNetxxxx tables.
Your question is quite difficult to understand, but I'm going to take a wild stab in the dark. If I'm completely off-based, feel free to let me know and provide additional information.
The default Entity Framework convention is to create table names as the pluralized versions of the entities they represent, so for a class like Picture, you would get a table in your database named Pictures, with an s. If you're getting "duplicates", I can only surmised that you're using an existing database or otherwise created the schema for the database manually instead of letting Entity Framework do it. In this manually created schema, you apparently named your tables in a singular fashion. I'm also assuming that you've got automatic migrations running or else you'd just get an error that the database is out of sync instead of duplicate anything. With automatic migrations, if Entity Framework determines that the database doesn't contain the schema it needs, it will update it accordingly automatically, which in your scenario would result in "duplicate" tables named in the convention that Entity Framework looks for.
You have two choices here. The best is to simply let the conventions be the conventions. Don't create your own schema or name your tables in your schema according to Entity Framework conventions (pluralized). With EF6 you can actually alter the conventions if you really care that much.
Option two is to prefix every entity class with the Table attribute to explicitly tell Entity Framework what table name it should look for, e.g.:
[Table("Picture")]
public class Picture
{
...
}
However, that's quite cumbersome and prone to error over the long haul of your application.
I have these two models:
public partial class Country
{
public Country()
{
this.Dinners = new HashSet<Dinner>();
}
public int CountryID { get; set; }
public string Name { get; set; }
public virtual ICollection<Dinner> Dinners { get; set; }
}
and
public partial class Dinner
{
public Dinner()
{
this.TRsvps = new HashSet<TRsvp>();
}
public int DinnerID { get; set; }
public System.DateTime EventDate { get; set; }
public string Description { get; set; }
public string HostedBy { get; set; }
public Nullable<int> CountryID { get; set; }
public virtual Country Country { get; set; }
}
I got a bit confused on how Entity Framework will act when a user tries to delete a parent entity (in our case it is the Country entity) that has child records (Dinners).
For example if I have the following code inside my mvc action method:-
public ActionResult DeleteConfirmed(int id)
{
Country country = db.Countries.Find(id);
db.Countries.Remove(country);
db.SaveChanges();
return RedirectToAction("Index");
}
And exception will be raised if I try to remove a country which has dinners, which sounds valid.
I tried modifying my code as follow, by including the Dinners when retrieving the Country object:
Country country = db.Countries.Include(a => a.Dinners).Single(a2 => a2.CountryId = id);
db.Countries.Remove(country);
db.SaveChanges();
return RedirectToAction("Index");
No exception will be raised, so I thought that EF would have deleted the child dinners, but what happens is that it updates the countryID FK inside the Dinners table to be null.... (Cascade Set to Null)
I tried looping over the Dinners collection as follows:
public ActionResult DeleteConfirmed(int id)
{
Country country2 = db.Countries.Find(id) ;
foreach(var d in country2.Dinners)
{
db.Dinners.Remove(d);
}
db.Countries.Remove(country2);
db.SaveChanges();
return RedirectToAction("Index");
}
but this raised the following error:
An exception of type 'System.InvalidOperationException' occurred in
System.Core.dll but was not handled in user code
Additional information: Collection was modified; enumeration operation
may not execute.
I realized that I should explicitly call the .Tolist() on the foreach to get the parent and all its children deleted as follows:
foreach(var d in country2.Dinners.ToList())
Can anyone advice if I getting things wrong, or this is the only way to support cascade on delete using EF ?
Thanks
If you want your deletes to cascade automatically, in your OnModelCreating method, you need to manually enable it:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Country>().WillCascadeOnDelete(true);
}
You are describing the documented behaviour for cascade delete when the foreign key is nullable:
If a foreign key on the dependent entity is nullable, Code First does
not set cascade delete on the relationship, and when the principal is
deleted the foreign key will be set to null.
If you want it to cascade delete then the relationship is required and the foreign key should not be nullable. Change public Nullable<int> CountryID { get; set; } to public int CountryID { get; set; }
Reference:
http://msdn.microsoft.com/en-us/data/jj591620.aspx#CascadeDelete
Additional info following your comment
You don't have to .Include to get cascade delete to work on required relationships i.e. once you have made the foreign key non-nullable. I am sure of this because that is how my application works.
I think you are observing that Remove marks your entire object graph for removal - whether required or not - in the same way that Add marks the entire graph for insertion. NB - I am not 100% sure of this bit so you should test this before you rely on it.
Further reading here:
using Dbset.Add Versus using EntityState.Added
Why Does Entity Framework Reinsert Existing Objects into My Database?
What is the difference between IDbSet.Add and DbEntityEntry.State = EntityState.Added?
I'm trying to update a model, but get the error "The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted."
From what I understand from The relationship could not be changed because one or more of the foreign-key properties is non-nullable the problem might be with how Entity Framework handles my virtual ICollection
However I'm not really sure how to implement the solution when using scaffolded repository pattern. Do I have to edit the Save()-method ParentObjectRepository-class?
Actually I really think that there must be some way to make EF understand this. I can't see how the EF-team was thinking "Probably noone is using a collection of objects with a foreign key constraint, lets not support that".
Update
Added code
[HttpPost]
public ActionResult Edit(int id, FormCollection formCollection)
{
var eventRepository = new MagnetEventRepository();
var original = eventRepository.Find(id);
UpdateModel(original);
eventRepository.Save();
return RedirectToAction("Details", "Home", new { slug = original.Slug });
}
public void Save()
{
context.SaveChanges();
}
More code:
public class MagnetEvent
{
public virtual int Id { get; set; }
[Required]
public virtual string Name { get; set; }
[Required]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd HH:mm}")]
[DataType(DataType.DateTime)]
public virtual DateTime? StartDate { get; set; }
public virtual string Description { get; set; }
[StringLength(100)]
public virtual string Slug { get; set; }
public virtual int MaximumCapacity { get; set; }
[DataType(DataType.Currency)]
public virtual int TicketPrice { get; set; }
public virtual int LocationId { get; set; }
public virtual Location Location { get; set; }
public virtual Collection<Ticket> Tickets { get; set; }
public virtual Collection<AttendeeInformationField> CaptureAttendeeInformationFields { get; set; }
public virtual int CustomerId { get; set; }
[Required]
public virtual CUSTOMER Customer { get; set; }
}
The Save()-method is from MagnetEventRepository, which is scaffolded from the above class.
Another update
I successfully removed the error by changing MagnetEventId in AttendeeInformationField to nullable int. When examining the database I can see exactly what's wrong.
Let's say I have a single AttendeeInformationField with the value "E-mail". When I edit my MagnetEvent, the AttendeeInformationField updates the MagnetEventId to null and then adds a new post with the correct MagnetEventId and Value.
I'd very much prefer if the posts in AttendeeInformationField were updated instead.
can you add the code for your event object. The one you call original.
It might be so that the UpdateModel change some info on the associated objects and that's not good if so. Not sure about this though I can't see all the code.
I prefer to not uder UptadeModel and instead use a inputmodel or your MVC model as the inparameter and manually map the chages to the loaded original object.
Antoher problem is that I can't see if
eventRepository.Save();
really do an SaveShages? does it? I can se some context code in another method Save?
As the exception say it seams like your associated collections or other associated objects cant find a valid ID value.
Are you Eager-loading the associated objects? like Customer?
One thing of note is that you shouldn't have the [Required] on Customer as its inferred from the fact that your FK isn't nullable. Required should only be used on a navigation property if you do not have the FK in the model.
To try to diagnose the issue, can you load the object and look at it in a debugger, you should expect that both locationId and CustomerId have non-zero values.
I found a solution to my problem. It seems to be a bug (?) in ASP.NET MVC when it comes to UpdateModel and a model containing an ICollection.
The solution is to override the default behaviour, as described in this blog post: http://www.codetuning.net/blog/post/Binding-Model-Graphs-with-ASPNETMVC.aspx
Update
I found a solution! The above only worked when updating existing items in the collection. To solve this, I have to manually check and add new AttendeeInformationFields. Like this:
[HttpPost]
public ActionResult Edit(int id, MagnetEvent magnetEvent)
{
var eventRepository = new MagnetEventRepository();
var original = eventRepository.Find(id);
UpdateModel(original);
foreach (var attendeeInformationField in magnetEvent.CaptureAttendeeInformationFields)
{
var attendeeInformationFieldId = attendeeInformationField.Id;
if (original.CaptureAttendeeInformationFields.AsQueryable().Where(ai => ai.Id == attendeeInformationFieldId).Count() == 0)
{
original.CaptureAttendeeInformationFields.Add(attendeeInformationField);
}
}
eventRepository.Save();
}
Together with the modified DefaultModelBinder, this actually works with both editing and adding. For now I haven't tried deleting.
Still, I hope there is a simpler way to do this. Seems like a lot of coding to do a very basic task.
I have a couple of classes (for this example anyway) that use code first with the entity framework to connect to the database.
public class Customer
{
[Key]
public long CustomerId { get; set; }
public string CompanyName { get; set; }
...
public virtual List<Contact> Contacts { get; set; }
}
public class Contact
{
[Key]
public long ContactId { get; set; }
public string Forename { get; set; }
...
public long CustomerId { get; set; }
public virtual Customer Customer { get; set; }
}
When I hook these up in my context class directly to the db the foreign key relationships hook up fine and I can access the collection of contacts from within the customer class.
class RemoteServerContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Contact> Contacts { get; set; }
...
}
My problem is that these database tables are used by various different systems and are massive. In order to increase efficiency I have overridden the default behaviour to point at a view (and also a stored proc elsewhere) rather than directly at the table.
public IEnumerable<Customer> Customers ()
{
return Database.SqlQuery<Customer>("SELECT * FROM vw_CustomerList");
}
public IEnumerable<Contact> Contacts()
{
return Database.SqlQuery<Contact>("SELECT * FROM vw_ContactsList");
}
I have made sure that in each of the views I have included the foreign key fields: CustomerId and ContactId.
When I do this however the class joins appear to be lost - there's always a null when I drill into either of the objects where it should be pointing to the other one. I have tried to set up what the foreign key field should point to but this doesn't seem to help either.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>().HasRequired(p => p.Customer)
.WithMany()
.HasForeignKey(k => k.CustomerId);
}
Is there a way to establish the connection when overriding the default behaviour?
There is no overriding in this case. If you removed
public DbSet<Customer> Customers { get; set; }
and replaced it with
public IEnumerable<Customer> Customers ()
{
return Database.SqlQuery<Customer>("SELECT * FROM vw_CustomerList");
}
you have completely changed the behavior. The first uses entities and full power of EF. The second is only helper to execute custom SQL. Second without first or without defining entity in OnModelCreating doesn't use Customer as mapped entity at all - it uses it as any normal class (only mapped entities can use features like lazy loading).
Because your Customer is now mapped to view you cannot use your former Customer class used with table. You must define mapping of Customer to a view by cheating EF:
modelBuilder.Entity<Customer>().ToTable("vw_ContactsList"); // EF code fist has no view mapping
Once you have this you can try again using:
public DbSet<Customer> Customers { get; set; }
Unless your view is updatable you will get exception each time you try to add, update or delete any customer in this set. After mapping relation between Customer and Contact mapped to views your navigation properties should hopefully work.
The problem with SqlQuery is the way how it works. It returns detached entities. Detached entities are not connected to the context and they will not lazy load its navigation properties. You must manually attach each Customer instance back to context and to do that you again need DbSet.