I'm having troubles with creating and/or storing m:n relationship with EF 4.3 Code first
So the first entity Publication is defined as with some other internal scalar properties:
public class Publication : IDataErrorInfo{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PublicationId { get; set; }
[InverseProperty("Publications")]
public virtual ICollection<Group> Groups { get; set; }
and the other class in the same way:
public class Group : IDataErrorInfo {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int GroupId { get; set; }
[InverseProperty("Groups")]
public ICollection<Publication> Publications { get; set; }
which according to numerous articles should be fine.
There are several problems I occur. AT first:
If I create a new publication and assert it some Groups. All is stored to the db. But then I restart the program, the same particular publication has ICollection set to null. Therefore the information about relationship with Group has been deleted.I don't know why :(
When I try to update exisiting Publication entry with Group relationship, the DBUpdateException is thrown with the following text:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types
In the inner exception is the same, and in the inner exception of this one is the following:
Violation of PRIMARY KEY constraint 'PK_Publicat_3AF5D6A10AD2A005'. Cannot insert duplicate key in object 'dbo.PublicationGroups'.
The statement has been terminated.
I'm asserting the the new values of the Publication as follows:
var entry = db.Publications.First(a => a.PublicationId == publKey);
entry.Groups = db.Groups.
Where(a => groupKeys.Contains(a.GroupId)).
Select(b => b).
ToList();
where publKey is the key of the edited entity and groupKeys is the List of GroupId which should be the publication be related to.
after calling db.SaveContext() the exception is thrown
This topic has been covered by numerous articles, but I didn't find any solution. All of the examples are using the same code, but apparently I'm missing something. I'm using SQL Ce 4.0 as the persistance data storage.
Thank you guys for answer, I'm dealing with it since yesterday, but don't why this happens
Are you calling .Save after you've added the groups to the database? Otherwise when you try to add the groups to the publication, they won't actually be in the DbSet yet. The following code works for me - perhaps you could clarify how it differs from yours?
public class Publication
{
public int PublicationId { get; set; }
public string PublicationName { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
public class Group
{
public int GroupId { get; set; }
public string GroupName { get; set; }
public ICollection<Publication> Publications { get; set; }
}
public class Context : DbContext
{
public DbSet<Publication> Publications { get; set; }
public DbSet<Group> Groups { get; set; }
}
class Program
{
static void Main(string[] args)
{
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context>());
Context context = new Context();
// Only add the groups if it's a new database
if (!context.Groups.Any())
{
context.Groups.Add(new Group { GroupName = "Group 1" });
context.Groups.Add(new Group { GroupName = "Group 2" });
context.SaveChanges();
}
if (context.Publications.Any())
{
Console.WriteLine("At startup, P1 is in groups " + String.Join(", ", context.Publications.First().Groups.Select(g => g.GroupName)));
}
// Add publication
Publication p;
p = new Publication();
p.Groups = context.Groups.ToList(); // Add to all existing groups
context.Publications.Add(p);
context.SaveChanges();
Console.WriteLine("P1 is in groups " + String.Join(", ", context.Publications.First().Groups.Select(g => g.GroupName)));
}
}
(Code Updated to use SqlCe provider)
Related
My first question - be kind :-).
In the code below, I am attempting to reference an "Include(d)" entity (Schedules) to obtain its Name property. In EF 6, both "schedule" and "schedule_2" return the correct value of Name. In EF Core, "schedule" returns NULL and "schedule_2" returns the correct value of Name.
I do not understand why I should have to load the "schedules" List. Shouldn't the .Include force an Eager Load of the Schedules for each Election such that each Election Schedule's Name property would be available for the "schedule" assignment?
// Relevant Model entities in database
// DbSet<Election> Elections { get; set; }
//
// The following are the related classes defined in the database context...
public class Election
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Schedule> Schedules { get; set; }
}
public class Schedule
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public int? CfsElectionId { get; set; }
public string Name { get; set; }
[Required] // sets cascade delete
[ForeignKey("CFSElectionID")]
public virtual Election Election { get; set; }
}
class Program
{
static void Main()
{
var db = new FfmsDbContext();
var elections = db.Elections
.Include(i => i.Schedules)
.ToList();
//The following returns NULL?
var schedule = elections.First().Schedules?.First().Name ?? "NULL";
var schedules = db.Schedules
.ToList();
//The following returns the correct Name property?
var schedule_2 = elections.First().Schedules?.First().Name ?? "NULL";
Console.WriteLine($#"sched: {schedule}");
Console.WriteLine($#"schedules.First().Name: {schedules.First().Name}");
Console.WriteLine($#"sched2: {schedule_2}");
Console.WriteLine("Done...");
Console.ReadLine();
}
}
/*
Output...
sched: NULL
schedules.First().Name: Candidates
sched2: Candidates
Done...
*/
Turns out that my problem ended up being in the References of the Class.
I had accidentally chosen System.Data.Entity as the offered choice for .Include.
The correct reference should have been Microsoft.EntityFrameworkCore.
Once I adjusted the reference, the .Include worked as desired.
I got stuck with creating mapping for two entities that have to be mapped to the same database table. The reason why I need it is to create lite entity and full entity that will be used in different cases. I have already had existing database and I have my own database context that is derived from DbContext class. All mappings were made by using EntityTypeConfiguration<T> classes.
The table.
[PROCESS]
ProcessId <int> PK
Name <nvarchar>
StartDate <datetime>
ProcessSequency <int>
Limit <int>
OwnerName <nvarchar>
Now this table is mapped to the single domain model, but I want to change it.
I want to split this entity to the next two.
public class ProcessEntity
{
public int ProcessId { get; set; }
public int Name { get; set; }
public virtual ProcessDetailsEntity ProcessDetails { get; set; }
}
public class ProcessDetailsEntity
{
public int ProcessId { get; set; }
public DateTime StartDate { get; set; }
public int ProcessSequency { get; set; }
public int Limit { get; set; }
public string OwnerName { get; set; }
public virtual ProcessEntity ProcessBase { get;set; }
}
I created two configuration classes for them.
public class ProcessEntityConfiguration : EntityTypeConfiguration<ProcessEntity>
{
public ProcessEntityConfiguration()
{
// ToTable("PROCESS");
Map(m => m.ToTable("PROCESS"))
HasKey(t => t.ProcessId);
HasRequired(s => s.ProcessDetails).WithRequiredPrincipal(t => t.ProcessBase);
}
}
public class ProcessDetailsEntityConfiguration : EntityTypeConfiguration<ProcessDetailsEntity>
{
public ProcessDetailsEntityConfiguration()
{
// ToTable("PROCESS");
HasKey(t => t.ProcessId);
Map(m => m.ToTable("PROCESS"));
}
}
Everything looks fine and should work, but I see the error:
"InnerException": {
"Message": "An error has occurred.",
"ExceptionMessage": "Invalid column name 'Discriminator'.\r\nInvalid column name 'Discriminator'.",
"ExceptionType": "System.Data.SqlClient.SqlException",
"StackTrace": " at System.Data.SqlClient.SqlConnection.OnError(SqlException exception,
Boolean breakConnection, Action`1 wrapCloseInAction)
I googled it and found out that the Discriminator is used by Entity Framework in order to apply TPH - Table per hierarchy behavior. I also found one useful article that could be a solution for me: Entity Framework - Advanced mapping scenarios. Unfortunately the solution that is provided under Mapping a single table to multiple entities header doesn't work for me and I don't know why. Could you help me with this issue?
I'm a little bit familiar with NHibernate, so I tried to do it in NHibernate way. The idea is to create two independent entities. It was my first approach. Here is the code of this attempt.
public abstract class ProcessBaseEntity
{
public int ProcessId { get; set; }
public int Name { get; set; }
}
public class ProcessEntity : ProcessBaseEntity
{
}
public class ProcessDetailsEntity : ProcessBaseEntity
{
public DateTime StartDate { get; set; }
public int ProcessSequency { get; set; }
public int Limit { get; set; }
public string OwnerName { get; set; }
}
public class ProcessEntityConfiguration : EntityTypeConfiguration<ProcessEntity>
{
public ProcessEntityConfiguration()
{
ToTable("PROCESS");
HasKey(t => t.ProcessId);
}
}
public class ProcessDetailsEntityConfiguration : EntityTypeConfiguration<ProcessDetailsEntity>
{
public ProcessDetailsEntityConfiguration()
{
ToTable("PROCESS");
HasKey(t => t.ProcessId);
}
}
Unfortunately this attempt wasn't successful. The following exception was thrown:
The entity types 'ProcessEntity' and 'ProcessDetailsEntity' cannot
share table 'PROCESS' because they are not in the same type hierarchy
or do not have a valid one to one foreign key relationship with
matching primary keys between them.
I started splitting my first entity into two small as I want to make my SQL query as lighter as possible. Sometimes I wonder if it essential or not? Maybe my request won't be lighter even I spitted my entity. Maybe it worth trying to put all my properties in a single entity and forget about it.
I'm currently using MVC with EF to have a small server with API querying a SQL database. But in the API reply I'm not able to hide some parameters.
The main object
public class AssetItem
{
[Key]
public Int32 AssetId { get; set; }
public String AssetName { get; set; }
public int OdForeignKey { get; set; }
[ForeignKey("OdForeignKey")]
public OperationalDataItem OperationalDataItem { get; set; }
}
The other one:
public class OperationalDataItem
{
[Key]
public Int32 OperationalDataId { get; set; }
public String Comunity { get; set; }
public List<AssetItem> AssetItems { get; set; }
}
From what I have read, this should be ok, I have also set the context:
public AssetContext(DbContextOptions<AssetContext> options) : base(options)
{}
public DbSet<AssetItem> AssetItems { get; set; }
public DbSet<OperationalDataItem> OperationalDataItems { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AssetItem>().HasOne(p =>
p.OperationalDataItem).WithMany(b => b.AssetItems).HasForeignKey(p =>
p.OdForeignKey);
}
And the seeding in program.cs
context.AssetItems.Add(
new AssetItem { AssetName = "Test test", OdForeignKey = 1,
OperationalDataItem =
new OperationalDataItem {Comunity = "Comunity1" }});
So calling the API this results in:
{ "assetId":3,
"assetName":"Test test",
"odForeignKey":1,
"operationalDataItem":null }
From what I read this is because of the lazy loading, how can I hide the result operationalDataItem?
In case is not possible i have of course try to query for it and give it back and it give something like:
{ "assetId":3,
"assetName":"Test test",
"odForeignKey":1,
"operationalDataItem":
{ "operationalDataId":1,
"comunity":"Comunity1",
"assetItems":[
But in this case I would like to hide "assetsItems" in the reply to the FE.
How can I hide those parameters?
The API is quite simple, just an example code:
var todoItem = await _context.AssetItems.FindAsync((Int32)id);
var item = _context.OperationalDataItems.Find((Int32)todoItem.OdForeignKey);
todoItem.OperationalDataItem = item;
return todoItem
If you want to fetch data from the database, but you only want to fetch some properties, use Select. Usually this is more efficient than using Find, because you'll only transfer the data that you actually plan to use.
To fetch some properties of the assetItem that has primary key assetItemId:
var result = dbContext.AssetItems
.Where(assetItem => assetItem.AssetItmId = assetItemId)
.Select(assetItem => new
{
// Select only the properties that you plan to use
Id = assetItem.AssertItemId,
Name = assetItem.Name,
OperationalData = new
{
// again, select only the properties that you plan to use
Id = assetItem.OperationalData.OperationalDataId,
Community = assetItem.OperationalData.Community,
},
})
.FirstOrDefault();
Or the other way round:
Fetch several properties of all (or some) OperationalDataItems, each with some properties of all (or some) of its AssetItems:
var result = dbContext.OperqationalDataItems
.Where(operationalDataItem => ...) // only if you don't want all
.Select(operationalDataItem => new
{
Id = operationalDataItem.Id,
Community = operationalDataItem.Community
AssetItems = operationalDataItem.AssetItems
.Where(assetItem => ...) // only if you don't want all its assetItems
.Select(assetItem => new
{
// Select only the properties you plan to use:
Id = assetItem.Id,
...
// not useful: you know the value of the foreign key:
// OperationalDataId = assetItem.OperationalDataId,
})
.ToList();
})
.ToList(); // or: FirstOrDefault if you expect only one element
Entity framework knows your one-to-many relation and is smart enough to know which (group-)join is needed for your query.
Some side remarks
You've declare your many-relation a List<AssetItem>. Are you sure that operationalDataItem.AssetItems[4] has a defined meaning? Wouldn't it be better to stick to the entity framework code first conventions? This would also eliminate the need for most attributes and / or fluent API
public class OperationalDataItem
{
public int Id { get; set; }
public String Comunity { get; set; }
...
// Every OperationalDataItem has zero or more AssetItems (one-to-many)
public virtual ICollection<AssetItem> AssetItems { get; set; }
}
public class AssetItem
{
public int Id { get; set; }
public String Name { get; set; }
...
// every AssetItem belongs to exactly one OperationalDataItem, using foreign key
public int OperationDataItemId { get; set; }
public virtual OperationalDataItem OperationalDataItem { get; set; }
}
In entity framework the columns of a table are represented by the non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many)
Because I stuck to the conventions, no attributes nor fluent API is needed. Entity framework is able to detect the one-to-many relation and the primary and foreign keys. Only if I am not satisfied with the names or the types of the columns I would need fluent API.
I am trying to insert an object into a database table with Entity Framework and using code first (fluent api). Whilst doing this I keep running into one of the following errors:
1) InvalidOperationException: A dependent property in a
ReferentialConstraint is mapped to a store-generated column. Column:
'Id'
2) Cannot insert value into identity column with IDENTITY_INSERT set
to OFF
My relationship is a one-to-one however perhaps I can rework or structure the database to accomplish what I am wanting. I have also thought about utilizing a one to zero or zone even though the other object will always be required.
So I have the following database tables mapped into these C# objects (with virtual for the mapping):
public class test
{
[Key]
public long Id { get; set; }
public DateTime ResultDate { get; set; }
public virtual test_additional test_additional { get; set; }
public virtual test_status test_status { get; set; }
}
public class test_additional
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long TestId { get; set; } //Foreign Key to test
...
public virtual test test { get; set; }
}
public class test_status {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long TestId { get; set; } //Foreign Key to Test
public long TestFormId { get; set; } //this is the object I want to insert, Foreign key to the Primary key of test_form
...
public virtual test test { get; set; }
public virtual test_form test_form { get; set; } //object mapping
}
public class test_form {
[Key]
public long Id { get; set; } //Primary Key
public string FileName { get; set; }
public virtual test_status test_status { get; set; }
}
So some pretty simple objects, I've stripped members/columns that are necessary for the functionality for ease of readability.
So there are test objects that have an optional test_additional or test_status .
These are generated with a one to zero-or-one relationship. Which are working fine and I have the relationship defined as:
modelBuilder.Entity<test>()
.HasOptional(e => e.test_additional)
.WithRequired(e =>e.test);
modelBuilder.Entity<test>()
.HasOptional(e => e.test_status)
.WithRequired(e => e.test);
Now the entity I am having trouble with is the test_form, if a test_status is defined there should always be a test_form associated with that. I currently have a relationship defined as:
modelBuilder.Entity<test_form>()
.HasRequired(e => e.test_status)
.WithRequiredDependent(e => e.test_form);
In addition I have tried appending this config:
modelBuilder.Entity<test_status>()
.HasKey(e => e.TestFormId);
--
Here is a simple implementation of inserting this object in the database:
try {
test UserTest = new test { ResultDate = DateTime.Now; }
UOW.test.Insert(UserTest);
UOW.Save();
test_additional ta = new test_additional { TestId = UserTest.Id; }
test_form tf = new test_form { FileName = "Testing.pdf"; }
UOW.test_additional.Insert( ta );
UOW.test_form.Insert( tf );
UOW.Save(); //This is where it will throw that error.
test_status status = new test_status {
TestId = UserTest.Id;
TestFormId = tf.Id;
}
UOW.test_status.Insert( status );
UOW.Save();
} catch {
throw;
}
--
I have used BreakPoints before the Unit of Work saves and I can confirm that the Id in the test_form object is the default of long which is 0. So I am not setting the Identity Column explicitly. Upon removing of test_form (in the implemented method) I can insert into the test_additional category and save with no issue.
So my question is really... are my entity relationships defined correctly? Would it be smarter to use an additional One to Zero-or-One for the test_form object? Why can I not insert this simple object into my database?
I have also thought about defining the virtual test_form object in test_status as an ICollection, then I could use .HasMany(e => e.test_form).HasForeignKey(e => e.TestFormId); so it would bind to the Foreign Key even though I would only be using 1 item for the test_status.
Opinions? Am I close?
Thanks again for taking the time to read my question!
i had your problem. just do delete your database and migration files. after do it add the new migration to create the new database.
I have a one-to-many relationship where I am trying to delete a set of instances on the "many" side, but keep getting the exception "Adding a relationship with an entity which is in the Deleted state is not allowed". This is Entity Framework 6.1.1.
The relationship is one-to-many from Teacher to Course. The two classes are defined as:
[Table("Course")]
public partial class Course {
public int Id { get; set; }
public int? TeacherId { get; set; }
public virtual Teacher Teacher { get; set; }
}
[Table("Teacher")]
public partial class Teacher
{
public Teacher()
{
Course = new HashSet<Course>();
}
public int Id { get; set; }
[Required]
[StringLength(50)]
public string TeacherName { get; set; }
public virtual ICollection<Course> Course { get; set; }
}
The code that tries to delete the courses, is part of an import: A set of courses is coming in, and the courses that are in the database but not part of the incoming courses, should be deleted from the database. (In addition, the courses that are part of the incoming set but not in the database, should be created, but this seems to work).
var existingCourses = ctx.Courses.ToList();
var toCreate = incomingCourses.Where(x => !existingCourses.Contains(x)).ToList();
var coursesToDelete = existingCourses.Where(x => !incomingCourses.Contains(x)).ToList();
ctx.Courses.RemoveRange(coursesToDelete);
ctx.SaveChanges(); // The exception occurs here
The incoming set of courses are parsed from an XML file to a DTO. Before comparing them to the existing courses, they are placed in a list as:
var incomingCourses = incomingDtos.Select(x => new Course
{
Teacher = new Teacher { TeacherName = x.TeacherNameFromXml }
}.ToList();
There are other properties on the Course entity that identifies the course, but I have not shown them here as I suppose they are irrelevant.
When debugging, I noticed that the Teacher property of the Courses that are being deleted are non-empty before the call to RemoveRange() but null afterwards.. So it seems that there is some kind of cascade delete taking place.
I have tried to remove all cascade deletes via my DbContext and also specifying the relationship there. This changes nothing.
public class MyDbContext : DbContext {
public MyDbContext() {}
public MyDbContext(string connectionString) {
Database.Connection.ConnectionString = connectionString;
}
public virtual DbSet<Teacher> Teacher { get; set; }
public virtual DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<Teacher>()
.HasMany(x => x.Course)
.WithOptional(x => x.Teacher)
.HasForeignKey(x => x.TeacherId)
.WillCascadeOnDelete(false);
}
}
The comment from #LukasKabrt pointed me in the right direction. Instead of comparing the Course objects directly, I convert the existing courses to DTOs "temporarily" inside the LINQ and compare DTOs. Now I am able to delete all from coursesToDelete
var coursesToDelete = (
from e in existing
let dto = new CourseDto(e)
where !incomingDtos.Contains(dto)
select e).ToList();