Mapping same collection with two Navigation Properties - entity-framework

I have the following model
public class Locale
{
public int Id { get; set; }
public ICollection<Localization<Locale>> Localizations { get; set; }
}
public class Localization<T>
{
public int Id { get; set; }
public Locale Locale { get; set; }
public string DisplayName { get; set; }
public T Entity { get; set; }
}
In this case, I want to be able to localize any entity, include the localization itself (ie: for places where we show available languages in the users language ).
I have this working in NHibernate, but I need to move to EF. The issue arises when I want to use the fluent API to map it as follows.
modelBuilder.Entity<Locale>()
.HasMany(x => x.Localizations)
.WithRequired(x => x.Locale)
.Map(x => x.MapKey("LocaleId"));
This works, but then I need to map the entity itself.
Doing this overrides the previous map.
modelBuilder.Entity<Locale>()
.HasMany(x => x.Localizations)
.WithRequired(x => x.Entity)
.Map(x => x.MapKey("EntityId"));
Doing it this way throws an error on either field (I've also tried making a sub class of Localization called LocaleLocalization, with the same result).
modelBuilder.Entity<Localization<Locale>>()
.HasRequired(x => x.Entity)
.WithMany()
.Map(x => x.MapKey("LCIDLocale"))
The Error
The navigation property "Entity" is not a declared property on type
Localization. Verify that it has not been explicitly excluded from the model
and that it is a valid navigation property.

The solution is that I need to map two collections, one representing the collection of Localizations for this Locale, and one representing the collection of other Locales which are localized into this locale.
ICollection<Localizations> MyLocalizations { get; set; }
ICollection<Localizations> LocalesLocalizedByMe { get; set; }

Related

Include ignored column in EFCore

I have a scenario where I'd wish to ignore a column (so that it get's not joined on every include etc.) but include it explicit if I need to. Selecting all the other columns needed on every join is not a practible solution for me. I store a user's profile picture in the DB and the owner, midifier etc. gets joined on almost every object, making some queries pretty slow.
Is there a best practice for this scenario?
Edit: I thought about creating a 2nd, virtual Model mapped to the same table containing only this one column but two models mapped to the same table seam to make some problems.
As you have added in you Edit section. That is how I solved a similar problem.
This works for me and it runs in production today.
And I can easy include the the Body filed when I need to.
public class Content
{
public Guid Id { get; set; }
public string Heading { get; set; }
public string Preamble { get; set; }
public virtual ContentBody Body { get; set; }
}
public class ContentBody
{
public string Body { get; set; }
public Guid Id { get; set; }
}
This is where I do the Model mapping:
public class ContentMap : IEntityTypeConfiguration<Content>
{
public void Configure(EntityTypeBuilder<Content> builder)
{
// Primary Key
builder.ToTable("Content");
builder.HasKey(e => e.Id);
builder.Property(e => e.Id).ValueGeneratedOnAdd();
builder.Property(p => p.Heading).HasColumnName("Heading");
builder.Property(p => p.Preamble).HasColumnName("Preamble");
// Relationships
builder.HasOne(t => t.Body)
.WithOne().HasForeignKey<ContentBody>(t => t.Id);
}
}
public class ContentBodyMap : IEntityTypeConfiguration<ContentBody>
{
public void Configure(EntityTypeBuilder<ContentBody> builder)
{
// Primary Key
builder.ToTable("Content");
builder.HasKey(e => e.Id);
builder.Property(p => p.Body).HasColumnName("Body");
}
}

EF Core Navigation property on ApplicationUser is always null

