Many to Many between IdentityUser and other table in a separate context - entity-framework

I am having difficulty creating a join table relationship between my Identity Framework IdentityContext(the IdentityUser) and one of my other tables Let's call it Entry. The problem is, Entry is in an entirely separate context doing it's own thing as well.
What is the proper way to associate these two? Where do I define the Join Table in fluent api?
Right now, I am getting the following error.
The key {'ApplicationUserId'} contains properties in shadow state and is referenced by a relationship from 'ApplicationUser.ApplicationUserEntries' to 'ApplicationUserEntry.ApplicationUser'. Configure a non-shadow principal key for this relationship.
These are how my tables are defined.
public class ApplicationUser : IdentityUser
{
...
public virtual List<ApplicationUserEntry> ApplicationUserEntries { get; set; }
}
public class Entry
{
public int Id { get; set; }
...
public virtual List<ApplicationUserEntry> ApplicationUserEntries { get; set; }
}
And the join table as follows.
public class ApplicationUserEntry
{
public int ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public int EntryId { get; set; }
public Entry Entry { get; set; }
}
For the IdentityContext I have just some generic setup for other properties
var users = modelBuilder.Entity<ApplicationUser>();
users.Property(u => u.Name).IsRequired().HasMaxLength(65);
users.Property(u => u.FirstName).HasMaxLength(32);
users.Property(u => u.LastName).HasMaxLength(32);
And in my GoalsContext I have some general setup for other unrelated stuff, and the join table defined for ApplicationUserEntry
// Entry Configuration
var entries = modelBuilder.Entity<Entry>();
entries.HasKey(e => e.Id);
entries.HasAlternateKey(e => new { e.MilestoneId, e.CategoryId, e.MetricId });
entries.Property(e => e.Value).IsRequired();
entries.Property(e => e.Locked).IsRequired().HasDefaultValue(false);
entries.ToTable("GoalsEntries");
// ApplicationUserEntry Join Table
modelBuilder.Entity<ApplicationUserEntry>()
.ToTable("GoalsApplicationUserEntry")
.HasKey(se => new { se.ApplicationUserId, se.EntryId });
modelBuilder.Entity<ApplicationUserEntry>()
.HasOne(se => se.ApplicationUser)
.WithMany(s => s.ApplicationUserEntries)
.HasForeignKey(se => se.ApplicationUserId);
modelBuilder.Entity<ApplicationUserEntry>()
.HasOne(se => se.Entry)
.WithMany(e => e.ApplicationUserEntries)
.HasForeignKey(se => se.EntryId);
Now I'm sure I'm obviously missing something but I can't figure out what. I've never attempted to create a many to many relationship between two tables that are defined in two different contexts... and not even sure if that's wise or not to do.
My ultimate goal is to be able to associate owners with Entry records, so they can only be modified by the owners, which I verify with Identity Framework.
Ideally I would just prefer a unidirectional relationship, so I can find the owner from the Entry, but I'm not intending to get a list of Entry by looking at the IdentityUser

Related

Fluent API: Define both "many to many" as relationship and as relationship object too

