I have the following classes
public class Lookup
{
public int Id{get;set;}
public string Name{get;set;}
public int Order{get;set;}
}
public class CatalogType:Lookup // this was added on Add-migration "Second"
{
}
public class Catalog:Lookup
{
public int CatalogTypeId{get;set;} // this was added on add-migration "Second"
public CatalogType CatalogType{get;set;}
}
and I already have data in the database in the table Lookups that represent group of lookup classes like gender, marital statuses, catalog, etc. and the Lookups table contains a row with Name="General" that was used by Catalog(i.e Discriminator field="Catalog")
in the Configuration file inside the Seed function I wrote this code
context.Lookups.AddOrUpdate(p => new { p.Name, **p.GetType().FullName** },
new CatalogType
{
Name = "General",
IsActive = true,
Order = 1,
},
new CatalogType
{
Name = "Custom",
IsActive = true,
Order = 2,
});
context.SaveChanges();
My problem: I tried first context.Lookups.AddOrUpdate(p=>p.Name) and when I try to make update-database, the migration fails "sequence contains more than one element"
Then I tried to use p.GetType().Name the error was:
An anonymous type cannot have multiple properties with the same name.
Then I tried to use p.GetType().FullName and upon executing the update-database command, I got the following error:
The properties expression 'p => new <>f__AnonymousType18`2(Name =
p.Name, FullName = p.GetType().FullName)' is not valid. The expression
should represent a property: C#: 't => t.MyProperty' VB.Net:
'Function(t) t.MyProperty'. When specifying multiple properties use an
anonymous type: C#: 't => new { t.MyProperty1, t.MyProperty2 }'
VB.Net: 'Function(t) New With { t.MyProperty1, t.MyProperty2 }'.
I know that the problem caused because Lookups table contains already the Name="General" but how to tell the EntityFramework to take the column discriminator into consideration while trying to AddOrUpdate method?
in other words, i might have same data for 2 different objects and i want to add data on adding migration, how to achieve this if i have for example red car, red door and i want to add red apple for example? it will not allow me in my current situation, how to solve this issue?
Hope my explanation for this problem was clear.
try this :
//but two lines below in "OnModelCreating" method in your Context
modelBuilder.Entity<Lookup>().Map<Catalog>(m => m.Requires("IsCatalog").HasValue(true));
modelBuilder.Entity<Lookup>().Map<CatalogType>(m =>m.Requires("IsCatalog").HasValue(false));
// then :
context.Lookups.AddOrUpdate(p => new { p.Name , p.IsCatalog},
new CatalogType
{
Name = "General",
IsActive = true,
Order = 1,
},
new CatalogType
{
Name = "Custom",
IsActive = true,
Order = 2,
});
//context.SaveChanges(); //if you used base.OnModelCreating(modelBuilder);
base.OnModelCreating(modelBuilder); // then you don't need to save
after searching about this subject i came to this result
TPH will work with discriminator field to distinguish between derived classes
TPC does not depend on discrimintor field because the primary key of the inherited class is the same primary key of the derived class
when trying to add the data to the Catalog and i am putting constraint ( if Name repeated then make update else create), the EF failed to set the discriminator='Catalog' since it is TPC so the update will fail because table contains other data 'General'
when trying to add mapping conditions, this is not allowed by EF to use same inherited class for TPC and TPH at the same time.
hope this will help others fell in the same problem like me
Related
Why does Entity Framework add a related record even if it already exists in the database? It should just update the junction table in this case)?
I am working with Entity Framework 6, I have a many-to-many relationship between Directors and Movies. I want to post a new movie using a Dto called MovieUpdateDto. Movie class has a property of type ICollection<Directors> Directors.
The json that represents the Dto looks like this:
{
"Name": "new movie",
"Description": "new movie Description",
"Price": 1000.0,
"Directors": [{"Id": 11, "Name": "Coco"}, {"Id": "12", "Name": "Jambo"}]
}
The problem with the Post is that it doesn't work as expected when inserting a movie with one or more existing directors (that have already been added to the db). In such cases, those directors are readded to the db.
As a side note, EF implicitly adds the directors as new records in the Directors table when I add the movies dto with directors information, and also implicitly takes care of the junction table MoviesDirectors.
Here is my first attempt:
// POST: api/movies
public void Post([FromBody] MovieUpdateDto movie)
{
var bookToInsert = Mapper.Map<Movie>(movie);
foreach (var director in movieToInsert.Directors)
{
var existingDirector = _unitOfWork.Directors.Find(d => d.Id == director.Id);
if (existingDirector.Any())
{
// don't add director again to the database, but do add (directorId, bookId) in the MoviesDirectors junction table.
// How do I reference the junction table though? Should this work somehow without referencing the juntion table?
}
}
_unitOfWork.Movies.Add(movieToInsert);
_unitOfWork.Complete();
}
PS:
The junction table called MoviesDirectors is autogenerated by EF when setting the right configurations. More precisely, in the MovieConfiguration class, I have:
HasMany(c => c.Directors)
.WithMany(t => t.Movies)
.Map(m =>
{
m.ToTable("MoviesDirectors"); // a way to overwrite the name that EF gives this table
m.MapLeftKey("MovieId");
m.MapRightKey("DirectorId");
});
which automatically creates MoviesDirectors.
For your second attempt you can do just foreach (var director in movieToInsert.Directors.ToList()), but I would recommend just to check the director.Id to be not a default value (0 or Guid.Empty) and if it is not - using Attach:
foreach (var director in movieToInsert.Directors)
{
if (director.Id > 0)
{
_unitOfWork.Directors.Attach(director);
}
}
I am working with an existing project that has a database with what appears to be manually created data via SQL Server \ SSMS.
Further down the project someone else has come and created a seed data \ configuration file. This is where I have been introduced into the solution and have created a new migration file, and found that I am getting an error:
PRIMARY KEY constraint 'PK_AnswerTypes'. Cannot insert duplicate key in object 'forms.AnswerTypes'. The duplicate key value is (1)
Looking through Azure Pipelines, this appears to have been an issue since the configuration file was created.
The configure code is
public void Configure(EntityTypeBuilder<FieldType> builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.ToTable("FieldTypes", FormEngineSchemas.Forms);
// TODO: convert to enum
builder.HasData(
new FieldType
{
FieldTypeId = 1,
FieldTypes = "NUMBER"
},
new FieldType
{
FieldTypeId = 2,
FieldTypes = "DROPDOWN"
},
new FieldType
{
FieldTypeId = 3,
FieldTypes = "DATE"
});
}
The upscript is
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
schema: "forms",
table: "AnswerTypes",
columns: new[] { "AnswerTypeId", "AnswerTypes" },
values: new object[,]
{
{ 1, "Range" },
{ 2, "Length" },
{ 3, "regex" }
});
}
I would be grateful if someone could help advise me how to get passed this as I am looking to not have to delete the existing data in the database because I dont want to risk potential orphaned records, or risk failed deletes.
I have had a look round and this is the closest that I can see to my issue
https://github.com/dotnet/efcore/issues/12324
Looking here it looks like the seeding has been done correctly
https://www.learnentityframeworkcore.com/migrations/seeding
https://learn.microsoft.com/en-us/archive/msdn-magazine/2018/august/data-points-deep-dive-into-ef-core-hasdata-seeding
So, questions I have are;
If the database was all created and seeded from the beginning and all worked fine would all subsequent migrations work ok and not attempt to seed them again.
What is the best way to to get around this issue.
Is there anything I might have missed or not considered?
Thanks
Simon
Since you do not want to lose data, you can consider using migrationBuilder.UpdateData() instead of migrationBuilder.InsertData(). The InserData method will add new records to the DB while UpdateData will search for and update existing records in the DB.
Make sure you have EF Core v2.1 and up for this to work.
When trying to seed my database I am using the anonymous type like this:
context.CalibrationTargets.AddOrUpdate(x => new { calId = x.CalibrationSetup.Id, targetIndex = x.TargetIndex },
new CalibrationTarget()...
However because CalibrationSetup is a virtual property I seem to get the error:
The properties expression x => new <>f__AnonymousType102(calId = x.CalibrationSetup.Id, targetIndex = x.TargetIndex) is not valid. The expression should represent a property: C#: t => t.MyProperty VB.Net: Function(t) t.MyProperty. When specifying multiple properties use an anonymous type: C#: t => new { t.MyProperty1, t.MyProperty2 } VB.Net: `Function(t) New With { t.MyProperty1, t.MyProperty2 }
Also after reading this post The properties expression is not valid. The expression should represent a property, the problem seem to be because of the fact that CalibrationSetup is a virtual property.
Is there a way I can get around this without having to populate the database with another Calibration_Id column that isn't virtual to use in the anonymous type?
Hard to work without seeing the models, but to seed CalibrationTargets you will probably want something like below. Your syntax for AddOrUpdate also looks off. You have to have known values you are going to seed with.
context.CalibrationTargets.AddOrUpdate(
x => x.CalibrationTargetId, // This is the field that determines uniqueness
new CalibrationTarget
{
CalibrationTargetId = 1, // if this is an identity column, omit and use a different field above for uniqueness
TargetIndex = 99, // what value for target index? Fixed value or variable can be used
CalibrationSetup = new CalibrationSetup { Id = 77 } // Same as above comment
},
new CalibrationTarget
{
CalibrationTargetId = 2, // if this is an identity column, omit and use a different field above for uniqueness
TargetIndex = 88, // what value for target index? Fixed value or variable can be used
CalibrationSetup = new CalibrationSetup { Id = 66 } // Same as above comment
}
... repeat for all seeded records
);
context.SaveChanges();
If the FK CalibrationSetupId is exposed, you could simplify.
I'm trying to insert data into a primary table and a dependent table at the same time. A database relationship does exist between the two tables, and table Employee have an Identity column as the Primary Key.
I'm trying:
Database.Employee emp = new Database.Employee()
{
x = DateTime.Now,
y = "xxx"
};
Database.Skill skill = new Database.Skill()
{
Skill = "Reading"
};
emp.Skills = skill; //Error on this line
dc.Employees.InsertOnSubmit(emp);
dc.SubmitChanges();
But I get the error that:
Cannot implicitly convert type 'Database.Skill' to
'System.Data.Linq.EntitySet'
It looks like Database.Employee.Skills is a collection, not a single object. I.e. in the model it would look something like:
public class Employee
{
...
public EntitySet<Skill> Skills { get; set; } // collection of skills
}
Your line of code
emp.Skills = skill;
is trying to instantiate a collection as a single instance, so you are getting conversion error.
As such the correct way to add a skill would be something like
emp.Skills.Add(skill);
Exact implementation depends on what Database.Employee.Skills is.
I'm working with EF6, migrating from edmx to Code first. I did a reverse engineering from an existing DB, and I'm tring to reproduce the model I had before with a nested TPH done on a table called "Contacts" wich has 2 different fields "Type" and "SubType" respectively as first and second level discriminator.
I'd like to have something like this:
Contact
Model (Type = 1)
BookingModel (SubType = 1)
ScoutingModel (Subtype = 2)
Customer (Type = 2)
Agency (SubType = 3)
Client (Subtype = 4)
Service (Subtype = 5)
I've coded Contact, Model and Customer classes as Abstract and BookingModel, ScoutingModel, Agency, Client, Service as Concrete without including "Type" and "Subtype" fields as they are the discriminators.
Here is the mapping code:
public virtual DbSet<Contact> Contacts { get; set; }
modelBuilder.Entity<Contact>()
.Map<Model>(e => e.Requires("Type").HasValue((byte)ContactTypeEnum.Model))
.Map<Customer>(e => e.Requires("Type").HasValue((byte)ContactTypeEnum.Customer));
modelBuilder.Entity<Customer>()
.Map<Agency>(e => {
e.Requires("Type").HasValue((byte)ContactTypeEnum.Customer);
e.Requires("SubType").HasValue((byte)CustomerTypeEnum.Agency);
})
.Map<Client>(e => {
e.Requires("Type").HasValue((byte)ContactTypeEnum.Customer);
e.Requires("SubType").HasValue((byte)CustomerTypeEnum.Client);
})
.Map<Service>(e => {
e.Requires("Type").HasValue((byte)ContactTypeEnum.Customer);
e.Requires("SubType").HasValue((byte)CustomerTypeEnum.Service);
});
modelBuilder.Entity<Model>()
.Map<BookingModel>(e => {
e.Requires("Type").HasValue((byte)ContactTypeEnum.Model);
e.Requires("SubType").HasValue((byte)ModelTypeEnum.Booking);
})
.Map<ScoutingModel>(e => {
e.Requires("Type").HasValue((byte)ContactTypeEnum.Model);
e.Requires("SubType").HasValue((byte)ModelTypeEnum.Scouting);
});
But this is not working, I'm geting an error like
(52,10) : error 3023: Problem in mapping fragments starting at lines 52, 68, 75, 82, 89, 98, 106, 729, 747:Column Contact.Type has no default value and is not nullable. A column value is required to store entity data.
The "Type" and "SubType" fields in the DB are Tinyint nullable with no default values. I've tried also to change them as nullable or giving a default value but it doesn't change the resulting error.
Am I doing something wrong in setting the mapping, or am I choosing a wrong way to face this scenario?