I'm trying to have a list of "buddies" for an ApplicationUser (just using the standard ASP NET Core Identity implementation).
In my mind, an ApplicationUser can have multiple Buddies, and a Buddy can have multiple ApplicationUser's. This means its a many to many relationship.
In line with EF Core's existing limitations with M2M relationships, I made a join class that looks like this:
public class ApplicationUserBuddies
{
public string UserGUID { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public string BuddyGUID { get; set; }
public ApplicationUser BuddyUser { get; set; }
}
I then configure ModelBuilder to recognise this relationship with in my OnModelCreating override
builder.Entity<ApplicationModels.ApplicationUserBuddies>().HasOne(x => x.ApplicationUser)
.WithMany(x => x.Buddies).HasForeignKey(x => x.UserGUID).IsRequired();
However, when I query through this table using EF, the "buddy" navigation property is always null. The actual string is set correctly (to that users GUID). This is how I am getting those details (note I am using Include):
var buddies = await _context.ApplicationUserBuddies.Include(x => x.BuddyUser).Where(x => x.UserGUID == UserGUID)
.Select(x => x.BuddyUser).ToListAsync();
Another strange thing is in my ApplicationUserBuddies table, I get a third column that I haven't set up anywhere called BuddyUserID
I think the issue I am having is because I have a many to many relationship that references one table (AspNetUsers table) and that could be affecting it.
How can I have a list of users as a navigation property from AspNetUsers and have it work correctly? Thanks everyone.
EDIT
I have changed my model to be in line with EF conventions
public class ApplicationUserBuddies
{
public string UserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public string BuddyUserId { get; set; }
public ApplicationUser BuddyUser { get; set; }
}
And my model builder now looks like this:
builder.Entity<ApplicationModels.ApplicationUserBuddies>().HasKey(x => new { x.UserId, x.BuddyUserId });
builder.Entity<ApplicationModels.ApplicationUserBuddies>().HasOne(x => x.ApplicationUser)
.WithMany(x => x.Buddies).HasForeignKey(x => x.BuddyUserId).IsRequired();
And now I have the columns UserId, BuddyUserId, and the erronerous BuddyUserId1. I think that this is essentially the same issue that I had at the outset.

BUG: Entity Framework Power Tools Fails to Generate Designer

Entity Framework 6.1 using Power Tools 4 generates an error when using the command:
View Entity Data Model (Read Only) on the context.
The error:
An error occurred while trying to build the model for Context. See the output window for details.
The output window basically states:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.MissingMethodException: Method not found: 'xyz.domain.User xyz.domain.DomainBase.get_ModifiedBy()'
* CRITICAL * The power tools are also caching the schema and do NOT update with changes! ***
Example:
Create a base class:
public abstract class DomainBase {
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
public User CreatedBy { get; set; }
public string CreatedByName { get; set; }
public User ModifiedBy { get; set; }
public string ModifiedByName { get; set; }
}
Create a class that inherits from the base class:
public class User : DomainBase {
public int UserId { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public string Salt { get; set; }
public string Pin { get; set; }
}
Create Base Mapping:
public class DomainBaseConfig<TEntity> : EntityTypeConfiguration<TEntity>
where TEntity : DomainBase {
public DomainBaseConfig() {
this.HasOptional(c => c.ModifiedBy).WithOptionalDependent()
.Map(m => m.MapKey("ModifiedById"));
this.HasOptional(c => c.CreatedBy).WithOptionalDependent()
.Map(m => m.MapKey("CreatedById"));
this.Property(t => t.CreatedByName).HasMaxLength(FieldMaxSize.FullName).IsRequired();
this.Property(t => t.ModifiedByName).HasMaxLength(FieldMaxSize.FullName).IsRequired();
this.Property(t => t.DateCreated).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
this.Property(t => t.DateModified).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
}
}
Created User class mapping:
public UserConfig() {
this.ToTable("Users");
this.HasKey(c => c.UserId);
this.Property(t => t.FirstName).HasMaxLength(FieldMaxSize.FirstName).IsRequired();
this.Property(t => t.LastName).HasMaxLength(FieldMaxSize.LastName).IsRequired();
this.Property(t => t.Password).HasMaxLength(FieldMaxSize.Password).IsRequired();
this.Property(t => t.Pin).HasMaxLength(FieldMaxSize.Pin).IsRequired();
this.Property(t => t.Salt).HasMaxLength(FieldMaxSize.Salt).IsRequired();
this.Property(t => t.Username).HasMaxLength(FieldMaxSize.Username).IsRequired().HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute("IX_Username", 1) { IsUnique = true }));
}
When trying to view the model, the error seems to be related to the base class referring to the parent class where the parent requires the base (they reference each other) and the designer doesn't like this, but EF handles it just fine.
PROOF: If I remove the references to "User" in the base class, the designer WILL work (badly)... NOTE... I do get it to work by removing both base class properties referencing the User class and their associated mappings... BUT... the designer STILL MODELS THEM!
So, I remove the User properties (ModifiedBy and CreatedBy) in the base class and I also remove the associated mappings, then the designer builds a graphic representations of the schema ... BUT... it still includes the properties I just commented out (they are clearly cached somewhere)!
PLEASE!
(1) Correct the designer so it doesn't crash on valid schema as provided in the sample
(2) Fix the caching problem with the designer so that it NEVER builds schema for the designer on old/stale code!
Based on this example, the Power Tools designer is completely unreliable as it can clearly build a model that DOES NOT match actual code/schema.
For entity framework >= 6 you need to use Entity framework designer tools http://www.microsoft.com/en-au/download/details.aspx?id=40762