I've always defined my many-to-many relationships like this:
modelBuilder.Entity<PriceList>()
.HasMany(i => i.Brands)
.WithMany(p => p.PriceLists)
.Map(m =>
{
m.ToTable("PriceListsBrands");
m.MapLeftKey("PriceListId");
m.MapRightKey("BrandId");
});
And it has always worked well.
But now that I'm using "Entity Framework Extensions Bulk Insert" (https://entityframework-extensions.net/bulk-insert) I want to bulk insert into PriceListsBrands table.
So I had to create the relationship object per sé...(which I usually don't need because I already have the navigation properties Brands and PriceLists)
public class PriceListBrand
{
public long PriceListId { get; set; }
public virtual PriceList PriceList { get; set; }
public long BrandId { get; set; }
public virtual Brand Brand { get; set; }
public PriceListBrand()
{
}
}
To finally call BulkInsert:
var priceListsBrands = new List<PriceListBrand>();
// Populate
// ...
dbContext.BulkInsert(priceListsBrands);
But then I'm getting this exception:
EntityType 'PriceListBrand' has no key defined. Define the key for this EntityType.
I tried adding the following FluentAPI definition above my previous FluentAPI code:
modelBuilder.Entity<CustomerPriceListBrand>()
.HasKey(e => new { e.PriceListId, e.BrandId })
.ToTable("PriceListsBrands");
But then I'm getting this exception:
The EntitySet 'PriceListBrand1' with schema 'dbo' and table
'PriceListsBrands' was already defined.
(Notice the "1": it is trying to define twice the relationship)
So the question is: how can I both define a "many to many" relationship and a relationship object too?

EF6:How to include subproperty with Select so that single instance is created. Avoid "same primary key" error

I'm trying to fetch (in disconnected way) an entity with its all related entities and then trying to update the entity. But I'm getting the following error:
Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
public class Person
{
public int PersonId { get; set; }
public string Personname { get; set }
public ICollection Addresses { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public int PersonId { get; set; }
public string Line1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public Person Person { get; set; }
public ICollection<Feature> Features { get; set; }
}
// Many to Many: Represented in database as AddressFeature (e.g Air Conditioning, Central Heating; User could select multiple features of a single address)
public class Feature
{
public int FeatureId { get; set; }
public string Featurename { get; set; }
public ICollection<Address> Addresses { get; set; } // Many-To-Many with Addresses
}
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
var person = dbContext.People.AsNoTracking().Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}
public void UpdateCandidate(Person newPerson)
{
Person existingPerson = GetPerson(person.Id); // Loading the existing candidate from database with ASNOTRACKING
dbContext.People.Attach(existingPerson); // This line is giving error
.....
.....
.....
}
Error:
Additional information: Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Kindly suggest.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Since you are using a short lived DbContext for retrieving the data, all you need is to remove AsNoTracking(), thus allowing EF to use the context cache and consolidate the Feature entities. EF tracking serves different purposes. One is to allow consolidating the entity instances with the same PK which you are interested in this case, and the second is to detect the modifications in case you modify the entities and call SaveChanges(), which apparently you are not interested when using the context simply to retrieve the data. When you disable the tracking for a query, EF cannot use the cache, thus generates separate object instances.
What you really not want is to let EF create proxies which hold reference to the context used to obtain them and will cause issues when trying to attach to another context. I don't see virtual navigation properties in your models, so most likely EF will not create proxies, but in order to be absolutely sure, I would turn ProxyCreationEnabled off:
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
dbContext.Configuration.ProxyCreationEnabled = false;
var person = dbContext.People.Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}

Entity Framework Many To Many exception with inheritance

I am trying to create two many-to-many relationship maps on a Record object:
Record object that is inherited from
public class Record {
public virtual ICollection<Language> SourceLanguages { get; set; }
public virtual ICollection<Language> TargetLanguages { get; set; }
}
Second Object
public class Language
{
public int Language { get; set; }
public string Locale { get; set; }
public string LanguageName { get; set; }
public virtual ICollection<Record> Records { get; set; }
}
Map for Record
public class RecordMap : EntityTypeConfiguration<Record>
{
this.HasMany(r => r.SourceLanguages)
.WithMany(c => c.Records)
.Map(sl =>
{
sl.ToTable("SourceLanguageRecordMap", "dbo");
sl.MapLeftKey("RecordId");
sl.MapRightKey("LanguageId");
});
this.HasMany(r => r.TargetLanguages)
.WithMany(c => c.Records)
.Map(tl =>
{
tl.ToTable("TargetLanguageRecordMap", "dbo");
tl.MapLeftKey("RecordId");
tl.MapRightKey("LanguageId");
});
}
When I run migration on the object listed above I get the following error:
System.Data.Entity.Core.MetadataException: Schema specified is not
valid. Errors: The relationship
'Toolbox.EntityModel.Contexts.Record_SourceLanguages' was not loaded
because the type 'Toolbox.EntityModel.Contexts.Language' is not
available. ...
Schema specified is not valid. Errors: The relationship
'Toolbox.EntityModel.Contexts.Record_SourceLanguages' was not loaded
because the type 'Toolbox.EntityModel.Contexts.Language' is not
available.
If I comment the following line out, it will work with just one many to many map, however, it will add RecordId_Record to Language Table. Any idea why?
this.HasMany(r => r.TargetLanguages)
.WithMany(c => c.Records)
.Map(tl =>
{
tl.ToTable("TargetLanguageRecordMap", "dbo");
tl.MapLeftKey("RecordId");
tl.MapRightKey("LanguageId");
});
Any idea as to what I am doing wrong?
If you have 2 Many-to-Many relationships to the same table you need to create 2 separate ICollection properties in order for Entity Framework to fully pick up on what you're trying to do. You can't combine them into one, or else you'll get that lovely error that you're seeing there.

EntityFramework is naming my mapping table wrong

