I have two objects with a many-to-one relationship:
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public virtual Collection<ProductInventory> ProductInventorys { get; set; } = new Collection<ProductInventory>();
}
public class ProductInventory
{
public int ProductInventoryID { get; set; }
public string ProductInventoryName { get; set; }
public int ProductID { get; set; }
public virtual Product ProductFK { get; set; }
}
I would like to add a new Product with a collection of existing ProductInventory (my API would have an input of ProductInventoryID array) into the database, so I perform like:
private void AddProduct(int[] productInventoryIDs)
{
Product newProduct = new Product();
newProduct.Name = "New Product";
// Here I have no clue which would be the correct way...should I use
// Approach A - fetch each related "ProductInventory" entity from database,
// then add them into the collection of my new base entity - Product)
productInventoryIDs.ToList().Foreach(p =>
{
newProduct.ProductInventorys.Add(_dbContext.ProductInventory.FindById(p))
}
);
_dbContext.Products.Add(newProduct);
_dbContext.SaveChanges();
// Approach B: Save base "Product" entity first, then grab the new ProductID,
// then fetch each "ProductInventory" from database and assign the foreign key with the new "ProductID" value, and then save each
_dbContext.Products.Add(newProduct);
var newProductID = _dbContext.SaveChanges();
productInventoryIDs.ToList().Foreach(pi =>
{
var existedProductInventoryFromDb = _dbContext.ProductInventory.FindById(pi);
existedProductInventoryFromDb.ProductID = newProductID;
_dbContext.SaveChanges();
}
);
}
By using approach (A), my newProduct failed to save and I looked into SQL resource, looks like it is trying to insert ProductInventory as well, although these ProductInventory already exist in the database. I guess that's because I add them into my base entity's collection?
By using approach (B), I am feeling a little awkward for doing that as it's like fetching and saving multiple times for just one object, I doubt if I am doing the correct way...
Maybe I am wrong at both approaches, so what would be the correct way to deal with above scenario?
Sorry, first let me say I did try to search this problem online and there are some but none specifically related to my scenario. I spent couple of hours and could not figure out.
I post my code below, stripping out all irrelevant information
First, here are my classes
public class StudentDm
{
public int Id { get; set; }
public virtual List<StudentParentDm> StudentParents { get; set; }
// other properties ...
}
// constructs a many to many relationship with some additional info in this model
public class StudentParentDm : EntityBaseDm
{
public int Id { get; set; }
public int StudentId { get; set; }
public virtual StudentDm Student { get; set; }
public int ParentId { get; set; }
public virtual ParentDm Parent { get; set; }
// other properties ...
}
public class ParentDm
{
public int Id { get; set; }
// other properties ...
}
Mappings:
public StudentMap()
{
HasMany(m => m.StudentParents).WithRequired().HasForeignKey(m => m.StudentId).WillCascadeOnDelete(false);
}
public StudentParentMap()
{
HasRequired(m => m.Student).WithMany().HasForeignKey(m => m.StudentId).WillCascadeOnDelete(false);
HasRequired(m => m.Parent).WithMany().HasForeignKey(m => m.ParentId).WillCascadeOnDelete(false);
}
public ParentMap()
{
HasMany(m => m.StudentParents).WithRequired().HasForeignKey(m => m.ParentId).WillCascadeOnDelete(false);
}
Then the code, here I am trying to create multiple new StudentParents, each with its own new Parent, to a student.
foreach (StudentParentDm studentParent in studentParents) // foreach new studentParent
{
StudentParentDm trackedStudentParent;
if (studentParent.Id == 0)
{
trackedStudentParent = new StudentParentDm
{
Parent = new ParentDm()
};
// map from studentParent to trackedStudentParent, including the Parent
// ...
trackedStudent.StudentParents.Add(trackedStudentParent);
} else
{
// unimportant
}
}
unitOfWork.Commit() // blows up with error message
Then I get this message:
Unable to determine the principal end of the 'Cobro.BusinessObjects.DatabaseContextServices.ParentDm_StudentParents' relationship. Multiple added entities may have the same primary key.
This only happens when I try to add more than 1 StudentParent at a time. I am not sure why the number of StudentParents would matter? I think I have the relationships set up correctly.
It also works with adding multiple StudentGrades, but the difference is that StudentGrade model is flat (does not have any child like StudentParent has a Parent)
Since nobody answered. Ill post what I discovered in case it can help anyone.
In StudentParentMap, add
m => m.Parent
such that
HasMany(m => m.StudentParents).WithRequired(m => m.Parent).HasForeignKey(m => m.ParentId).WillCascadeOnDelete(false);
Not sure why this mattered.. the mapping seemed sufficient enough such that the code-first generated database relationship did not change with this new addition. However, it was needed for EF to figure out how to configure FK during a multiple records adding transaction. It was pretty subtle for me.
I'm trying to update existing entities in my database.
I want to add a m:n relationship.
This is how I try to do it:
Get all artists from database
Identify artists that are not yet in the database
Save new artists
Get all genres
Insert ArtistGenre-relationship for all artists
Update artists
public void SyncArtists(ICollection<FullArtistWrapper> fullArtists)
{
using (var unitOfWork = UnitOfWorkFactory.CreateUnitOfWork())
{
var dbArtists = unitOfWork.Repository<IArtistRepository>().GetArtists().ToList();
var newArtists = fullArtists.Where(s => dbArtists.All(d => d.ArtistId != s.Id)).ToList();
var artistsToInsert = newArtists.Select(artist =>
new Artist
{
ArtistId = artist.Id,
Name = artist.Name
}).ToList();
unitOfWork.Repository<IArtistRepository>().InsertEntities(artistsToInsert);
unitOfWork.Commit();
// dbArtists.AddRange(artistsToInsert);
var allArtists = unitOfWork.Repository<IArtistRepository>().GetArtists().ToList();
var allGenres = unitOfWork.Repository<IGenreRepository>().GetGenres();
foreach (var artist in allArtists)
{
var fullArtist = fullArtists.Single(f => f.Id == artist.ArtistId);
var assignedDbGenres = allGenres.Where(g => fullArtist.Genres.Any(f => f == g.Name));
artist.Genres.AddRange(assignedDbGenres);
}
unitOfWork.Repository<IArtistRepository>().UpdateEntities(allArtists);
unitOfWork.Commit();
}
}
My Entities look like this:
public class Artist : PersistenceEntity
{
public Artist()
{
Genres = new List<Genre>();
}
[Key]
public string ArtistId { get; set; }
public string Name { get; set; }
public virtual ICollection<Genre> Genres { get; set; }
}
public class Genre : PersistenceEntity
{
[Key]
public int GenreId { get; set; }
public string Name { get; set; }
public virtual ICollection<Artist> Artist { get; set; }
}
The problem is the following:
I do have genre in the database (they get saved in an earlier step)
The artists are saved correctly, all artists are in the database
But the relationships don't get updated
Why is this the case?
My UpdateEntities method looks like this:
public void UpdateEntities<TPersistentEntity>(ICollection<TPersistentEntity> persistentEntitiesToBeUpdated) where TPersistentEntity : PersistenceEntity
{
persistentEntitiesToBeUpdated.ForEach(UpdateAndSave);
}
public void Update(PersistenceEntity entity)
{
Context.Entry(entity).State = EntityState.Modified;
}
public void UpdateAndSave(PersistenceEntity entity)
{
Update(entity);
Context.SaveChanges();
}
Why aren't my relationships inserted?
Thanks in advance
What is not very well visible in your code is that you cache the artists in a private member in your class. This means that EF will now know about these entities. The proper implementation of the unit of work pattern is the following:
Load an entity from EF
Modify this instance
Save the instance
If you manage instances outside, EF will have a different instance associated, and it will not find any changes. the code you write marking the entity manually as modified is not needed, as EF does its own change tracking. Even if you put the state manually to modified, EF will just rescan the entity it has associated (not equal to the one you have actually modified) and will not find any changes.
The main question is why you cache these artists outside of EF. This is not good practice, in general you have more than one server / client in use, and the DB can be updated by anyone. These cached instances would never know about this. I would completely get rid of such a situation and reload entities always from EF. Then you ensure that you always get the latest state no matter who has updated the DB.
I developed and uploaded a web service to Azure using Entity Framework 6.1.3 with MVC design pattern.
So let's imagine I have a Workshop that can have many Clients and a Client that can have many Workshops.
So far my results have been null, empty values and some times correct values but without the relationship (no clients inside my workshop, and the other way around).
This is what I have at this point:
public class Workshop
{
public Workshop()
{
this.Clients = new HashSet<Client>();
this.ModuleSets = new HashSet<ModuleSet>();
}
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Client> Clients { get; set; }
public virtual ICollection<ModuleSet> ModuleSets { get; set; }
}
public class Client
{
public Client()
{
this.Workshops = new HashSet<Workshop>();
this.Vehicles = new HashSet<Vehicle>();
}
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Workshop> Workshops { get; set; }
public virtual ICollection<Vehicle> Vehicles { get; set; }
}
Yes I have more relations going on at the same time.
Since that alone was not giving me anything, I added some Fluent Api, like this:
modelBuilder.Entity<Workshop>().
HasMany(c => c.Clients).
WithMany(p => p.Workshops).
Map(
m =>
{
m.MapLeftKey("Workshop_Id");
m.MapRightKey("Client_Id");
m.ToTable("WorkshopClients");
});
The names that are shown are the ones that are in the table WorkshopClients (auto generated by entity framework).
I also read this article to make sure I was doing the right thing when it came to Fluent API.
How to define Many-to-Many relationship through Fluent API Entity Framework?
And this is my simple request on the client:
var request = new RestRequest("api/Workshops") { Method = Method.GET };
var workshopList = await api.ExecuteAsync<List<Workshop>>(request);
API/Workshops method:
// GET: api/Workshops
public IQueryable<Workshop> GetWorkshops()
{
return db.Workshops;
}
It looks like you are not using lazy loading or that part is lost when you pass the data over the API. Make sure you tell your API to include the child objects:
public IQueryable<Workshop> GetWorkshops()
{
return db.Workshops
.Include(w => w.Clients);
}
Note: You may need to add using System.Data.Entity; to use the lambda version of Include, otherwise you can use the string version.
I would recommend keeping your mappings in a separate mapping file, but if you are going to do it in the OnModelCreating method then this should do what you need.
modelBuilder.Entity<Workshop>()
.HasRequired(c => c.Clients) //or HasOptional depending on your setup
.WithMany(d => d.Workshop)
.HasForeignKey(d => new { d.ID });
modelBuilder.Entity<Clients>()
.HasRequired(c => c.Workshop) //same
.WithMany(d => d.Clients)
.HasForeignKey(d => new { d.ID });
Also in both entities add this to your ID properties:
[Key]
public int Id { get; set; }
I'm using Entity Framework 4.3.1 Code-First and I need to split an entity between two tables. The tables have a primary key shared, and it is 1-to-1, but the columns are not named the same on each table.
I don't control the data layout, nor can I request any changes.
So for example, the SQL tables could be
And this would be my entity...
public class MyEntity
{
public int Id {get; set;}
public string Name {get;set}
public string FromAnotherTable {get;set;}
}
And here is the mapping I have.
public class MyEntityMapping : EntityTypeConfiguration<MyEntity>
{
public MyEntityMapping()
{
this.Property(e => e.Id).HasColumnName("ThePrimaryKeyId");
this.Property(e => e.Name).HasColumnName("MyDatabaseName");
this.Property(e => e.FromAnothertable).HasColumnName("AnotherTableColumn");
this.Map(m =>
{
m.Properties(e =>
{
e.Id,
e.Name
});
m.ToTable("MainTable");
});
this.Map(m =>
{
m.Properties(e =>
{
e.Id,
e.FromAnotherTable
});
m.ToTable("ExtendedTable");
});
}
Since the key shared between them has a different column name, I'm not sure how to map it. This mapping will compile, but fails at runtime because EF emits SQL looking for the "ThePrimaryKeyId" column on the "ExtendedTable" table, which doesn't exist.
EDIT
To clarify, what I have defined above can (and does) work if the PK on the "ExtendedTable" followed naming conventions. But it doesn't and I can't change the schema.
Basically, what I need EF to emit is a SQL statement like
SELECT
[e1].*, /*yes, wildcards are bad. doing it here for brevity*/
[e2].*
FROM [MainTable] AS [e1]
INNER JOIN [ExtendedTable] AS [e2] /*Could be left join, don't care. */
ON [e1].[ThePrimaryKeyId] = [e2].[NotTheSameName]
But the only thing it seems to want to emit is
SELECT
[e1].*,
[e2].*
FROM [MainTable] AS [e1]
INNER JOIN [ExtendedTable] AS [e2]
ON [e1].[ThePrimaryKeyId] = [e2].[ThePrimaryKeyId] /* this column doesn't exist */
Edit
I tried the 1-to-1 approach again at NSGaga's suggestion. It didn't work, but here are the results.
Entities
public class MyEntity
{
public int Id { get; set; }
public int Name { get; set; }
public virtual ExtEntity ExtendedProperties { get; set; }
}
public class ExtEntity
{
public int Id { get; set; }
public string AnotherTableColumn { get; set; }
public virtual MyEntity MainEntry { get; set; }
}
Here are the mapping classes
public class MyEntityMapping : EntityTypeConfiguration<MyEntity>
{
public MyEntityMapping()
{
this.Property(e => e.Id).HasColumnName("ThePrimaryKeyId");
this.Property(e => e.Name).HasColumnName("MyDatabaseName");
this.ToTable("MainTable");
this.HasKey(e => e.Id);
this.HasRequired(e => e.ExtendedProperties).WithRequiredPrincipal(f => f.MainEntry);
}
}
public class ExtEntityMapping : EntityTypeConfiguration<ExtEntity>
{
public ExtEntityMapping()
{
this.Property(e => e.Id).HasColumnName("NotTheSameName");
this.Property(e => e.AnotherTableColumn).HasColumnName("AnotherTableColumn");
this.ToTable("ExtendedTable");
this.HasKey(e => e.Id);
this.HasRequired(e => e.MainEntry).WithRequiredDependent(f => f.ExtendedProperties);
}
}
This setup gets the message
"Column or attribute 'MyEntity_ThePrimaryKeyId' is not defined in 'ExtendedTable'"
Changing the final map line to
this.HasRequired(e => e.MainEntry).WithRequiredDependent(f => f.ExtendedProperties).Map(m => M.MapKey("NotTheSameName"));
Returns this message
"Each property name in a type must be unique. property name 'NotTheSameName' was already defined."
Changing the mapped key to use the column from the parent table, MapKey("ThePrimaryKeyId"). returns this message
"Column or attribute 'ThePrimaryKeyId' is not defined in 'ExtendedTable'"
Removing the Id property from the ExtEntity class throws an error because then the entity doesn't have a defined key.
I have been working on this very issue for a few days, what I finally did was to set the column name of the Id field within the context of the mapping fragment. This way you can give the Id (or the foreign key dependent on the Id) a different name from the Id of the main table.
this.Map(m =>
{
m.Property(p => p.Id).HasColumnName("NotTheSameName");
m.Properties(e =>
{
e.Id,
e.FromAnotherTable
});
m.ToTable("ExtendedTable");
});
If you run and debug this, you would find that it would give you something like what you want:
[e1].[ThePrimaryKeyId] = [e2].[NotTheSameName]
I can't find anything that specifically states that the name of the column has to be the same in both tables; but neither can I find anything that says it doesn't, or explains how you would map that scenario. Every example I can find has the key with the same name in both tables. It looks to me like this is a hole in the DbContext design.
Move the HasColumnName to within the mapping:
this.Property(e => e.FromAnothertable).HasColumnName("AnotherTableColumn");
this.Map(m =>
{
m.Properties(e => new
{
e.Id,
e.Name
});
m.Property(e => e.Id).HasColumnName("ThePrimaryKeyId");
m.Property(e => e.Name).HasColumnName("MyDatabaseName");
m.Property(e => e.Id).HasColumnName("ThePrimaryKeyId");
m.ToTable("MainTable");
});
this.Map(m =>
{
m.Properties(e => new
{
e.Id,
e.FromAnotherTable
});
m.ToTable("ExtendedTable");
});
}
No Visual Studio here, but try this with the 1-to-1 approach:
this.HasRequired(e => e.ExtendedProperties).HasConstraint((e, m) => e.Id == m.Id);
Update:
Here are some links that might help (could not find a real reference link)
How to declare one to one relationship using Entity Framework 4 Code First (POCO)
Entity Framework 4 CTP 4 Code First: how to work with unconventional primary and foreign key names
And just to provide (as I promised) a 1-to-1 (two entities, two tables) mapping, for what it's worth.
Here is what works for me and should in your case...
public class MainTable
{
public int ThePrimaryKeyId { get; set; }
public string Name { get; set; }
}
public class ExtendedTable
{
public int NotTheSameNameID { get; set; }
public string AnotherTableColumn { get; set; }
public MainTable MainEntry { get; set; }
}
public class MainDbContext : DbContext
{
public DbSet<MainTable> MainEntries { get; set; }
public DbSet<ExtendedTable> ExtendedEntries { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MainTable>()
.HasKey(x => new { x.ThePrimaryKeyId });
modelBuilder.Entity<ExtendedTable>()
.HasKey(x => new { x.NotTheSameNameID });
// Extended To Main 1 on 1
modelBuilder.Entity<ExtendedTable>()
.HasRequired(i => i.MainEntry)
.WithRequiredDependent();
}
}
...and a test code something like...
using (var db = new UserDbContext())
{
foreach (var userid in Enumerable.Range(1, 100))
{
var main = new MainTable { Name = "Main" + userid };
db.MainEntries.Add(main);
var extended = new ExtendedTable { AnotherTableColumn = "Extended" + userid, MainEntry = main };
db.ExtendedEntries.Add(extended);
}
int recordsAffected = db.SaveChanges();
foreach (var main in db.MainEntries)
Console.WriteLine("{0}, {1}", main.Name, main.ThePrimaryKeyId);
foreach (var extended in db.ExtendedEntries)
Console.WriteLine("{0}, {1}, {2}, {3}", extended.AnotherTableColumn, extended.NotTheSameNameID, extended.MainEntry.Name, extended.MainEntry.ThePrimaryKeyId);
}
That creates the following SQL script, tables...
CREATE TABLE [MainTables] (
[ThePrimaryKeyId] [int] NOT NULL IDENTITY,
[Name] [nvarchar](4000),
CONSTRAINT [PK_MainTables] PRIMARY KEY ([ThePrimaryKeyId])
)
CREATE TABLE [ExtendedTables] (
[NotTheSameNameID] [int] NOT NULL,
[AnotherTableColumn] [nvarchar](4000),
CONSTRAINT [PK_ExtendedTables] PRIMARY KEY ([NotTheSameNameID])
)
CREATE INDEX [IX_NotTheSameNameID] ON [ExtendedTables]([NotTheSameNameID])
ALTER TABLE [ExtendedTables] ADD CONSTRAINT [FK_ExtendedTables_MainTables_NotTheSameNameID] FOREIGN KEY ([NotTheSameNameID]) REFERENCES [MainTables] ([ThePrimaryKeyId])
And a note, as per our discussion above...
This ain't the 'splitting' - but
(a) code first IMO doesn't allow anything like that (I tried that first and also modifying the migrations manually but it's 'internally' all based on the expected column names being the same and there seems to be no way around it, for this version of EF at least.
(b) table structure wise - the tables could be made to look exactly what you need (as I said before I used it to relate the existing aspnet membership tables (which I could not change) into my user-table which has an own user-id pointing to outside/aspnet table and id.
True, you cannot make it using one C# model class - but the C# side is much more flexible and if you can control the C# that should give the same effect, to my opinion at least (like in the test, you can access it always through the extended entity, both extended and the main columns and they're always matched 1 to 1 and stay 'in sync'.
Hope this helps some
NOTE: you don't have to worry about the fk id etc. - just always access and add the Main entry via MainEntry, and id-s will be fine.
EDIT:
You could also do the following, to gain the appearance of having to deal with just one class (i.e. sort of a split)
public class ExtendedTable
{
public int NotTheSameNameID { get; set; }
public string AnotherTableColumn { get; set; }
public string Name { get { return MainEntry.Name; } set { MainEntry.Name = value; } }
// public int MainID { get { return MainEntry.ThePrimaryKeyId; } set { MainEntry.ThePrimaryKeyId = value; } }
internal MainTable MainEntry { get; set; }
public ExtendedTable()
{
this.MainEntry = new MainTable();
}
}
...and use it like this...
var extended = new ExtendedTable { AnotherTableColumn = "Extended" + userid, Name = "Main" + userid };
...also you can revert the direction of the fk by doing the WithRequiredPrincipal instead of dependent.
(also all references have to be w/o 'virtual' if you have required one-to-one)
(and MainTable can be made 'internal' as it's here, so it's not visible from outside - it cannot be nested as that EF doesn't allow - is treated like NotMapped)
...well, that's the best I could do:)
I would like to suggest using some data annotations like this:
MainTable
---------
MainTableId
DatabaseName
ExtendedTable
----------
NotTheSameName
AnotherColumn
public class MainTable
{
[Key]
public int MainTableId { get; set; }
public string DatabaseName { get; set; }
[InverseProperty("MainTable")]
public virtual ExtendedTable ExtendedTable { get; set; }
}
public class ExtendedTable
{
[Key]
public int NotTheSameName { get; set; }
public string AnotherColumn { get; set; }
[ForeignKey("NotTheSameName")]
public virtual MainTable MainTable { get; set; }
}
Looks like it's been fixed in Entity Framework 6. See this issue http://entityframework.codeplex.com/workitem/388
I faced this issue, and solved by add Column attribute to match the both column names.
[Key]
[Column("Id")]
public int GroupId { get; set; }