So, consider the following class:
public class Solution
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
And the following methods
public void DoSomething()
{
new Item originalItem = new Item();
item.Name = "Test";
InserIntoDb(originalItem);
Assert.True(item.Id != 0);
}
public void InserIntoDb(Item item)
{
context.Item.Add(item);
context.SaveChanges();
}
On this case, after insert into the db, EF automatically updates the originalItem with its auto generated ID value.
My problem starts when I add some logic to only add to the db if the Name doesn't exist. If exists the Insert doesn't happen therefore the ID property is not populated.
My question is: Is there a way to make the context.Item (when retrieving all) to automatically update the ID of the originalItem value without having to change the InsertIntoDb method to return an Item and consequently having to add a line like
originalItem = InsertIntoDb(originalItem);
Thanks, in advance
Nope, there is no built in way to update the item with the id from the db if it exists.
You will have to implement a custom approach to make this work. If this is only a small or single amount of items at once, then just attempt to get the named item from the database prior to entry and then for the one(s) not present insert.
If you are doing this for a large batch of items (more than 5000), consider using a stored procedure to do this work on the db server side for query performance.
Related
I have a function in the repository, GetForms, the purpose of the function is to call a stored procedure and return rows with data. Everything is working fine until now.
Function
public IEnumerable<FormBO> GetForms()
{
var id = "1"
var Query= _context.FormBO.FromSqlRaw("dbo.SP_Core #pin_ID={0}", id)
.AsNoTracking().ToList(); //3K line of sp
return Query;
}
Model
public class FormBO
{
[Key]
public int? ID { get; set; }
public int? secondid { get; set; }
......
}
DbContext
Added this code, so context thinks it is a table in the database and, I don't have to do more stuff
public virtual DbSet<FormBO> FormBO { get; set; }
The problem
Whenever we scaffold the database and the db context, it regenerates all the files and code, so it removes the
public virtual DbSet<FormBO> FormBO { get; set; }
And we have to add this line manually is there any way I can change the logic, so I don't have to add this code (DBset<FormBO>) to DbContext every time a dba updates the database...
What I found
I found that if I change the model to ".Database" and FromSqlRaw to ExecuteSqlRaw, but it is just returning the count as int not a list of rows.
public IEnumerable<FormBO> GetForms()
{
var id = "1"
var Query = _context.Database.ExecuteSqlRaw("dbo.SP_Core #pin_ID={0}", id)
.AsNoTracking().ToList(); //3K line of sp
return Query;
}
If it is possible it automatically add the DBSet to context whenever we update the code which I don't think we will able to do.
or
Get the query result without the dbset model and then I will use foreach loop to add it in FormBO model it is just 10 rows
Since the table doesn't actually exist in the database, the built in scaffolding process won't attempt to create it.
However you could probably replace the IScaffoldingModelFactory service, with an implementation that extends RelationalScaffoldingModelFactory, and use the code-first fluent api to define meta data for tables that don't really exist.
You could probably use this type of approach to define types for all table values in the database. Since EF Core 5 is adding support for table values, maybe they'll do it for you, but I haven't tested that.
public class MyModelFactory : RelationalScaffoldingModelFactory
{
public MyModelFactory(
IOperationReporter reporter,
ICandidateNamingService candidateNamingService,
IPluralizer pluralizer,
ICSharpUtilities cSharpUtilities,
IScaffoldingTypeMapper scaffoldingTypeMapper,
LoggingDefinitions loggingDefinitions)
: base(reporter, candidateNamingService, pluralizer, cSharpUtilities, scaffoldingTypeMapper, loggingDefinitions)
{
}
protected override ModelBuilder VisitDatabaseModel(ModelBuilder modelBuilder, DatabaseModel databaseModel)
{
modelBuilder.Entity<FormBO>(entity =>
{
// ...
});
return base.VisitDatabaseModel(modelBuilder, databaseModel);
}
}
services.AddDbContextPool<ContextType>(o =>
{
o.ReplaceService<IScaffoldingModelFactory, MyModelFactory>();
// ...
});
Of course there's an easy answer too. The scaffolded context is a partial class. Just define your other DbSet in another source file.
I'm having trouble updating an item on my database. I've tried several different ways, but nothing seems to work. Here is my latest attempt:
public async Task<Finding> InsertOrUpdateItemAsync(Item localItem)
{
using (var context = new AppDbContext())
{
context.Items.Update(localItem);
context.SaveChanges();
}
}
When SaveChanges executes, I get the following error message: "The instance of entity type ItemStatus cannot be tracked because another instance with the key value ['ItemStatusId: 4'] is already being tracked.
Here are the relevant properties of my Item model:
public class Item
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ItemId { get; set; }
public ItemStatus InitialStatus { get; set; }
public ItemStatus FinalStatus {get; set; }
}
And here are the relevant properties from the ItemStatus class:
public class ItemStatus
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ItemStatusId { get; set; }
public string ItemStatusName{ get; set; }
]
Can anyone tell me what I'm doing wrong? I have checked to make sure I don't have an undisposed context somewhere. Also, when I look at the change tracker, I can see that it is tracking an entry from each row on the ItemStatus table. This doesn't seem right. Shouldn't it only track the ItemStatus that has been assigned to localItem rather than all the related entities?
I am calling the method from here:
savedItem = awaitdataService.InsertOrUpdateFindingAsync(ItemToDisplay);
The various properties of ItemToDisplay are bound to dropdown lists in the UI. I have verified that these properties are being assigned correctly before being sent to the InsertOrUpdateFindingAsync method.
I have also tried a this for the InsertOrUpdate method:
public async Task<Finding> InsertOrUpdateItemAsync(Item localItem)
{
using (var context = new AppDbContext())
{
Item itemFromDb = context.Items.Where(i => i.ItemId == localItem.ItemId).FirstOrDefault();
itemsFromDb = localItem;
context.Items.Update(localItem);
context.SaveChanges();
}
}
You apparently save a localItem that refers to two ItemStatus objects both having ItemStatusId = 4. The cause of the error is that these ItemStatus objects are two instances instead of one. The Update command tries to attach both of these instances to the context.
There are two ways to fix this:
Make sure that if both statuses in Item are identical they both refer to the same ItemStatus object. Depending on where localItem comes from, this may require JSON serializer settings or a modification in the code that constructs the localItem object.
(Preferred) Add the primitive foreign properties InitialStatusId and FinalStatusId to Item and only set these properties, not the references. Then the update is a simple update of scalar properties.
Because
savedItem = awaitdataService.InsertOrUpdateFindingAsync(ItemToDisplay);
this variable is itemtodisplay, you have to read from once and you keep this data in this variable, so your context to database, tracked this record when you read.
public async Task<Finding> InsertOrUpdateItemAsync(Item localItem)
{
using (var context = new AppDbContext())
{
context.Items.Update(localItem);
context.SaveChanges();
}
}
Then here when you sent this variable to there, there is already tracked variable, you tried to track it one more time.
There are 2 solutions:
Read this record with .AsNoTracking so it will not be tracked
When your update method you need to read one more time, like searching with id and keep it in another variable, and assign the parameters which you want to update to sent variable to new read one. then save it so it will not tracked, because there is a new read, actually you read this with tracking parameter because you don't need to add .AsNoTracking here, but it will create a problem because you are trying to change something on same tracked context.
I will add basic code based on my 2. desc.
public async Task<Finding> InsertOrUpdateItemAsync(Item localItem)
{
using (var context = new AppDbContext())
{
var data = context.Items.Where(x => x.Id == localItem.Id).FirstOrDefault();
data.Name = localItem.Name;
context.Items.Update(data);
context.SaveChanges();
}
}
I hope I described it well.
But note: using update method is not the best, because in that example I only want to update name property but it will create query that updates all columns. You should do it with out update, else there is 2 option here, if write update, it says hey EF mark all need to update, so it creates a query that to update all columsn. But this data is already tracked, if you change name like example EF Core knows it so you should only write there, SaveChanges(); or second method is if you want to update only one record, but your record is read with .AsNoTracking, you should access the context, get entries and change the field as modified
public void ContextAttach(TEntity entity)
{
_dataContext.Attach(entity);
_dataContext.Entry(entity).Property(Name).IsModified = true;
}
https://blog.oneunicorn.com/2020/01/17/dontcallupdate/
Is it possible to get the value of the primary key of the entity to be created next? Before it is created?
I tried:
Order newOrder = new Order();
MessageBox.Show(newOrder.orderId.toString());
It showed 0.
Is it possible?
Bigger Picture:
I am trying to build a fast food order management system. I have Order, Item and OrderItem tables. There is a many-to-many relationship between Order and Item and OrderItem table resolves this relationship.
So, when adding an order, I need to add OrderItem s whose orderId field should be populated by the 'Order' just being created, i.e. is not created yet.
EDIT: I use Code-First approach.
EF will take care of it.
So if you have Code First, you could do something like:
class Order
{
[Key]
public int Id { get; set }
public virtual List<OrderItem> Items { get; set; }
public Order()
{
Items = new List<OrderItem>();
}
}
class OrderItem
{
[Key]
public int Id { get; set; }
public string ItemName { get; set; } //of course just a demo property
}
and do something like:
Order order = new Order()
OrderItem item = new OrderItem();
item.ItemName = "Super Burger";
order.Items.Add( item );
context.Orders.Add( order );
context.SaveChanges();
All is well and the keys populated accordingly.
If the key is generated at the database then no, it's not possible. So I see 2 alternatives:
You can just add the entity to your collection, then call dbContext.SaveChanges() to write it to the database and use the id value in your code. (You could do this inside a transaction and rollback the transaction subsequently, or even just remove the record, if needs be.)
You can generate the id value yourself in your code rather than having something generated by the database - perhaps by using a GUID as an ID
There are positives and negatives to each approach depending on how this fits into whatever your 'big picture' is.
UPDATE:
in terms of positives and negatives - the first option is probably closer to what you originally planned. And can be made to work. Arguably would make it easier to set up relationships with other entites. On the flipside you need to hit the database each time you want to get a new id - which could be slower.
It's really a (opinion based!) design decision - either method can be made to work.
So, I have a problem in save data which contains related entities, when I save it a new relation blank is created.
Exemple:
Entities:
public class Project
{
public int Id { get; set; }
public int Code{ get; set; }
public string Description{ get; set; }
public virtual Client Client { get; set; }
}
public class Client
{
public int Id { get; set; }
public int Code { get; set; }
public string Name { get; set; }
}
The Controller GET:
public ActionResult Create()
{
PopulateDropDownClienteList(String.Empty); //Returns to ViewBag to create a combobox .in view
return View();
}
The View:
#Html.DropDownListFor(m => m.Client.Id, new SelectList(ViewBag.Client_Id, "Id", "Name"), new { Name = "Client.Id" });
The Controller POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(string command, Project project)
{
try
{
if (ModelState.IsValid)
{
projectRepository = new ProjeRepository();
Project pro = projectRepository.ReturnByCode(project.Code);
if (pro == null)
projectRepository.Save(project);
else
projectRepository.Update(project);
PopulateDropDownClienteList(String.Empty);
Return View();
}
else
{
return View(project);
}
}
catch (Exception ex)
{
return View();
}
}
So when I save the data, the client is not associated with the project. just creating a new blank Client.
You Project Save code is not updating the entity, it is ADDING a new one all the time.
You should have update logic similar to following grounds -
To Add new FK Entry and associate it with parent record -
var entity = entities.Students.Where(p => p.Id == "2").First();
entity.StudentContact = new StudentContact() { Contact = "xyz", Id = "2" };
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
// other changed properties
entities.SaveChanges();
To update a FK record with new details -
var entity = entities.Students.FirstOrDefault();
entity.StudentContact.Contact = "ABC";
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
entry.Property(e => e.StudentContact.Contact).IsModified = true;
// other changed properties
entities.SaveChanges();
The above code, I have a Student records which has FK relationship with StudentContacts. I updated Contact information of a student and then updated it to database using ATTACH.
You've got a number of issues here, so let me break them down.
First and foremost, do not ever catch Exception (at least without throwing it again). There's two very important things about using try...catch blocks: you should only wrap the code where you're expecting an exception (not nearly your entire method as you've done here), and you should only catch the specific exception you're expecting (not the base type Exception). When you catch Exception, any and every exception that could possibly be generated from your code will be caught, and in this case, simply discarded, which means you really will never know if this code works at all.
Second, you have a fine method that generates a dropdown list of choices, but never store the user's selection anywhere meaningful. To understand why, you need to stop and think about what's happening here. An HTML select element has a string value and a string text or label component. It does not support passing full objects back and forth. I can't see what your PopulateDropDownClienteList method does, but what it should be doing is creating an IEnumerable<SelectListItem>, where each item gets its Text property set to whatever you want displayed and its Value property to the PK of the Client. However, once you have that, you need some property on Project to post back to. Your virtual Client won't work as that needs a full Client instance, which your form will never have. So, you have two choices:
Implement a view model to feed to the view (and accept in the post). In that view model, in addition to all other editable fields, you'll include something like ClientId which will be an int type, and you'll bind this to your drop down list. Once you're in your post method, you map all the posted values to your project instance, and then use the ClientId to look up a client from the database. You then set the resulting client as the value for your Client property and save as usual.
You alter your database a bit. When you just specify a virtual, Entity Framework smartly creates a foreign key and a column to hold that relationship for you behind the scenes. That's great, but in situations like this, where you actually need to access that foreign key column, you're screwed. That way around that is to explicitly define a property to hold that relationship on your model and tell Entity Framework to use that instead of creating its own.
[ForeignKey("Client")]
public int ClientId { get; set; }
public virtual Client Client { get; set; }
With that, you can now directly use ClientId without worrying about filling in Client. You again bind your drop down list to ClientId, but now, you do not need to look up the client explicitly from the database. Entity Framework will just save the ClientId as it should to the database, and then restore the Client based on that when you look up the project again in the future.
I'm having a very odd issue with SubSonic where when I edit a class the database isn't being updated, even when I delete it and regenerate it.
Example: Simple class
public class Customer {
public Guid Id { get; set; }
public string Description { get; set; }
}
Customer c = new Customer() { Id = Guid.NewGuid(), Description = "Toaster" };
var repo = new SimpleRepository("CustomerTest",
SimpleRepositoryOptions.RunMigrations);
repo.Add(c);
If I run this code it works perfectly, creates a table "Customer" and inserts the row for the toaster. However if I decide to change my Customer class to:
public class Customer {
public Guid Id { get; set; }
public string Description { get; set; }
public int Cost { get; set;}
}
And run the same code adding a value for the Cost property the database table remains "Id, Description". If I create a totally new class and past in the Customer fields it will create the table correctly the first time and again any changes dont appear to work.
Any help?
First off all, you should try to figure out if subsonic detects your class definition changes properly.
This code should give you a overview of the statements subsonic want's to execute.
var migrator=new SubSonic.Schema.Migrator(Assembly.GetExecutingAssembly());
var provider=ProviderFactory.GetProvider("CustomerTest");
string[] commands=migrator.MigrateFromModel<Customer>(provider);
commands should contain all changes subsonic wants to make to your database.
You can execute these commands by yourself with:
BatchQuery query = new BatchQuery(provider);
foreach(var s in commands)
query.QueueForTransaction(new QueryCommand(s.Trim(), provider));
//pop the transaction
query.ExecuteTransaction();
(code taken from http://subsonicproject.com/docs/3.0_Migrations).
That said, I suppose commands will be empty in your case.
In that case that could be caused by a statement that is not implemented by the provider you are using (SqlServer/MySQL/SQLite/Oracle). Maybe you should download the SubSonic source and step into the migrator.MigrateFromModel(...) method to see what happens.
Another possible cause (if you use MySQL) could be that your information schema is not up to date. I encountered this problem a while ago. After changing my database and regenerating the DAL with SubSonic 2, my generated code didn't change.
I figured out that the mysql information schema (and subsonic does queries on the information schema) hadn't changed yet.
I solved this by executing FLUSH TABLES which caused the information scheme to reload. I don't know if that's a bug in mysql or desired behaviour but you should try FLUSH TABLES first.