Bad Navigation Property/One-to-Zero or One Relationship - breeze

this is my User Model:
public User{
... (no navigation Property to modeltype)
}
The following model is inspired from DocCode OrderDetails->Product where only OrderDetail has a Foreign Key to the Product.
For this config i get an error message from breeze: "Bad nav properties" for Users SB and TL but not for MA.
public modeltype{
public DateTime? ClosedBySB { get; set; }
public long? SBId { get; set; }
[ForeignKey("SBId")]
public User SB { get; set; }
public DateTime? ClosedByTL { get; set; }
public long? TLId { get; set; }
[ForeignKey("TLId")]
public User TL { get; set; }
public DateTime? ClosedByMA { get; set; }
public long? MAId { get; set; }
[ForeignKey("MAId")]
public User MA { get; set; }
....
}
while this works:
public modeltype{
public DateTime? ClosedBySB { get; set; }
//public long? SBId { get; set; }
//[ForeignKey("SBId")]
//public User SB { get; set; }
public DateTime? ClosedByTL { get; set; }
//public long? TLId { get; set; }
//[ForeignKey("TLId")]
//public User TL { get; set; }
public DateTime? ClosedByMA { get; set; }
public long? MAId { get; set; }
[ForeignKey("MAId")]
public User MA { get; set; }
....
}
I think this should work?
No additional Fluent Api Configuration is made.
Thanks for any help.

I had same problem when model contained 2+ properties with same Type (1-to-1 relation). I have to deep in logic of breeze work with associations to solve this problem. It seems that breeze analyzes each of them and removes from temp array, if current association have both ends. Otherwise breeze shows 'bad nav properties' error. In case of several 1-to-1 properties with same type breeze removes one end of all this 1-to-1 relations, except first property, and show error. Try to change this strings in "addToIncompleteMap" function in breeze.js:
incompleteTypeMap[np.entityTypeName] = assocMap;
to
if (incompleteTypeMap[np.entityTypeName])
(incompleteTypeMap[np.entityTypeName])[np.associationName] = np;
else
incompleteTypeMap[np.entityTypeName] = assocMap;

This bug is fixed as of breeze v 1.0.0. and thanks go to Sergey for pointing out the fix.

I suspect an EF configuration problem.
You succeed when there is one navigation returning a related User entity but fail when you have three such navigation properties. You have no [InverseProperty] to help EF figure it out because you don't want navigation properties from User back to ModelType (and I can imagine why you don't want them).
I think you will have to use the Fluent API to tell EF what you mean.

I commented out the ForeignKey Attributes and put this in the Config file for the ModelType:
HasOptional(p => p.SB)
.WithMany()
.HasForeignKey(s => s.SBId)
.WillCascadeOnDelete(false);
HasOptional(p => p.TL)
.WithMany()
.HasForeignKey(s => s.TLId)
.WillCascadeOnDelete(false);
HasOptional(p => p.MA)
.WithMany()
.HasForeignKey(s => s.MAId)
.WillCascadeOnDelete(false);
But this produces the same error. SB and TL throw bad nav property exception. The navigation property must be nullable so i used HasOptional().
I do not know where to put any other config so breeze recognizes the entityType (undefined for SB/TL therefor the exception).

Related

Implementing Many-To-Many relationship in Asp.Net (confused?)