EF 4.1 Code First ModelBuilder HasForeignKey for One to One Relationships

Very simply I am using Entity Framework 4.1 code first and I would like to replace my [ForeignKey(..)] attributes with fluent calls on modelBuilder instead. Something similar to WithRequired(..) and HasForeignKey(..) below which tie an explicit foreign key property (CreatedBySessionId) together with the associated navigation property (CreatedBySession). But I would like to do this for a one to one relationsip instead of a one to many:
modelBuilder.Entity<..>().HasMany(..).WithRequired(x => x.CreatedBySession).HasForeignKey(x => x.CreatedBySessionId)
A more concrete example is below. This works quite happily with the [ForeignKey(..)] attribute but I'd like to do away with it and configure it purely on modelbuilder.
public class VendorApplication
{
public int VendorApplicationId { get; set; }
public int CreatedBySessionId { get; set; }
public virtual Session CreatedBySession { get; set; }
}
public class Session
{
public int SessionId { get; set; }
[ForeignKey("CurrentApplication")]
public int? CurrentApplicationId { get; set; }
public virtual VendorApplication CurrentApplication { get; set; }
public virtual ICollection<VendorApplication> Applications { get; set; }
}
public class MyDataContext: DbContext
{
public IDbSet<VendorApplication> Applications { get; set; }
public IDbSet<Session> Sessions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Session>().HasMany(x => x.Applications).WithRequired(x => x.CreatedBySession).HasForeignKey(x => x.CreatedBySessionId).WillCascadeOnDelete(false);
// Note: We have to turn off Cascade delete on Session <-> VendorApplication relationship so that SQL doesn't complain about cyclic cascading deletes
}
}
Here a Session can be responsible for creating many VendorApplications (Session.Applications), but a Session is working on at most one VendorApplication at a time (Session.CurrentApplication). I would like to tie the CurrentApplicationId property with the CurrentApplication navigation property in modelBuilder instead of via the [ForeignKey(..)] attribute.
Things I've Tried
When you remove the [ForeignKey(..)] attribute the CurrentApplication property generates a CurrentApplication_VendorApplicationId column in the database which is not tied to the CurrentApplicationId column.
I've tried explicitly mapping the relationship using the CurrentApplicationId column name as below, but obviously this generates an error because the database column name "CurrentApplicationId" is already being used by the property Session.CurrentApplicationId:
modelBuilder.Entity<Session>().HasOptional(x => x.CurrentApplication).WithOptionalDependent().Map(config => config.MapKey("CurrentApplicationId"));
It feels like I'm missing something very obvious here since all I want to do is perform the same operation that [ForeignKey(..)] does but within the model builder. Or is it a case that this is bad practise and was explicitly left out?
You need to map the relationship as one-to-many and omit the collection property in the relationship.
modelBuilder.Entity<Session>()
.HasOptional(x => x.CurrentApplication)
.WithMany()
.HasForeignKey(x => x.CurrentApplicationId)

One to many relationship error

I have the following model, but I keep getting an error:
Unhandled Exception: System.InvalidOperationException: A relationship
multiplici ty constraint violation occurred: An EntityReference can
have no more than one r elated object, but the query returned more
than one related object. This is a no n-recoverable error.
public class Tournament
{
public long TournamentId { get; set; }
public string Title { get; set; }
public virtual User CreatedBy { get; set; }
}
public class User
{
public int UserId { get; set; }
}
modelBuilder.Entity<Tournament>()
.HasRequired(t => t.CreatedBy)
.WithOptional()
.Map(c => c.MapKey("CreatedById")); // correct column name
Your model fluent configuration entry is incorrect. Change it as follows
modelBuilder.Entity<Tournament>()
.HasRequired(t => t.CreatedBy)
.WithMany()
.Map(c => c.MapKey("CreatedById")); // correct column name
You'll have better luck managing Foreign keys if you modify you model a bit:
public class Tournament
{
public long TournamentId { get; set; }
public string Title { get; set; }
public virtual int CreatedById {get;set;}
public virtual User CreatedBy { get; set; }
}
and your mapping would look more like this:
modelBuilder.Entity<Tournament>()
.HasRequired(t => t.CreatedBy)
.WithMany()
.HasForeignKey(t => t.CreatedById); // correct column name
This way, when you create a new Tournament Entity you need only pass in the CreatedById and not the entire User object.
This can also happen if you have lazy loading enabled and not specifying all the navigation properties as Overridable (C# Virtual).