I want to update a mediate table's primary keys. This post suggests to take another primary key and don't change it, but we're using our tables in other non-EF projects, I didn't designed them and I'm not able to change them. Do I have any other option? Anything? Even deleting the old record and inserting a new one. I just don't know how to retrieve the old values.
This is my class:
public class ZChangeUnits : User
{
[Key]
[Column(TypeName = "VARCHAR", Order = 0), StringLength(4)]
public string CCode1 { get; set; }
[ForeignKey("CCode1")]
public virtual ZUnits ZUnits1 { get; set; }
[Key]
[Column(TypeName = "VARCHAR", Order = 1), StringLength(4)]
public string CCode2 { get; set; }
[ForeignKey("CCode2")]
public virtual ZUnits ZUnits2 { get; set; }
[Column(TypeName = "NUMERIC")]
[DecimalPrecision(18, 5)]
public decimal NZarib { get; set; }
}
UPDATE
I've posted the schema below, the left table is used for unit conversion. This is actually the suggested method in this answer.
I can't think of any other way to implement that. Whether I should update my table's design or some EF code will do the job. Any help and solution is welcome. :)
Related
I have the following two models within my Blazor Server project:
Vergadering:
public class Vergadering
{
[Key]
public int Id { get; set; }
public string Naam { get; set; }
public DateTime DatumTijd { get; set; }
public ICollection<Bestuurslid> Aanwezigen { get; set; }
public string? Notulen { get; set; }
public ICollection<Vergadering>? HoofdVergadering { get; set; }
public ICollection<Vergadering>? GekoppeldeVergaderingen { get; set; }
public ICollection<Bestand>? Bestanden { get; set; }
public string? UserLastEditId { get; set; }
public IdentityUser? UserLastEdit { get; set; }
public DateTime? LastEdit { get; set; }
public ICollection<VergaderingAgendaItem>? vergaderingAgendaItems { get; set; }
}
VergaderingAgendaItem:
public class VergaderingAgendaItem
{
public int Id { get; set; }
public string Omschrijving { get; set; }
public bool Afgerond { get; set; }
public int? ParentId { get; set; }
public VergaderingAgendaItem? Parent { get; set; }
public int VergaderingId { get; set; }
public Vergadering Vergadering { get; set; }
public string? UserAangedragenId { get; set; }
public IdentityUser? UserAangedragen { get; set; }
}
This results in three tables:
Vergaderingen
VergaderingAgendaItems
VergaderingVergadering
In my repository I have the following update method:
public async Task ChangeAfgerondStatusAsync(VergaderingAgendaItem item)
{
using (var _db = _factory.CreateDbContext())
{
_db.VergaderingAgendaItems.Update(item);
await _db.SaveChangesAsync();
}
}
Whenever the Vergadering does not have a GekoppeldeVergadering this update method does not create any problem.
But whenever the Vergadering does have a GekoppeldeVergadering and I update a VergaderingAgendaItem of that Vergadering I get this error:
An error occurred while saving the entity changes. See the inner exception for details.
Looking at the command prompt that opens up while running the project I saw the following query and error.
Queries:
Error:
An exception occurred in the database while saving changes for context type 'AVA_ZICHT.Data.ApplicationDbContext'.
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
Microsoft.Data.SqlClient.SqlException (0x80131904): Violation of PRIMARY KEY constraint 'PK_VergaderingVergadering'. Cannot insert duplicate key in object 'dbo.VergaderingVergadering'. The duplicate key value is (4, 3).
How is it that EF Core tries to update the GekoppeldeVergadering in VergaderingVergadering table. My method states VergaderingAgendaItem.Update()?
When handed a detached entity and told to Update it, EF will consider any associated entities as well. Since those references aren't tracked by the DbContext, the context will see those entities as new items to be inserted. This can result in duplicate key exceptions (as you are seeing) or inserting duplicate data with new PKs if those keys are set up as Identity columns.
One way to get around this issue is to use Automapper configured to just update the columns you expect to change:
public async Task ChangeAfgerondStatusAsync(VergaderingAgendaItem item)
{
using (var _db = _factory.CreateDbContext())
{
var existingItem = _db.VergaderingAgendaItems.Single(x => x.Id == item.Id);
Mapper.Map(item, existingItem);
await _db.SaveChangesAsync();
}
}
Alternatively this can be done manually by copying values from item to existingItem. existingItem is tracked entity so once it's updated, just call SaveChanges. The advantage of this over Update is that the resulting UPDATE SQL statement will only be for any columns that have actually changed, and it won't execute an UPDATE if nothing has actually changed.
This assumes we only want to copy fields from that entity, and none of the child/related entities. If you want to alter the collections/associations then you will need to eager load them and handle these separately. For instance changing the UserLastEdit reference, this is likely something you would want to eager-load so that it can be updated with the current User record.
My general advice is to avoid working with detached entities for concerns like this and instead use POCO view models. The trouble with using detached entities is that these are often incomplete representations of entity state, at worst, something deserialized from view state and cast into an Entity object. View Models can also be scaled down to just the data your client needs and what data is allowed to change. When it gets back to the server there is no confusion about what it is vs. what it pretends to be. Another consideration of applying updates which is important in multi-user systems is detecting stale data. Writing updates like this applies a "last in wins" approach where you should ideally check that the current DB data state concurrency token matches the token/version at the time that this user's original version was read. The attraction of using detached entities is the thought of avoiding a round-trip to the DB when performing an update, but in all honesty you should justify a round trip to ensure that the record is actually valid, the user actually can update that record, and the record hasn't been updated by someone else in the time this user was editing it.
Assume I've read and googled, and I still don't know what I'm doing incorrectly. Whenever I try to execute
_dbContext.Set<T>().Add(aMediaObjectWithAssociatedProvider);
_dbContext.SaveChanges();
I get the dreaded efcore violation of primary key constraint reference table
I have a class as such:
public class Media : BaseModel
{
public virtual string Title { get; set; }
public virtual string? Description { get; set; }
public virtual string Source { get; set; }
public virtual Guid? MediaTypeId { get; set; }
public virtual Guid? ProviderId { get; set; }
public virtual DateTime? StartDate { get; set; }
public virtual DateTime? EndDate { get; set; }
public virtual Provider? Provider { get; set; }
}
The BaseModel class is
public abstract class BaseModel : IBaseModel
{
public virtual Guid Id { get; set; }
}
The Provider class is as such:
public class Provider : BaseModel
{
public virtual string Name { get; set; }
public virtual string? ApiUsername { get; set; }
public virtual string? ConfigurationSection{ get; set; }
}
My DBContext has the following:
protected override void OnModelCreating(ModelBuilder mb)
{
mb.Entity<Media>().HasKey(x => x.Id);
mb.Entity<Media>().HasOne(p => p.Provider).WithOne().HasForeignKey<Media>(x => x.ProviderId);
}
The code for inserting a new object is as follows:
public T Insert(T oneObject)
{
try
{
// Ensure the entity has an ID
if (oneObject.Id == Guid.Empty)
{
oneObject.Id = Guid.NewGuid();
}
_dbContext.Set<T>().Add(oneObject);
_dbContext.SaveChanges();
}
catch (Exception error)
{
_logger.LogError(error.Message, error);
}
return oneObject;
}
Assume that providers are static, in a sense that they already exist in their table, and I don't want to add new providers when I save media... Media just needs to have a provider.
I know exactly what is happening (the model, after travelling through json back through the api to the server is losing context, but I'm also trying to build a repository type of system where I don't have to build complex save logic for every object. (hence why i'm hand wringing over adding code that loads existing providers).
This problem specifically began rearing its head when I was saving new Media objects into the database with existing Providers. I am still mulling over how to look up children dynamically, but i'm not quite there yet.
I've been at this for so long, i'm about ready to give up on efcore relations and just rebuild the models as single objects, and handle all of the manipulation in javascript. And I don't like this idea.
I know for a fact that there will be questions for more code, but please let me know what. Again, I'm just stepping into .net core / ef core so this code-first is a little confusing for me. Thanks
You may have 2 options to try. Do backup your whole project and database beforehand. Clone your database to another database name. Try these either one option using new cloned database for testing.
No.1
Set "newid()" without quotes in your ID's default value in sql server. So you don't need to use Guid.NewGuid() in code every insert. newid() will auto generate GUID.
No. 2
How about removing primary key from ID (GUID) and then creating new column "UID" (running number) and set UID (running number) as primary key and enable its identity? You need to change all other tables too. And re-link UID each other if you use relationship. This way, your UID will not have existing number when insert.
I am trying to create a quick demo shop, and I am failing with an optional many to one or zero relationship.
The relevant classes are:
Item
public class Item
{
public int ID { get; set; }
public string Name { get; set; }
public int SubCategoryID { get; set; }
public virtual SubCategory Category { get; set; }
public double Price { get; set; }
}
Order
public class Order
{
public int ID { get; set; }
public DateTime DateOfOrder { get; set; }
public virtual ICollection<Item> Items { get; set; }
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
}
However, I am getting confused because viewing the model shows:
but, the database itself shows (for items):
Which indicates to me that each item can only belong to a single order.
Do I have to create a separate class that is many to many orders/items?
I seem to remember EF doing this automatically, but, I haven't touched it for a few years and I just can't remember what I used to do.
I had to add:
public virtual ICollection<order> Orders { get; set; }
to the Item... I'm never going to call it this way, but it looks like that is required for EF to build this relationship.
I am sure it used to be easier, so, leaving this question open so someone can give a better answer!
If you add a collection of Orders to the Item entity, EF will create for you implicitly the junction table on your DB. In this page you can find more info about how to configure a many to many relationship.
The junction table generally is mapped when you need to add an additional column (that excludes both keys of the tables you are joining). In that case you need to create two one-to-many relationships between the entity that represent the junction table and Order and Item respectively. Some people recommend always map the junction table because that way you have all the tables represented as entities and you can write queries starting by the junction table. You can find interesting info about this subject in this link. But, in my experience, in most cases you can work perfectly without map explicitly the junction table.
I was encountring an error when I try to add class to the database
DB.Trips.Add(trip);
I solved it by setting the navigation properties to null, but i never had to do that before and it worked just fine, so im wondering why is that, as it doesnt seem to me as a good approach and the problem might persist.
When I do the DB.SaveChanges(); I get an error. From the SQL profiler I found out that it is trying to insert a record into Countries table.
exec sp_executesql N'insert [dbo].[Countries](......
But Trips table doesn't even have Country property. There is a City property, which has Country. But why would it try to add that as well and how can I force it to insert only into Trips table ?
The data comes in via angular $http.post, is it possible its somehow related ?
Trip class city related attributes
public int CityOriginID { get; set; }
public int CityDestinationID { get; set; }
public virtual City CityDestination { get; set; }
public virtual City CityOrigin { get; set; }
City class
public partial class City
{
public int CityID { get; set; }
public string Name { get; set; }
public string CountryCode { get; set; }
public virtual Country Country { get; set; }
}
Thanks for any suggestions
Try adding a reference to your Trip entity in your City entity like
public virtual ICollection<Trip> Trips {get; set;}
to indicate your one-to-many relationship
I'm learning EF with MVC and following
video
They saying that line :
public ICollection<Chirp> Chirps { get; set; }
adds the relationship between the two models, but I don't see any difference so far when looking at both tables columns and keys after regenerating tables WITHOUT this line. I'm missing something as this kind of relationship is mentioned in all the tutorials.
public class User
{
public int Id { get; set; }
public String Name { get; set; }
public ICollection<Chirp> Chirps { get; set; }
}
public class Chirp
{
public int Id { get; set; }
public string Message { get; set; }
public User User { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Chirp> Chirps { get; set; }
public DbSet<User> Users { get; set; }
}
In other words, does public ICollection Chirps { get; set; } tell EF to do anything with db structure ?
Thank you!
This should be generating an additional table that tracks the User to Chirps relationship. Look in your database for a User_Chirps or UserChirps table that should have two columns, User_Id and Chirp_Id. Additionally, I just noticed that your id property is using a lowercase i for id. I believe that the conventions in Entity Framework are expecting a property name of Id with an uppercase I. So you may want to try changing that as well.
Seems that difference is in constraint of the Chirps table. Without public ICollection Chirps { get; set; }
its:
ALTER TABLE [dbo].[Chirps] WITH CHECK ADD CONSTRAINT **[Chirp_User]** FOREIGN KEY([User_id])
REFERENCES [dbo].[Users] ([id])
while with it its :
ALTER TABLE [dbo].[Chirps] WITH CHECK ADD CONSTRAINT **[User_Chirps]** FOREIGN KEY([User_id])
REFERENCES [dbo].[Users] ([id]