I'm altering a business application to introduce a new entity SalesOrderReason. This will tie into another entity SalesOrder based on a SalesOrderReasonID field.
What I would like this code to do is to set the SalesOrderReasonID of the sales order (called from a button press - this is a desktop application).
In reality, what is happening is the first time it runs, it sets the SalesOrderReasonID - that's great. When I set the SalesOrderReasonID for the next SalesOrder it sets it correctly however it deletes a previous SalesOrder from the database which is obviously unwanted behavior.
I've had a similar issue previously where the relationship was configured incorrectly (I had put WithOne instead of WithMany) which gave the same symptoms, because of this I believe it's related to the configuration.
I've tried to configure it from the other perspective as well (SalesOrderReason config) without any success.
(Here is that attempt, I created an ICollection SalesOrdersWithReason on SalesOrderReason for this)
builder.HasMany(e => e.SalesOrdersWithReason)
.WithOne(s => s.SalesOrderReason)
.HasForeignKey(s => s.SalesOrderReasonID);
(Sorry if this is too much code I've tried to strip away what I thought was not relevant.)
This is the SalesOrder entity.
public class SalesOrder
{
public int ID { get; set; }
public int SalesOrderReasonID { get; set; }
public SalesOrderReason SalesOrderReason { get; set; }
}
This is the SalesOrderReason entity
public class SalesOrderReason
{
public int Id { get; set; }
public string Reason { get; set; }
public DateTime DateCreated { get; set; }
}
This is my configuration using fluent API
public class SalesOrderConfiguration : IEntityTypeConfiguration<SalesOrder>
{
public void Configure(EntityTypeBuilder<SalesOrder> builder)
{
builder.HasKey(e => e.ID);
builder.Property(e => e.ID)
.ValueGeneratedOnAdd();
builder.HasOne(s => s.SalesOrderReason)
.WithMany()
.HasForeignKey(s => s.SalesOrderReasonID);
}
}
This is the Save method in my Repository
public void Save() => _context.SaveChanges();
It all goes wrong here when saving a change to the SalesOrderReasonID
private static void ChangeSalesOrderReason(SalesOrder salesOrder, int salesOrderReasonID)
{
salesOrder.SalesOrderReasonID = salesOrderReasonID;
SalesOrderRepository.Save();
}
Edit to include UI code as requested
The SalesOrder in this use case is an existing object which comes from a Telerik gridview row, it gets selected then the user presses a button to update the SalesOrderReason on the SalesOrder to the one in a DropDownList.
The UI is in VB.Net (Odd I know but it's part of a legacy project we're rebuilding) the above C# code is in separate C# projects added as reference in the VB project.
Private Sub UpdateReasonButton_Click(sender As Object, e As EventArgs) Handles UpdateReasonButton.Click
For Each salesOrderRow As GridViewRowInfo In SalesOrderGridView.SelectedRows
SalesOrderController.ChangeSalesOrderReason(salesOrderRow.DataBoundItem, ReasonList.SelectedValue)
Next
End Sub
Following Panagiotis Kanavos's comment, I adjusted my DbContext to have a much shorter lifespan, I didn't have time to create a proper "Unit of Work" pattern but simply recreating the context whenever my code hit the repository seemed to do the trick.
Related
Have a table (tblCalculation) that had two related tables (tblAddon and tblVehicle) with foreign keys AddonId and VehicleId. Had scaffolded the context and models in my c# core 3.1 application. I then had to change the relationships of that table and remove the relation to tblVehicle table, so I removed the relationship in SSMS and deleted the foreign key VehicleId. Did the same in my code and removed the property VehicleId on the Calculation model and removed the HasOne(..).WithMany(...) definition from the OnModelCreating setup.
But when trying to load the table (which has been empty through this process) I get Invalid column name 'VehicleId'. Loading the (still empty" table in SSMS works fine but this code generates the error:
var calculations = await _context.Calculation.ToListAsync();
Is there some reference in ef I'm missing or what is the issue?
EDIT:
Ran a Scaffold-DbContext again and can't find any reference to a VehicleId
public partial class Calculation
{
public int Id { get; set; }
public int? AddonId { get; set; }
public byte Period { get; set; }
public short Mileage { get; set; }
public decimal? ResidualValue { get; set; }
public decimal? MonthlyCost { get; set; }
public DateTime Created { get; set; }
public string CreatedBy { get; set; }
public DateTime? Updated { get; set; }
public string UpdatedBy { get; set; }
public virtual Addon Addon { get; set; }
}
modelBuilder.Entity<Calculation>(entity =>
{
entity.Property(e => e.Created)
.HasColumnType("datetime")
.HasDefaultValueSql("(getdate())");
entity.Property(e => e.CreatedBy).HasMaxLength(50);
entity.Property(e => e.MonthlyCost).HasColumnType("money");
entity.Property(e => e.ResidualValue).HasColumnType("money");
entity.Property(e => e.Updated).HasColumnType("datetime");
entity.Property(e => e.UpdatedBy).HasMaxLength(50);
entity.HasOne(d => d.Addon)
.WithMany(p => p.Calculation)
.HasForeignKey(d => d.AddonId)
.HasConstraintName("FK_tblCalculation_tblAddon");
});
Replacing the current context's models with the temporary ones creates the same issue but running the identical models in the temporary context works. So something in the context keeps a reference to VehicleId but can't find it. So will generate a completely separate context (since there are other tables in the original context) and run from that.
The simplest solution is to scaffold the database again. This should give you a working context that you can use as a complete replacement for the one you have. If you have customized the models in your existing context you'll need to copy the changes over. This can be made easier with a tool like BeyondCompare; scaffold your db to a new, different folder to the one that the existing context is in, then (and these are BeyondCompare specific instructions) in windows explorer right click the new folder and choose "select left folder for compare", then go to the folder the existing context is in and right click, choose "compare to otherfolder". A side by side window pair appear, press Ctrl A and pick Compare Contents from one of the menus (I forget which, maybe Session or Action). BC will compare all the files and show any with DM significant differences in red. Double click a red file to see a side by side diff and use the arrows to copy from old to new, or use it to find any lingering files in your old context where VehicleId is still mentioned (it could be in a dependent related file, other than the ones you're focused on in the question)
Once you have made one context or the other be your main one, delete the other context.
I am running into an interesting issue when trying to add an entity to the database with Entity Framework. When I try to add a new exception to the database, I run into the following error:
"Cannot insert explicit value for identity column in table 'Notification' when identity_insert is set to off"
I am guessing this error is caused due to the Notification entity already having an Identifier (Id property). However, my goal is not to store the Notification entity. It just so happens that my NotificationException entity has a reference to an existing Notification entity.
How can I update my NotificationException entities properly without running into this problem? Actually turning the identity_insert off does not seem like a viable solution.
My two model classes:
public class Notification
{
// Primary Key
public long Id { get; set; }
// Properties
public bool IsSent { get; set; }
public bool IsExpired { get; set; }
public int RetryCount { get; set; }
public int RetryTime { get; set; }
}
public class NotificationException
{
// Primary Key
public long Id { get; set; }
// Properties
public int Timestamp { get; set; }
public string Exception { get; set; }
// Foreign Keys
public long NotificationId { get; set; }
// Navigation Properties
public virtual Notification Notification { get; set; }
}
Entity Configuration with Fluent API:
private void ConfigureNotificationEntity(EntityTypeBuilder<Notification> builder)
{
builder.ToTable("Notifications");
builder.HasKey(i => i.Id);
builder.Property(i => i.Id)
.IsRequired()
.ValueGeneratedOnAdd();
builder.HasMany(n => n.Exceptions)
.WithOne(e => e.Notification)
.OnDelete(DeleteBehavior.Cascade);
}
private void ConfigureNotificationExceptionEntity(EntityTypeBuilder<NotificationException> builder)
{
builder.ToTable("NotificationExceptions");
builder.HasKey(i => i.Id);
builder.Property(i => i.Id)
.IsRequired()
.ValueGeneratedOnAdd();
builder.HasOne(i => i.Notification)
.WithMany(j => j.Exceptions);
}
The main problem:
public async Task<NotificationException> Add (NotificationException item)
{
_context.NotificationExceptions.Add(item);
await _context.SaveChangesAsync();
return item;
}
As soon as the _context.SaveChangesAsync(); is called, the error mentioned above is thrown.
///Edit
I tested this issue with different objects as well. If the entity has no nested entities, then storing them works just fine. The issue is quite likely with the already known ID of the nested entity.
So it turns out in 2016, EF Core changed the behaviour of the Attach method from EF6: https://github.com/dotnet/efcore/issues/4424
to wit:
Add: Adds every reachable entity that is not already tracked
Attach: Attaches every reachable entity, except where a reachable entity has a store generated key and no key value is assigned, these will be marked as added.
Therefore, the solution is just to change
_context.NotificationExceptions.Add(item); to
_context.NotificationExceptions.Attach(item);
This will Add the new NotificationException pushed without a key; however the child Notification, with a declared key, will be attached as 'Unchanged'.
The problem looks like an issue with how you add notification object to your notificationException object.
Change your Add function like so:
public async Task<NotificationException> Add (NotificationException item)
{
var notification = _context.Notifications.Find( x => x.Id == yourNotificationId);
item.Notification = notification;
_context.NotificationExceptions.Add(item);
await _context.SaveChangesAsync();
return item;
}
The trick here is we got notification entity from same context that we will add notificationException object.
Check this link for more info
Found a fix!
var exception = new NotificationException()
{
Exception = "Very serious exception!",
QueuedNotificationId = notification.Id,// <-- setting the foreign key
Timestamp = 420
};
Instead of referencing the entire Notification object within the Exception entity, I just set the foreign key. This way I can store the entity just fine. Still a bit silly how EF doesn't automatically recognize the Notification entity already exists.
I am a bit confused, about how to do relationships in EF Core 2.1. I been doing it the fluent way.
I have
public class Employee : IdentityUser
{
public int BranchId { get; set; }
public Branch Branch { get; set; }
}
public class Branch
{
public int Id { get; set; }
public int CompanyId { get; set; }
public Company Company { get; set; }
public ICollection<Employee> Employees { get; set; }
}
public class EmployeerConfig : IEntityTypeConfiguration<Employee>
{
public void Configure(EntityTypeBuilder<Employee> builder)
{
builder.ToTable("Employees");
}
}
public class BranchConfig : IEntityTypeConfiguration<Branch>
{
public void Configure(EntityTypeBuilder<Branch> builder)
{
builder.HasKey(x => x.Id);
builder.Property(x => x.Id).ValueGeneratedOnAdd();
builder.HasMany(x => x.Employees);
builder.ToTable("Branches");
}
}
All the examples I seen mostly to use dbcontext model builder, but that way is no longer needed as you can now use split it up as I done.
I done my relationships 2 ways first for Company and Branch I don't even specify the relationship yet went I build my db it knows, however when I try to do that with Employee and Branch the relationship was not formed.
This made me add builder.HasMany(x => x.Employees) in the branch config and now the relationship works, however I am not sure if I have specify something in the Employee area to make to complete?
I also don't know if I need to still add virtual to my collections anymore and why if I don't use ToTable() and build my db, all the table names are abbreviated, I thought that was automatic.
EmployeerConfig is fine you can add key if yoy want
For Branch Config add this line hope this'll help you.
builder.HasMany(x => x.Employee).WithOne(b => b.Branch).HasForeignKey(b => b.BranchId)
.OnDelete(DeleteBehavior.Cascade);
There seem to be multiple ways to do this. You can specify the relationship in EmployeeConfig.Configure or in BranchConfig.Configure, or in the DbContext's OnModelCreating method. I haven't tested to see what kind of behavior you get from specifying it in multiple places, but it's probably fine as long as it's consistent. However, in order to make maintenance easier, you probably want to specify it in only one place. I think the natural place is the DbContext, as this is a piece of information related to multiple entities, not just one. If you'd prefer to put it in one of the configuration files, I would suggest that putting it in the parent entity seems more natural.
Here is how you would specify it in EmployeeConfig.Configure:
builder.HasOne<Branch>(e => e.Branch).WithMany(b => b.Employees).HasForeignKey(e => e.BranchId);
That says that the Employee has one Branch, that the Branch has many Employees, and that the dependent entity (Employee) has a foreign key pointing back at the Branch, called BranchId.
Here is how you would specify it in BranchConfig.Configure:
builder.HasMany<Employee>(b => b.Employees).WithOne(e => e.Branch).HasForeignKey(e => e.BranchId);
Which says the same thing: the Branch has many Employees, each of which has one Branch, and that the dependent entity (Employee) has a foreign key called BranchId.
Here is how you would specify it in DbContext.OnModelCreating:
modelBuilder.Entity<Branch>().HasMany<Employee>(b => b.Employees).WithOne(e => e.Branch).HasForeignKey(e => e.BranchId);
or
modelBuilder.Entity<Employee>().HasOne<Branch>(e => e.Branch).WithMany(b => b.Employees).HasForeignKey(e => e.BranchId);
I am working on a rather larger application that tries to follow a layered architecture pattern. On DBAL I use fluently configured NHibernate. Database objects sometimes have associations like these:
public class HeaderDbo
{
public HeaderDbo()
{
Details = new List<DetailDbo>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<DetailDbo> Details { get; set; }
}
public class DetailDbo
{
public virtual int Id { get; set; }
public virtual string DetailName { get; set; }
public virtual HeaderDbo Header { get; set; }
public virtual RelevantObjectDbo RelevantObject { get; set; }
}
public class RelevantObjectDbo
{
public virtual int Id { get; set; }
public virtual string RelevantText { get; set; }
}
Mapping is as follows:
public class HeaderDboMap : ClassMap<HeaderDbo>
{
public HeaderDboMap()
{
Table("Header");
Id(x => x.Id).Column("Id");
Map(x => x.Name);
HasMany(x => x.Details)
.Inverse()
.Cascade.All();
}
}
public class DetailDboMap : ClassMap<DetailDbo>
{
public DetailDboMap()
{
Id(x => x.Id).Column("Id");
Table("Detail");
Map(x => x.DetailName);
References(x => x.Header).Column("HeaderId");
References(x => x.RelevantObject).Column("RelevantObjectId")
.Cascade.SaveUpdate(); //??
}
}
public class RelevantObjectDboMap : ClassMap<RelevantObjectDbo>
{
public RelevantObjectDboMap()
{
Id(x => x.Id).Column("Id");
Table("RelevantObject");
Map(x => x.RelevantText);
}
}
Now, there are application domain entities that the DBOs are mapped to which do not necessarily reflect the database structure one-to-one. For example, Header might stay header, but Detail would, say, form itself from DetailDbo and parts of RelevantObjectDbo. Then the application does its thing over the entities - some transformation of Detail happens, which now needs to be persisted.
Suppose I only affected parts of the Detail entity which need to go into Detail table and don't affect RelevantObject table in any way. It might be a wrong way to think about the model but we also need to be practical about how persisting works. So, say, I would like to only have NHibernate update the Detail table without "touching" anything on the RelevantObject table. This is exactly the question, actually: how do I achieve that?
In reality, of course, the DB model is far bigger and more complicated and so is the application logic. There could be a part of BL that does not deal with the RelevantObject part of the data at all, so even though DBOs are loaded from the db fully, not all the data finds its way into the app model. But to persist the data back to the database - it seems that I need to hydrate the DB model fully and it's not always practical. So, how can we instruct NHibernate to "not touch" RelevantObject - in other words, not update dbo.Detail.RelevantObjectId?
I tried applying different Cascade options to DetailDbo.RelevantObject property, but if it stays at null, NHibernate always wants to set RelevantObjectId to NULL - I suppose, rightfully so.
I don't understand how I can write changes to the data that is relevant to my "part" of the BL without having to load and save half of my database through all the associations.
Thank you!
How are you performing these updates?
If you are not modifying anything on RelevantObject NHibernate will not send an update for that table. For example:
var header = session.Get<HeaderDbo>(1);
header.Details.First().DetailName = "Whatever";
session.Flush();
Should not cause an update to be issued to the RelevantObject table.
I have a self referencing object Cycle:
public class Cycle
{
public Cycle()
{
ParentCycle = this;
ChildCycles = new List<Cycle>{this};
}
public virtual Guid Id { get; set; }
public virtual Cycle ParentCycle { get; set; }
public virtual IList<Cycle> ChildCycles { get; set; }
public virtual int Version { get; set; }
}
With the following mapping:
public class CycleMap : ClassMap<Cycle>
{
public CycleMap()
{
Table("Cycle");
Id(x => x.Id).Column("CycleID");
References(x => x.ParentCycle).Column("ParentCycleID").Not.Nullable();
HasMany(x => x.ChildCycles).KeyColumn("ParentCycleID").Cascade.AllDeleteOrphan().Inverse();
Version(x => x.Version);
}
}
I run the following test code:
var parentCycle = new Cycle();
session.Save(parentCycle);
session.Flush();
session.Delete(parentCycle);
session.Flush();
The creation of the cycle works, but when I try to delete the cycle, I have 2 problems:
Before the delete statement, NHibernate does an update statement to set the ParentCycle to NULL. However this property is not nullable, because if the cycle does not have a parent it references to himself.
When I make the property nullable another problem occurs. Nhibernate does the update which now succeeds but during this update it does not increment the version number. This is a problem for our auditing system. With every update the version should be incremented.
I'm wondering if anyone else had the same problems, the ideal solution would be to stop the update because it is unnecessary. But I cannot seem to achieve this.
Github
I made my test code available on GitHub
It should be easily possible, with setting called inverse="true"
public CycleMap()
{
...
HasMany(x => x.ChildCycles)
.KeyColumn("ParentCycleID")
// the setting, instructing NHibernate that other end will care...
.Inverse()
.Cascade.AllDeleteOrphan();
...
The point is, if NHibernate knows, that the other end (the inversed one) is taking care about the relationship, it does not have to issue
update (with null)
delete
The deep description of the inverse="true" could be found here (I would really suggest to read through, it is really well structured overview, still valid for NHibernate):
inverse = “true” example and explanation by mkyong