My current aim is to build a database structure using classes in Entity Framework & ASP MVC.
I currently have a Users table and a Posts table. What I would like to do is create a many to many relationship for Users who have liked Posts (whilst conserving who created the post). And be able to access for each user all of the posts they have liked. Currently I have these classes but I'm unsure of how to link them as all of the online examples are linking Primary Keys from different databases where I just want to use the Username Parameter. Any help would be great. I have this so far.
public class Posts
{
[Key]
public virtual int PostId { get; set; }
public virtual string Title { get; set; }
public virtual string URL { get; set; }
[DisplayName("Main Text")]
public virtual string TextBody { get; set; }
public int PostLikes { get; set; }
private DateTime Datedata = DateTime.Now;
public DateTime PostDate { get { return Datedata; } set { Datedata = value; } }
public virtual Users User { get; set; }
public ICollection<PostLikes> UsersWhoHaveSigned { get; set; }
}
{
public class Users
{
[Key]
public virtual int UserId { get; set; }
public virtual string Username { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual List<Posts> Post { get; set; }
}
}
I have not built the UsersWhoHaveSigned table yet. Early experimentation caused me so much backtracing it was painful. Any help would be great.
Edit: I was hoping to ask for help and then appropriate that informtaion to fit my example which utilises the individual accounts add-on. This produces some addition files that are now causing interference with the code you've provided.
Here is the IdentityModels.cs file.
using System.Data.Entity;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
namespace Coursework2.Models
{
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit https://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
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 class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
}
I believe that the assembly directives at the top are preventing system.data.entity from being used so when I try to implement ApplicationDbContext : DbContext I get error messages :/
Ideally I'm looking to use the IdentityModels.cs file as a replacement for the users class. But still very lost.
First of all, I recommend that you use the singular form for your class names, as EF will automatically pluralize table names.
Second, for a key property, you can just use the term Id, without any annotations, and EF will pick it up as the principal key.
Finally, I'll assume you are looking to use a Code-First approach. Consider the following classes (yours, but refactored for clarity purpose):
public class Post
{
public virtual Guid Id { get; set; }
public virtual string UserName { get; set; }
public virtual User User { get; set; }
public virtual ICollection<PostLike> Likes { get; set; }
}
public class PostLike
{
public virtual Guid Id { get; set; }
public virtual Guid PostId { get; set; }
public virtual Post Post { get; set; }
public virtual string UserName { get; set; }
public virtual User User { get; set; }
}
public class User
{
public virtual Guid Id { get; set; }
public virtual string UserName { get; set; }
public virtual ICollection<Post> Posts { get; set; }
public virtual ICollection<PostLike> Likes { get; set; }
}
To make it work, you'd need a DbContext such as the following. Pay attention to the OnModelCreating method, which is where the magic happens:
public class ApplicationDbContext
: DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Post> Posts { get; set; }
public DbSet<PostLike> PostLikes { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>()
.HasAlternateKey(u => u.UserName);
modelBuilder.Entity<User>()
.HasMany(u => u.Posts)
.WithOne(p => p.User);
modelBuilder.Entity<Post>()
.HasOne(p => p.User)
.WithMany(u => u.Posts)
.HasForeignKey(p => p.UserName)
.HasPrincipalKey(u => u.UserName);
modelBuilder.Entity<Post>()
.HasMany(p => p.Likes)
.WithOne(pl => pl.Post);
modelBuilder.Entity<PostLike>()
.HasOne(pl => pl.Post)
.WithMany(p => p.Likes);
modelBuilder.Entity<PostLike>()
.HasOne(pl => pl.User)
.WithMany(u => u.Likes)
.HasForeignKey(pl => pl.UserName)
.HasPrincipalKey(u => u.UserName);
}
}
Voila! I hope it answers your question ;)
If so, please don't forget to mark my post as the answer!
Edit:
I'll provide some explanations, that I had left out to answer your question ASAP.
So, first thing you need to do, is to declare the UserName as an alternate key, because you want to create relationships depending on it, and you already have the 'Id' principal key declared.
Second, on each object that should own a User reference base on the UserName alternate key, you need to declare the object's UserName property as the foreign key of the relationship, and the User's UserName property as the principal key.
In other words, the foreign key is the property that a referencing object uses for the relationship, and the principal key is the property based on which the referenced object is bound to the referencing one.
Note that principal keys must have a key or alternate key constraint, or it won't work.
Just to comment on your answer. I found that I had to use
using Microsoft.EntityFrameworkCore and remove System.Data.Entity - This was causing the program to be confused as to which DbContext I wanted to use. Thanks!

Entity framework one to many - empty virtual collection

