In this question: Ef Many To Many, an answer came up on how to manually specify a linking table. But I have a slightly unique situation (which I'm sure isn't really unique).
My two tables each have an Id field. E.G.: [dbo].[Account].[Id] and [dbo].[Person].[Id]. Each of these tables in my Code-First has the following OnModelCreating:
modelBuilder.Entity<Account>.HasKey(x => x.Id);
modelBuilder.Entity<Person>.HasKey(x => x.Id);
But my [dbo].[AccountsToPersons]... table has fields E.G.: [AccountId] and [PersonId]
The AccountsToPersons table is not represented by a class in code.
I obviously already have an existing Model, but we are using EF Code-First Fluent API instead of updating model from database.
So how do I change this code to make it work with mapping different ID columns names?
public DbSet<Person> Persons { get; set; }
public DbSet<Account> Accounts { get; set; }
. . .
modelBuilder.Entity<Account>()
.HasMany(a => a.Persons)
.WithMany()
.Map(x =>
{
x.MapLeftKey("AccountId"); // <-- Account.Id to AccountsToPersons.AccountId??
x.MapRightKey("PersonId"); // <-- Person.Id to AccountsToPersons.PersonId??
x.ToTable("AccountsToPersons");
});
When running a basic Linq To EF Query (from x in context.Accounts select x).ToList();, the query fails with the following error:
"Invalid Column Name 'Person_Id'."
But when running the Query (from x in context.Persons select x).ToList();, I get no error.
Other than basic typed columns, my models have these added to them:
// In my Account Model, I also have this property:
public IList<Person> Persons { get; set; }
// In my Person Model, I also have this property:
public IList<Account> Accounts { get; set; } // <-- in the Person model
And please note that even though my Accounts query passes and has field information, the Persons field is always null, even though I'm sure there are links in my AccountsToPersons table.
Try adding p => p.Accounts to your WithMany clause:
modelBuilder.Entity<Account>()
.HasMany(a => a.Persons)
.WithMany(p => p.Accounts) // <-- I think this should fix it
.Map(x =>
{
x.MapLeftKey("AccountId"); // <-- Account.Id to AccountsToPersons.AccountId??
x.MapRightKey("PersonId"); // <-- Person.Id to AccountsToPersons.PersonId??
x.ToTable("AccountsToPersons");
});
I just built up a test solution for your problem and for me it looks that is working.
One thing that i see you did different than me is:
public IList<Person> Persons { get; set; } // <-- in the Account model
public IList<Account> Accounts { get; set; } // <-- in the Person model
Try modifying into this:
public DbSet<Person> Persons { get; set; }
public DbSet<Account> Accounts { get; set; }
If this doesn't work i`ll post my entire setup.
My structure looks like this and it works for the queries you displayed:
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public IList<Account> Accounts { get; set; }
}
public class Account
{
public int ID { get; set; }
public string Name { get; set; }
public IList<Person> Persons { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Account> AccountSet { get; set; }
public DbSet<Person> PersonSet { get; set; }
public ApplicationDbContext()
: base("DefaultConnection")
{
this.Database.Log = (msg) => { Debug.Write(msg); };
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Account>().HasKey(x => x.ID);
modelBuilder.Entity<Person>().HasKey(x => x.ID);
modelBuilder.Entity<Account>()
.HasMany(a => a.Persons)
.WithMany()
.Map(w =>
{
w.MapLeftKey("AccountId");
w.MapRightKey("PersonId");
w.ToTable("AccountsToPersons");
});
}
}
Have you tried modifying your AccountsToPersons mapping slightly:
modelBuilder.Entity<Account>()
.HasMany(a => a.Persons)
.WithMany(p => p.Accounts) <-- Change
.Map(x =>
{
x.MapLeftKey("AccountId"); // <-- Account.Id to AccountsToPersons.AccountId??
x.MapRightKey("PersonId"); // <-- Person.Id to AccountsToPersons.PersonId??
x.ToTable("AccountsToPersons");
});
Related
I use the following Model to create a self-referencing table:
public class Part
{
[Key]
public long PartId { get; set; }
[Required]
public string PartName { get; set; }
public long? ParentPartId { get; set; }
public Part Parentpart { get; set; }
//Navigation properties
public ICollection<Part> ChildParts { get; set; } //For self-referencing
}
The Fluent API is:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//For parts self-referencing
modelBuilder.Entity<Part>(entity =>
{
entity.HasKey(x => x.PartId);
entity.Property(x => x.PartName);
entity.HasOne(e => e.Parentpart)
.WithMany(e => e.ChildParts)
.HasForeignKey(e => e.ParentPartId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
});
}
I can use the following code to get all the parents of a given part:
[HttpGet]
public async Task<IActionResult> GetParentPartsList(string partName)
{
var q = _sqlServerContext.Parts.Where(x => x.PartName == partName).Include(x => x.Parentpart);
while (q.Select(x => x.ParentPartId) == null)
{
q = q.ThenInclude(x => x.Parentpart);
}
q = q.ThenInclude(x => x.Parentpart);
return Ok(await q.ToListAsync());
}
I want to get a list of all children for a given part. I searched some threads such as this link, but they have a one-one relationship between child and parent while I need a one-many relationship. How can I achieve this?
Imagine these models are a part of a social network application. User can make a Group and can add other users in it(as members) and a user can be a member of the group. I'm using .net core and also entity framework core.
Models:
public class User : BaseEntity
{
public string UserName { get; set; }
public IList<Group> OwnGroups { get; set; }
public IList<GroupMember> MemberInGroups { get; set; }
}
public class Group : BaseEntity
{
public string Name { get; set; }
public int OwnerUserId { get; set; }
[ForeignKey("OwnerUserId")]
public User OwnerUser { get; set; }
public IList<GroupMember> Members { get; set; }
}
public class GroupMember : BaseEntity
{
public int GroupId { get; set; }
public int UserId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
[ForeignKey("UserId")]
public Group Group { get; set; }
}
Fluent API:
modelBuilder.Entity<User>()
.HasMany(x => x.OwnGroups)
.WithOne(x => x.OwnerUser).HasForeignKey(x => x.OwnerUserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<User>()
.HasMany(x => x.MemberInGroups)
.WithOne(x => x.User).HasForeignKey(x => x.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Group>()
.HasMany(x => x.Members)
.WithOne(x => x.Group).HasForeignKey(x => x.GroupId).IsRequired().OnDelete(DeleteBehavior.Cascade);
When I want to migrate to the database, this error happens:
Introducing FOREIGN KEY constraint 'FK_GroupMembers_Users_UserId' on table 'GroupMembers' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.
However, I can make this model in SQL Server manually and there is no problem.
There are at least two ways to fix it. But I want to know why EF Core says, there is a cycle. What's the problem is?
I think your bridge table(junction) has a problem.
Try this
public class GroupMember: BaseEntity
{
public User User { get; set; }
[Key, Column(Order = 0)]
public int UserId { get; set; }
public Group Group { get; set; }
[Key, Column(Order = 1)]
public int GroupId { get; set; }
}
First of all: Get rid of the Fluent API part
//modelBuilder.Entity<User>()
//.HasMany(x => x.OwnGroups)
//.WithOne(x => x.OwnerUser).HasForeignKey(x => x.OwnerUserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
//modelBuilder.Entity<User>()
// .HasMany(x => x.MemberInGroups)
// .WithOne(x => x.User).HasForeignKey(x => x.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
//modelBuilder.Entity<Group>()
// .HasMany(x => x.Members)
// .WithOne(x => x.Group).HasForeignKey(x => x.GroupId).IsRequired().OnDelete(DeleteBehavior.Cascade);
And then, based on Microsoft Docs, choose one of these cases:
Change one or more of the relationships to not cascade delete(make it nullable)
public int? OwnerUserId { get; set; }
The second approach instead, we can keep the OwnerUserId relationship required(non-nullable) and configured for cascade delete, but make this configuration only apply to tracked entities, not the database.
modelBuilder
.Entity<User>()
.HasMany(e => e.OwnGroups)
.WithOne(e => e.OwnerUser)
.OnDelete(DeleteBehavior.ClientCascade);
In this way, if we want to delete a User and all its Groups, we should load both the User and its Groups in the application(RAM). Look at this:
After running this code, the User and all its Groups will be removed.
var context = new MyDbContext();
var user = context.Users.Single(x => x.UserName == "SampleName");
var groups = context.Groups.Where(x => x.OwnerUser == user).ToList();
context.Users.Remove(user);
context.SaveChanges();
But the code below will throw an exception.
var context = new MyDbContext();
var user = context.Users.Single(x => x.UserName == "SampleName");
context.Users.Remove(user);
context.SaveChanges();
Microsoft.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN KEY constraint 'FK_Groups_Users_OwnerUserId' on table 'Groups' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
My problem is similar to Is it possible to have a relation where the foreign key is also the primary key? but I have to do this with Fluent API.
I have basically the same situation as described in the question, but I cannot use annotations on my domain models due to our coding standards. Here is a bit of code (Clarified):
Domain Classes:
public class Table1
{
public long ID { get; set; }
public int SubTableType { get; set; }
...
public Table2 Table2 { get; set; }
public Table3 Table3 { get; set; }
public List<Table4> Table4s { get; set; }
public List<Table5> Table5s { get; set; }
}
public class Table2
{
public long ID { get; set; }
public string Location { get; set; }
public string Task { get; set; }
...
public Table1 Table1 { get; set; }
public Table6 Table6 { get; set; }
public List<Table7> Table7s { get; set; }
}
public class Table3
{
public long ID { get; set; }
public string DescriptionAndLocation { get; set; }
...
public Table1 Table1 { get; set; }
}
Configuration Classes:
internal class Table1Configuration : EntityTypeConfiguration<Table1>
{
public Table1Configuration()
{
ToTable("Table1");
HasKey(so => so.ID);
Property(so => so.SubTableType)
.IsRequired();
Property(so => so.ID)
.IsRequired()
.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
...
}
}
internal class Table2Configuration : EntityTypeConfiguration<Table2>
{
public Table2Configuration()
{
ToTable("Table2");
HasKey(bc => bc.ID);
Property(bc => bc.ID)
.IsRequired();
Property(bc => bc.Location)
.IsOptional()
.HasColumnType("nvarchar")
.HasMaxLength(50);
Property(bc => bc.Task)
.IsOptional()
.HasColumnType("nvarchar")
.HasMaxLength(4000);
...
HasRequired(bc => bc.Table1)
.WithOptional(so => so.Table2);
HasRequired(bc => bc.Table8)
.WithMany(bot => bot.Table2s)
.HasForeignKey(bc => bc.Tabe8ID);
}
}
internal class Table3Configuration : EntityTypeConfiguration<Table3>
{
public Table3Configuration()
{
ToTable("Table3");
HasKey(hic => hic.ID);
Property(hic => hic.DescriptionAndLocation)
.IsOptional()
.HasColumnType("nvarchar")
.HasMaxLength(4000);
Property(hic => hic.ID)
.IsRequired()
.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
HasRequired(hic => hic.Table1)
.WithOptional(so => so.Table3);
}
}
When I run this code I get the error:
Invalid column name 'Table2_ID'.
What you are asking is the so called Shared Primary Key Associations, which is the standard (and better supported) EF6 model for one-to-one relationships.
Rather than removing the ID property, you should remove the MapKey call which is used to define a shadow FK property (which you don't need).
Since the property called ID by convention is a PK and required, basically all you need is this:
HasRequired(hic => hic.Table1)
.WithOptional(so => so.Table2); // or Table3
or the explicit equivalent of [Key] / [ForeignKey] combination:
HasKey(hic => hic.ID);
HasRequired(hic => hic.Table1)
.WithOptional(so => so.Table2); // or Table3
Exactly as the example for Configuring a Required-to-Optional Relationship (One-to–Zero-or-One) from the documentation.
I would try something like this:
modelBuilder.Entity<Table1>().HasKey(t => t.ID);
modelBuilder.Entity<Table1>().Property(t =>t.ID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Table1>()
.HasOptional(t1 => t1.Table2)
.WithRequired(t2 => t2.Table1).Map(m => m.MapKey("ID"));
I have 2 entities with a many to many relationship:
[Table("Student", Schema = "School")]
public class Student
{
public long ID { get; set; }
public virtual List<Teacher> Teachers { get; set; }
//...
}
[Table("Teacher", Schema = "School")]
public class Teacher
{
public long ID { get; set; }
public virtual List<Student> Students { get; set; }
//...
}
I've specified in the fluent API how to construct the join table as such:
public StudentMap(string schema)
{
//Where schema = "School"
HasMany(p => p.Teachers)
.WithMany(p => p.Students)
.Map(m =>
{
m.ToTable("StudentsTeachers", schema);
m.MapLeftKey("Student_ID");
m.MapRightKey("Teacher_ID");
});
}
However, when I go to access the Teachers navigation object on the Student, it defaults to the EF convention as opposed to what I've designated for the join table. How would I go about specifying to the SchoolContext that we should be looking at School.StudentsTeachers table instead of dbo.StudentTeachers ?
The problem isn't in the designation of the join table, or the many to many relationship being generated. Those worked fine. The problem arises when attempting to use the Entities, I need a way to specify the relationship should use the Join table I specified, as opposed to the EF naming convention. I was able to resolve a similar issue with EF using its conventions over my table names by using the Table Attribute as shown above. I'm now looking for an equivalent answer except with regards to the many to many join table that exists, but doesn't have an explicit model
I think this should work:
[Table("Student", Schema = "School")]
public class Student
{
[Key,Column("Student_ID")]
public long ID { get; set; }
public virtual List<Teacher> Teachers { get; set; }
//...
}
[Table("Teacher", Schema = "School")]
public class Teacher
{
[Key,Column("Teacher_ID")]
public long ID { get; set; }
public virtual List<Student> Students { get; set; }
//...
}
public StudentMap()
{
HasMany(p => p.Teachers)
.WithMany(p => p.Students)
.Map(m =>
{
m.ToTable("StudentsTeachers", "School");
m.MapLeftKey(p => p.ID);
m.MapRightKey(p => p.ID);
});
}
I'm having a lot of trouble with creating my business entities from my data entities.
Github
My Data.Entities.User looks as follows:
public class User
{
public User()
{
Messages = new List<Message>();
Followers = new List<User>();
Favorites = new List<Message>();
Notifications = new List<Notification>();
SubscribedTopics = new List<Topic>();
}
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Tag { get; set; }
public string Picture { get; set; }
public ICollection<Message> Messages { get; set; }
public ICollection<User> Followers { get; set; }
public ICollection<Message> Favorites { get; set; }
public ICollection<Notification> Notifications { get; set; }
public ICollection<Topic> SubscribedTopics { get; set; }
}
My Data.Mappers.UserMapper looks like this:
class UserMapper : EntityTypeConfiguration<User>
{
public UserMapper()
{
// Table Mapping
ToTable("Users");
// Primary Key
HasKey(u => u.Id);
Property(u => u.Id)
.IsRequired();
// Properties
Property(u => u.Name)
.IsRequired()
.HasMaxLength(140);
Property(u => u.Email)
.IsRequired()
.HasMaxLength(255)
.IsUnicode(false);
Property(u => u.Tag)
.IsRequired()
.IsUnicode(false)
.HasMaxLength(255)
.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute()));
Property(u => u.Picture)
.IsOptional();
// Relationships
HasMany(u => u.Followers)
.WithMany()
.Map(u => u.MapLeftKey("FollowerID"));
HasMany(u => u.Favorites)
.WithMany()
.Map(u => u.MapLeftKey("MessageID"));
HasMany(u => u.SubscribedTopics)
.WithMany(t => t.Subscribers)
.Map(u =>
{
u.ToTable("TopicSubscribers");
u.MapLeftKey("UserID");
u.MapRightKey("TopicID");
});
}
}
Finally, my Domain.Entities.User like this:
public class User : EntityBase<string>
{
public string Name { get; set; }
public string Email { get; set; }
public string Tag { get; set; }
public string Picture { get; set; }
public IEnumerable<Message> Messages { get; set; }
public IEnumerable<User> Followers { get; set; }
public IEnumerable<Message> Favorites { get; set; }
public IEnumerable<Notification> Notifications { get; set; }
public IEnumerable<Topic> SubscribedTopics { get; set; }
protected override void Validate()
{
if (string.IsNullOrWhiteSpace(Name))
{
AddBrokenRule(new ValidationRule("Name", "Name_Missing"));
}
if (string.IsNullOrWhiteSpace(Email))
{
AddBrokenRule(new ValidationRule("Email", "Email_Missing"));
}
if (string.IsNullOrWhiteSpace(Tag))
{
AddBrokenRule(new ValidationRule("Tag", "Tag_Missing"));
}
System.Uri uriResult;
if (!string.IsNullOrWhiteSpace(Picture) &&
Uri.TryCreate(Picture, UriKind.Absolute, out uriResult) &&
(uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps))
{
AddBrokenRule(new ValidationRule("Picture", "Picture_InvalidURI"));
}
}
}
EntityBase adds the Id parameter, so as far as parameters are concerned, these two classes should be identical.
The part where I run into trouble is mapping the Data Entity to the Domain Entity.
public override IEnumerable<User> GetAll()
{
IEnumerable<User> user = _context.Users.Project()
.To<User>("Followers");
return user;
}
I think what's causing trouble is the circular navigational properties. User1 might have a follower named User2, while at the same time following User2.
So far I have tried both AutoMapper and ValueInjecter, but I have not had any success with either.
I tried adding "Virtual" to all navigational properties, enabling lazy and proxy loading, but this causes both AutoMapper and ValueInjecter to fail. ValueInjecter due to a already opened datareader and AutoMapper because of a type mismatch.
I tried explicitly loading navigational properties, but as soon as I Include("Followers") on User, I get a stackoverflow.
Trying to create a AutoMapperConfiguration where I specify a maxDepth of 1 yields a stackoverflow unless I add opt.ExplicitExpansion to every navigational property.
If i then try to explicitly expand a navigational property, I get
The type 'ShortStuff.Domain.Entities.User' appears in two structurally
incompatible initializations within a single LINQ to Entities query. A
type can be initialized in two places in the same query, but only if
the same properties are set in both places and those properties are
set in the same order.
Ideally I would want a solution that lets me explicitly control which navigational properties to expand without recursing.
For example, I'd like to do something like:
_context.Users.Include("Followers").NoNavigation().AsEnumerable();
And then I would be able to access User.Followers and have a list of other users, with their navigational properties set to null.
Many thanks!
Full source code of my Repository / Service learning project can be found on Github at https://github.com/Bio2hazard/ShortStuff/tree/master/ShortStuffApi
Edit:
I made some progress.
I got things to work by turning off proxy generation & lazy loading, and then using ValueInjector like so:
IEnumerable<Data.Entities.User> userList = _context.Users.Include("Followers").Include("Favorites").Include("Messages").Include("Notifications").Include("SubscribedTopics");
IEnumerable<User> users = userList.Select(u => new User
{
Id = u.Id,
Email = u.Email,
Picture = u.Picture,
Tag = u.Tag,
Name = u.Name,
Followers = u.Followers.Select(uu => new User().InjectFrom<SmartConventionInjection>(uu)).Cast<User>(),
Favorites = u.Favorites.Select(uf => new Message().InjectFrom<SmartConventionInjection>(uf)).Cast<Message>(),
Messages = u.Messages.Select(um => new Message().InjectFrom<SmartConventionInjection>(um)).Cast<Message>(),
Notifications = u.Notifications.Select(un => new Notification().InjectFrom<SmartConventionInjection>(un)).Cast<Notification>(),
SubscribedTopics = u.SubscribedTopics.Select(ut => new Topic().InjectFrom<SmartConventionInjection>(ut)).Cast<Topic>()
});
But that's a ton of code. I could probably create a factory for this, but there has got to be a easier way, right?
with ValueInjecter you can use the SmartConventionInjection which will only access the properties if it needs to get the value:
http://valueinjecter.codeplex.com/wikipage?title=SmartConventionInjection&referringTitle=Home
other injections usually get the value too so that you could use it in the matching algorithm
for an example of using valueinjecter with Entity Framework (code first, latest)
have a look at this project: http://prodinner.codeplex.com