Initializing/Adding fixed entries on table creation - c#

I am using Entity Framework code first, and I have two tables that need to be initialized with fixed entries just on table creation. For example:
Columns for table A:
-Id
-Name
Columns for table B:
-Id
-Name
On table creation, I need to add the below information for each table:
For table A:
Id Name
1 "Name_A1"
2 "Name_A2"
3 "Name_A3"
4 "Name_A4"
For table B should be more or less the same:
1 "Name_B1"
2 "Name_B2"
3 "Name_B3"
4 "Name_B4"
5 "Name_B5"
6 "Name_B6"
7 "Name_B7"
8 "Name_B8"
so how to achieve this? I have thought to override method Seed and add those registries there (hard-coded) but I do not know if it is the best solution. Maybe in development phase it is a good practise to override this method and add there the information to the tables but once application will be deployed in the customer, maybe it is better to once created the database by entity framework in the development machine, then do an export and import it to the customer computer and finally fill those tables with the fixed entries.

As you and Raphaƫl mentioned, you can use the Seed() method:
protected override void Seed(MyContext context)
{
context.A.AddOrUpdate
(
a => a.Name,
new A { Name="Name_A1" },
new A { Name="Name_A2" },
new A { Name="Name_A3" },
new A { Name="Name_A4" }
);
context.SaveChanges();
context.B.AddOrUpdate
(
b => b.Name,
new B { Name="Name_B1" },
new B { Name="Name_B2" },
new B { Name="Name_B3" },
new B { Name="Name_B4" },
new B { Name="Name_B5" },
new B { Name="Name_B6" },
new B { Name="Name_B7" },
new B { Name="Name_B8" }
);
context.SaveChanges();
}
Using AddOrUpdate will ensure no duplicate data is inserted into the database. The first parameter, a => a.Name, lets EF identify each entity. The identity could be a PK value of the entity, but if your DB is generating these, you won't know the value, so using AddOrUpdate will check to see if the Name exists. If it doesn't, a new record will be inserted. If it does find a match, the entity will be updated. It is of course up to you to decide on using something that will uniquely identify each entity.

you can use EF Migration to achieve that first you should Enable EF Migration in your project using this command in powershell console :
Enable-Migration -ContextTypeName yourContextName
above command will create a folder called Migration, then in configuration file you can override seed method
just override seed method :
protected override void Seed(MyContext context)
{
context.tableA.AddOrUpdate(
p => p.Name,
new tableA { Name = "Name_A1" },
new tableA { Name = "Name_A2" },
new tableA { Name = "Name_A3" },
new tableA { Name = "Name_A4" }
);
context.tableB.AddOrUpdate(
p => p.Name,
new tableB { Name = "Name_B1" },
new tableB { Name = "Name_B2" },
new tableB { Name = "Name_B3" },
new tableB { Name = "Name_B4" }
);
}
into above method you can define your look-up data like list of postal codes, list of countrys and ....

Related

Why does Entity Framework add a related record even if it already exists in the database?

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);
}
}

EF Core Migration - PK Violation when seeding data

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.

Add data using migration Entity Framework Core without specifying the id

I would like to use EF migrations to add some data inside my DB. The fact is that all Id are auto-generated. I found the standard way to achieve it with EF Core, which is the following
modelBuilder.Entity<TypeNote>()
.HasData(
new TypeNote { Id = 1, Name = "General" },
new TypeNote { Id = 2, Name = "E-mail" },
new TypeNote { Id = 3, Name = "Meeting" },
new TypeNote { Id = 4, Name = "Reminder" },
new TypeNote { Id = 5, Name = "Telephone" },
new TypeNote { Id = 6, Name = "Visit" }
);
My issue here is that I don't want to specify the Id, but it seems that there is no other way using HasData method.
Do you know another way to add data inside DB using migration?
I found a way to do an insert during migration:
migrationBuilder.InsertData(
table: "TypeNote",
columns: new[] { "Name" },
values: new object[,]
{
{ "Test" },
{ "Test1" }
});
The fact is that I wanted to access dbContext inside the migration. Which is impossible because DB is updating.
This is standard for HasData. Auto generated fields are not generated with HasData method.
Please have a look at this EntityFrameworkCore issue.
I think what you are looking for is referred to as "seed data"
Please check out this Microsoft Doc on how to add seed data to your database and let me know whether or not that is what you're looking for.