I have this code first database
public partial class SystemWarning
{
[Key]
public long Id { get; set; }
/// <summary>
/// id of the admin that created the entry
/// </summary>
public string CreatedById { get; set; }
public virtual AspNetUser CreatedBy { get; set; }
public string AcknowledgedById { get; set; }
public virtual AspNetUser AcknowledgedBy { get; set; }
}
public partial class AspNetUser
{
public AspNetUser()
{
SystemWarnings = new HashSet<SystemWarning>();
}
[Key]
public string Id { get; set; }
public virtual ICollection<SystemWarning> SystemWarnings { get; set; }
}
And linked together as follows
modelBuilder.Entity<AspNetUser>()
.HasMany(e => e.SystemWarnings)
.WithOptional(e => e.CreatedBy)
.HasForeignKey(e => e.CreatedById).WillCascadeOnDelete(false);
For reasons that escape me at the moment, when I extract my AspNetUser, the SystemWarnings collection is always empty, even if there are systemwarnings that are linked to the AspNetUser in the database.
I have a bunch of these 1-n links, even on the same object, and the other links remain non empty, and for now I'm not seeing the difference.
#edit: here's that other object for comparison:
public partial class UserProfile: BaseObject
{
public Guid Id { get; set; }
public string OwnerId { get; set; }
public virtual AspNetUser Owner { get; set; }
}
and the mapping
modelBuilder.Entity<AspNetUser>()
.HasMany(e => e.OwnedUserProfiles)
.WithOptional(x => x.Owner)
.HasForeignKey(x => x.OwnerId);
Seems the same to me, except that the SystemWarning has a long as Id with identity specs (forgot to post that before - it's defined as follows)
modelBuilder.Entity<SystemWarning>().Property(e => e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
And that SystemWarning actually has another reference to the AspNetUser that I now added (AcknowledgedBy(Id)) which is linked as follows
modelBuilder.Entity<AspNetUser>()
.HasMany(e => e.SystemWarnings)
.WithOptional(e => e.AcknowledgedBy)
.HasForeignKey(e => e.AcknowledgedById).WillCascadeOnDelete(false);
I'll post the SQL trace soon...
Well, that edit did the trick.. both links from SystemWarning to AspNetUser are mapped on the SystemWarnings collection.. no wonder that's not working the way it's supposed to.
Note to myself.. map every 1:n to a different collection or there'll be trouble.

MVC Include path not valid error

Controller
public ActionResult Index(int? id)
{
var userEmail = User.Identity.Name;
var model = db.Staffs.Where(i => i.Email == userEmail).Include("Histories").Include("CurrentApplications").FirstOrDefault();
return View(model);
}
I got the following error for the line var model = db.Staffs.Where(i => i.Email == userEmail).Include("Histories").Include("CurrentApplications").FirstOrDefault(); but i don't know why I got it.
Error
A specified Include path is not valid. The EntityType
'StaffPortalDBModel.Staff' does not declare a navigation property with
the name 'Histories'.
Staff class
public partial class Staff
{
public int StaffID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public Nullable<decimal> AllocatedLeave { get; set; }
public Nullable<int> BalanceLeave { get; set; }
public virtual IEnumerable<History> Histories { get; set; }
public virtual IEnumerable<CurrentApplication> CurrentApplications { get; set; }
}
I believe you may need to use ICollection and not IEnumerable when defining Navigation properties. This may be the issue.
I'd also recommend (assuming your version of Entity Framework is high enough that it is supported) to use strongly typed Includes so that if you change your property in Staff.cs you'll get a compile time error.
ie: .Include(s => s.Histories)

Cascade Delete Entity within entity

I have been working on a project and I'm trying to get the cascade delete to kick in. I have a model below I use for comments. These comments can have replies that come off of them that call the comment class. What I'm trying to do is to make it delete all the replies that can flow off the comment.
Comment -> Reply -> Reply -> Reply -> so on.
If I'm going about this in the wrong direction, please let me know. I have tried to research into this but all I come up with is One-to-One and One-Many cascade codes. I'm using CodeFirst with MVC 4 to build my project.
Edited
public class Comment
{
// Properties
public long Id { get; set; }
[Required]
[StringLength(250, ErrorMessage = "{0} must be between {1} and {2} characters", MinimumLength = 2)]
public string Body { get; set; }
[Required]
public DateTime CreateDate { get; set; }
[Required]
[InverseProperty("Comments")]
public User Author { get; set; }
[InverseProperty("CommentCount")]
public Blog Blog { get; set; }
public bool Hidden { get; set; }
public long RepliesId { get; set; }
[InverseProperty("Replies")]
public virtual Comment Comments { get; set; }
[InverseProperty("Comments")]
public virtual ICollection<Comment> Replies { get; set; }
public virtual ICollection<Vote> Votes { get; set; }
public Comment()
{
CreateDate = DateTime.UtcNow;
Hidden = false;
}
}
Here is my DataContextInitializer
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Comment>().HasMany(i => i.Replies)
.WithOptional(i => i.Comments)
.HasForeignKey(i => i.RepliesId)
.WillCascadeOnDelete();
}
You could enable cascade delete with something like this (i.e. you need to manually set the relationship)...
modelBuilder.Entity<Comment>()
.HasOptional(x => x.Replies)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
However, it won't do you much good - as Update-Database will fail with something like...
A foreign key constraint that has an UPDATE or a DELETE CASCADE rule,
and self-references a column in the same table, is not allowed.
i.e. that works on FK-s that are connecting different tables - but not if self-referencing.
See this post with some more relevant info - especially the comment that mentions
"you need to drop the FK and manually create it with cascade delete in
your DatabaseInitializer"
EF4 Code first - delete all children when deleting parent from db?
In short, I don't think there is a straight-forward solution - but some manual setup (initializer etc.) is required (I haven't tried it). Or try to reorganize, flatten the relationships a bit (I haven't thought much, just throwing here some pretty general approaches).
Just FYI - even though I think it's not going to get you anywhere (see above)...
public class Comment
{
// Properties
public long Id { get; set; }
//[Required]
//[StringLength(250, ErrorMessage = "{0} must be between {1} and {2} characters", MinimumLength = 2)]
public string Body { get; set; }
[Required]
public DateTime CreateDate { get; set; }
// [Required]
// [InverseProperty("Comments")]
public MyUser Author { get; set; }
// [InverseProperty("CommentCount")]
public Blog Blog { get; set; }
public bool Hidden { get; set; }
public virtual ICollection<Comment> Replies { get; set; }
public virtual ICollection<Vote> Votes { get; set; }
public Comment()
{
CreateDate = DateTime.UtcNow;
Hidden = false;
}
}
modelBuilder.Entity<Comment>()
.HasOptional(x => x.Replies)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
This should work fine if you let it 'not cascade'. Otherwise fails.

EF CTP4: How to tell EF a column is NOT identity

I have a code-first, POCO project in which I am trying to adjust an existing database so that it syncs up with what EF is expecting, given my existing model.
I have these entities:
public class FlaggedDate
{
[Key]
public long scheduledDayID { get; set; }
[Required]
public DateTime date { get; set; }
[StringLength(50)]
[Required]
public string dateStatus { get; set; }
[Required]
public bool isVisit { get; set; }
[Required]
public bool hasAvailableSlots { get; set; }
[Required]
public bool hasInterviewsScheduled { get; set; }
// navigation properties
public ICollection<ScheduledSchool> scheduledSchool { get; set; }
public ICollection<Interview> interviews { get; set; }
public ICollection<PartialDayAvailableBlock> partialDayAvailableBlocks { get; set; }
public Visit visit { get; set; }
public ICollection<Event> events { get; set; }
}
and
public class Visit
{
[Key]
public long flaggedDateScheduledDayID { get; set; }
[Required]
public bool isFullDay { get; set; }
// navigation property
public FlaggedDate flaggedDate { get; set; }
}
The relationship between these two is 1 : 0|1 -- i.e., FlaggedDate will exist but it may or may not have a corresponding single Visit object.
EF thinks, based on this model, that FlaggedDate should have an extra field, visit_flaggedDateScheduledDayID, which is nullable. I finally realized why: it thinks the Visit field, flaggedDateScheduledDayID, is an identity column. It's not supposed to be an identity column; it's supposed to be a foreign key that connects to FlaggedDate.
I think it does this by convention: I remember reading something to the effect that in CTP4, any field that is a single key and is int or long is assumed to be an identity column.
Is there any way I can tell EF that this is NOT an identity column? I tried fiddling with the Fluent API, but it's a mystery to me, and there are no data annotations that you can use for this.
Or, alternatively, is there any way I can fiddle with the navigation properties to get this to come out right?
If you're using mapping files with fluent API
this.Property(t => t.Id)
.HasColumnName("Site_ID")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
I would imagine it should also work declaratively
[HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)]
although I didn't try that.
I discovered I can override the identity behavior with this code:
modelBuilder.Entity<Visit>().Property(v => v.flaggedDateScheduledDayID).StoreGeneratedPattern = System.Data.Metadata.Edm.StoreGeneratedPattern.None;
However, it is still not making it a foreign key. I guess that's a different question, though. It seems setting the StoreGeneratedPattern to None is the way to override the default behavior.

Resources