I have the following Entity class definition:
[Table("Users")]
public class WebUser
{
public virtual int Id { get; set; }
public virtual ICollection<Client> Clients { get; set; }
// more properties...
}
Notice that table name is different than the class name. I also have a ClientUsers table which is a many-to-many mapping for clients and users. Problem is, when I try to access the webUser.Clients property I get the following exception:
"Invalid object name 'dbo.ClientWebUsers'."
Looks like Entity Framework is trying to guess the name of the third table, but it apparently was not smart enough to take into account the table attribute that I have there. How can I tell EF that it is ClientUsers and not ClientWebUsers? Also what rule does it follow to know which table name comes first and which one comes second in the new table name? I think it's not alphabetical order.
I'm using EF 5.0. Thanks!
From the looks of things you're using Code First, so I'll answer accordingly. If this is incorrect, please let me know.
I believe the convention being used to determine the name of the many-to-many table is determined by the order in which they occur as DbSet properties in your SomeContext : DbContext class.
As for forcing EntityFramework to name your table whatever you like, you can use the Fluent API in the OnModelCreating method of your SomeContext : DbContext class as follows:
public class DatabaseContext : DbContext
{
public DatabaseContext()
: base("SomeDB")
{
}
public DbSet<WebUser> Users { get; set; }
public DbSet<Client> Clients { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<WebUser>().HasMany(c => c.Clients)
.WithMany(p => p.WebUsers).Map(
m =>
{
m.MapLeftKey("ClientId");
m.MapRightKey("UserId");
m.ToTable("ClientUsers");
});
}
}
This assumes your classes are something like the following:
[Table("Users")]
public class WebUser
{
public virtual int Id { get; set; }
public virtual ICollection<Client> Clients { get; set; }
// more properties...
}
public class Client
{
public int Id { get; set; }
public ICollection<WebUser> WebUsers { get; set; }
// more properties
}
Finally, here's an integration test (NUnit) demonstrating the functionality working. You may need to drop your database before running it as Code First should want to update/migrate/recreate it.
[TestFixture]
public class Test
{
[Test]
public void UseDB()
{
var db = new DatabaseContext();
db.Users.Add(new WebUser { Clients = new List<Client> { new Client() } });
db.SaveChanges();
var webUser = db.Users.First();
var client = webUser.Clients.FirstOrDefault();
Assert.NotNull(client);
}
}
Edit: Link to relevant documentation for the Fluent API
Rowan's answer (adding here for reference):
Here is the information on how to configure a many-to-many table (including specifying the table name). The code you are after is something like:
modelBuilder.Entity<WebUser>()
.HasMany(u => u.Clients)
.WithMany(c => c.WebUsers)
.Map(m => m.ToTable("ClientUsers");
~Rowan

EF4 CTP5 Database First + Many-To-Many Relation (Error)

I'm pretty sure it's something regarding hidden conventions, but I always get an error when trying to map a many-to-many relation to an existing database.
Here is the simplest example:
[Table("ALRole", SchemaName = "AL")]
public class Role
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
[Table("ALUser", SchemaName = "AL")]
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
I got the usual three tables in the db: the first two are obvious, and the third is created with this script:
CREATE TABLE AL.ALUsersRoles
(
RoleID int NOT NULL,
UserID int NOT NULL,
CONSTRAINT PK_ALUserRole PRIMARY KEY(RoleID, UserID),
CONSTRAINT FK_ALUserRole_RoleID FOREIGN KEY(RoleID) REFERENCES AL.ALRole(ID),
CONSTRAINT FK_ALUserRole_UserID FOREIGN KEY(UserID) REFERENCES AL.ALUser(ID)
)
Now I try to map the many-to-many relation, with code like this:
// ...I'm in the EntityTypeConfiguration-derived class (User)
HasMany(u => u.Roles)
.WithMany(r => r.Users)
.Map(m =>
{
m.MapLeftKey(u => u.ID, "UserID");
m.MapRightKey(r => r.ID, "RoleID");
ToTable("ALUsersRoles", "AL");
});
I tried all the possibile combinations and variations in this code, but I always get the error:
{"Invalid column name 'Name'.\r\nInvalid ...and so on...
So I think it must be the table that is not created correctly.
Any ideas?
Thanks in advance
Andrea
P.S.: I stripped down some of my code, so maybe there can be some small typo...
well, this works for me same as OP.
//many-to-many between *Users -> Web_User_Rol <- Web_Rol*
modelBuilder.Entity<Users>()
.HasMany(u => u.Web_Rols).WithMany(r => r.Users)
.Map(t=>t.MapLeftKey("user_id")
.MapRightKey("roleID")
.ToTable("Web_User_Rol"));
There is nothing wrong with your object model or fluent API code. I've used them and they perfectly created the desired schema without any exception. I think your problem comes from another entity (perhaps one with a "Name" property) unrelated to what you've shown here. To find that, drop (or rename) your existing database and let Code First create one for you and then compare the 2 databases and see what is different.