Does Entity Framework Database First know about unique keys set in the database?

EF6.
I have a project using EF6 Database first.
My database table had a unique key on 6 columns. All was working well in the project. I then realized the unique key was incorrect and I didn't need it so I deleted it from the database.
I have a Product which contains an ICollection<ProdPrice> ProdPrices.
Now, when I use Product product = db.Products.Find(id); I get a product but the ProdPrices collection only contains 6 items when it should contain 12. (The first 6 items are in the list).
If I delete the first item from the db and run the code again, 6 items are returned - items 2 - 7.
If I change one of the values in a column that was in the unique key that item will come though in the code.
So I'm thinking EF is somehow remembering the unique key and only returning the first items that do not conflict with the unique key.
I tried to "update model from database" in the edml file - didn't resolve the issue. So I deleted the ProdPrice table from the edml and the "update model from database" - didn't work.
So my question is - Am I correct is saying EF is remembering the deleted unique key? If yes, how do I get it to forget about it? If I am incorrect, then can you explain what is actually happening?
EDIT: SQL generated by the call to the database - standard select statement which returns all 12 records when run in SSMS (columns removed for ease of reading) :
SELECT
[Extent1].[ProdPriceID] AS [ProdPriceID],
[Extent1].[ProdID] AS [ProdID],
[Extent1].[PermanentlyDelete] AS [PermanentlyDelete],
[Extent1].[DateCreated] AS [DateCreated]
FROM [dbo].[ProdPrice] AS [Extent1]
WHERE [Extent1].[ProdID] = 67577
Here are the results so you can see I get 12 records.
Code Result:
So I decided to just request the prices for the product - I get 12 results, this is really confusing me:
EDIT:
I thought I had resolved it...
To resolve (and test further) I decided to recreate the ProdPrice table in the db - I did this by generating the script using MSSSMS and including the data so I had an exact copy of the table, calling it ProdPrice2.
I had both ProdPrice and ProdPrice2 Entities in my system (This confirmed I'm still connected to the correct database). ProdPrice still only returned records that did not conflict with the original Unique Index. ProdPrice2 - returned all records!
Whoop, thought that was it - I then removed ProdPrice from my system, leaving ProdPrice2 - I ran the system, ProdPrice2 now only has 6 records, not the 12 I had previously!!!
I added ProdPrice back in. ProdPrice2 still has 6 records, ProdPrice now has all expected records.
I'M STUMPED!!!! This is really stopping my development! I can't continue until this is resolved!
After wrestling with this for a couple of days and some excellent help and suggestions from #AlbertoMonteiro I couldn't get the behaviour I expected.
After continuing to try various things, and debugging, I eventually hit some code I forgot I had in there...!
So the answer to the original question
Does Entity Framework Database First know about unique keys set in the database?
is, no Entity Framework Database First DOESN'T know about unique keys set in the database.
EF does however, use any overridden Equals() functions when creating lists of entities. In my code I had overridden the Equals functions to mimic the unique keys in the database meaning when I added to lists of Prices I could match "duplicates" and merge them together. This code resulted in my Prices list not being fully populated now that I had changed the way I wanted the system to work.
The answer is that I'm an idiot and I should have remembered the code I had written. I will leave this question here as it may stop someone losing a couple of days development in the future.
Entity Framework in this question, doesn't worry about your deleted Unique Key, also this Unique Key doesn't filter the data when you select something, the unique key prevents that you can not add another row with the same unique key.
So again, this unique key isn't a trouble, and EF doesn't care about it.
But you can debug the SQL generated from EF and check why you are getting only 6 items from property ProdPrices.
We can use the Database.Log property from context, to analyze the SQL generated.
You said that you have this code line Product product = db.Products.Find(id);
Lets modify it a little bit to analyze the sql.
//Add breakpoint in this line
Product product = db.Products.Find(id);
db.Database.Log = sql => System.Diagnostics.Debug.WriteLine(sql);
var prodPrices = product.ProdPrices.ToList();
//Next line
Add a breackpoint in first line(that find the product)
Press F10(Step over) 3 times
Breakpoint must be now in next line of code after the creation of prodPrices
Open the Output window
The SQL generated from ProdPrice, should be there
Analyze the SQL query, execute in SSMS and check the result.
I tried this in my machine, but it works fine:
public class Program
{
public static void Main(string[] args)
{
using (var ctx = new MyClass())
{
if (!ctx.Products.Any())
{
var product = new Product
{
ProdPrices = new List<ProdPrice>
{
new ProdPrice {PermanentlyDelete = true, DateCreated = new DateTime(2015,12,29,18,28,27)},
new ProdPrice {PermanentlyDelete = true, DateCreated = new DateTime(2015,12,29,18,28,28)},
new ProdPrice {PermanentlyDelete = true, DateCreated = new DateTime(2015,12,29,18,28,28)},
new ProdPrice {PermanentlyDelete = true, DateCreated = new DateTime(2015,12,29,18,28,29)},
new ProdPrice {PermanentlyDelete = true, DateCreated = new DateTime(2015,12,29,18,28,29)},
new ProdPrice {PermanentlyDelete = true, DateCreated = new DateTime(2015,12,30,19,08,38)},
new ProdPrice {PermanentlyDelete = false, DateCreated = new DateTime(2015,12,31,10,18,06)},
new ProdPrice {PermanentlyDelete = false, DateCreated = new DateTime(2015,12,31,10,18,06)},
new ProdPrice {PermanentlyDelete = false, DateCreated = new DateTime(2015,12,31,10,18,07)},
new ProdPrice {PermanentlyDelete = false, DateCreated = new DateTime(2015,12,31,10,18,07)},
new ProdPrice {PermanentlyDelete = false, DateCreated = new DateTime(2015,12,31,10,18,08)},
new ProdPrice {PermanentlyDelete = false, DateCreated = new DateTime(2015,12,31,10,18,08)},
}
};
ctx.Products.Add(product);
ctx.SaveChanges();
Console.WriteLine("Saved");
}
}
using (var ctx = new MyClass())
{
var product = ctx.Products.Find(1);
var count = product.ProdPrices.Count;
Console.WriteLine(count);
}
}
}
public class MyClass : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<ProdPrice> ProdPrices { get; set; }
}
public class Product
{
[Key]
public long ProdID { get; set; }
public virtual ICollection<ProdPrice> ProdPrices { get; set; }
}
public class ProdPrice
{
public long ProdPriceID { get; set; }
public bool PermanentlyDelete { get; set; }
public DateTime DateCreated { get; set; }
}
It seems you are creating the dbcontext only once, and that too is static.
When ever you are making a fetch/write call to db create a new context, that should do.
Again,
Create a base class which contains DbContext and inherit that class every time you need to talk to the db.
Something like this
public class TheDBEntities
{
private TheDBEntities _context = new TheDBEntities();
protected TheDBEntities Context { get; private set; }
protected TheDataContext()
{
Context = _context;
}
protected void SaveChanges()
{
Context.SaveChanges();
}
protected void SaveChangesAsync()
{
Context.SaveChangesAsync();
}
}
public class DBBusinessLayer: TheDBEntities
{
public DBBusinessLayer() { }
public void GetData(int id)
{
// use it here Context.<yourentities>
}
}

DbMigration seed sequence contains more than one element

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

Categories