SubmitChanges not "submitting" in LINQ to SQL - c#

I'm creating an application using WPF, MVVM and LINQ to SQL. I have a collection of notes on an object of type Calculation. I have therefore created a ViewModel-class for this called vmCalculation. My problem is, that when I try to add a note to this object and submit the changes the "note" isn't submittet to the database.
Content of vmCalculation
public class vmCalculation : vmBase
{
Calculation calc;
public ObservableCollection<Note> Notes { get; private set; }
public vmCalculation(Calculation calc)
{
this.calc = calc;
Notes = new ObservableCollection<Note>();
foreach (var n in calc.Notes) Notes.Add(n);
}
public void AddNote()
{
Notes.Add(new Note
{
NoteText = "New note",
NoteType = 1
});
}
internal void Save()
{
foreach (var n in Notes.Where(n => n.NoteId == 0))
calc.Notes.Add(n);
}
}
Method in vmNotes (ViewModel for the "NoteWindow")
public void SaveChanges()
{
CurrentCalc.Save();
DC.SubmitChanges();
}
CurrentCalc is a property that gets/sets a vmCalculation that I use in the databinding (binding a DataGrid to CurrentCalc.Notes).
When I run AddNote() on CurrentCalc the view is updated just fine with a "New note"-note. But, when I run SaveChanges() the note isn't written to the database.
Any thoughts on this problem?
A possible cause for the the problem could be, that I don't initialize the DataContext (DC) in vmNotes. I get the DataContext from another ViewModel so that I don't destroy the MVVM-structure.

You must add your new entities to the datacontext before you submit it.
Example:
DC.Notes.InsertOnSubmit(NewNote);
DC.SubmitChanges();

Thought of a possible solution for my problem.
I updated the SaveChanges()-method on the vmNotes class a bit.
public void SaveChanges()
{
var newNotes = currentCalc.Notes.Where(n => n.NoteId == 0);
DC.Notes.InsertAllOnSubmit(newNotes);
DC.SubmitChanges();
}
}
UPDATE 03/09/2011:
Above code is not needed anyway.
I discovered that I had multiple (and static) instances of my DataModel-class.
I cut away some of these and now my original code works just fine!

Related

C# Winforms .NET v4.5.2: best approach for bound objects/controls to save & display updated values

I have some objects which are loaded from an SQLite database into a separate list. The user can select an item from that list via combobox and edit its data in a subform. I work with Binding so that the list and its item is immediately updated on every change. Furthermore, I have a LastModified field (DateTime) in order to see the time of the last change which is set via my item SaveToDB() method.
Now, I am wondering how to handle the database update as well as the display of updated bound values correctly and have 3 open questions:
How am I able to store the old item values in order to compare those with the final edit (after leaving the edit control, like a TextBox) without reading the database entry again? As all bound variables are immediately changed with the edit I think I would need a copied item object when the object is loaded and saved inbetween but which is not bound itself. How can I create such a copy?
What is the best event to fire the comparison and database update? I think it is Validated() but I am not sure. It definitely is not the Changed() event as here a database update would be triggered after every keystroke, even when the changes are undone again before leaving the edit control (for which I want to use the comparison in point 1 to get rid of unnecessary database updates).
Why is my bound DateTime label not updated when I call the entity SaveToDB() method? Do you need to rebind every control after each code internal change of object properties? Seriously? I find it also a bit stupid to explicitely update the combobox.Text while the updated data is correctly displayed when I dropdown the combobox. Really strange behaviour.
Code:
public class Entity {
public int ID { get; set; }
public string DateTime { get; set; }
public string Name { get; set; }
...
public void SaveToDB() {
DateTime = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
if (DB.Read(QueryRead()) == null) {
DB.Write(QueryAdd());
} else {
DB.Write(QueryUpdate());
}
}
}
public class Entities : SortableBindingList<Entity> {
...
}
public class EntityStore {
public Entities Entities;
public BindingSource Source; // used for combobox.datasource in selector-form
// singleton
...
public EntityStore() {
Entities = new Entities();
Source = new BindingSource() { DataSource = Entities };
ReadAllFromDB(); // fills Entities
}
...
public void Update(Entity entity) {
entity.SaveToDB();
}
}
public partial class FormSelector : Form {
// FormEntity gets the selected entity via its parent form:
FormEntity formEntity;
...
cbxEntity.DataSource = EntityStore.Current.Source;
formEntity.SetEntity(EntityStore.Current.Entities.FirstOrDefault(x => x.ID == ((Entity)((GridViewRowInfo)(cbxEntity.SelectedItem)).DataBoundItem).ID));
...
}
public partial class FormEntity : Form {
Entity entity;
...
public void SetEntity(Entity entity) {
this.entity = entity;
tbxID.DataBindings.Add(new Binding("Text", entity, "ID", false));
lblDateTime.DataBindings.Add(new Binding("Text", entity, "DateTime", false));
tbxName.DataBindings.Add(new Binding("Text", entity, "Name", false));
}
private void tbxName_Validated(object sender, EventArgs e) {
// missing comparison logic
// ...
// I could also directly call entity.SaveToDB(); here but I would like
// to keep the logic flow separated as follows:
// DB <- Entity <- EntityStore <- Form(s)
EntityStore.Current.Update(entity);
}
}
Sidenote/Question: I also stumbled over the INotifyPropertyChanged event but I do not understand if its used in winforms and for what purpose.
To prevent a stack overflow exception by implementing INotifyPropertyChanged you additionally need to use private properties when getting and setting public properties.

Repository remove method not working - Object not found

public class Hardware
{
public int id { get; set; }
public int Nodes { get; set; }
public int Repeaters { get; set; }
public int Hubs { get; set; }
}
public abstract class Repositories<T> where T:class
{
//where T:class fixes the issue with .SET
//Repository class constructor once initialized will instantiate datamodel
//Manufacturing data model instantiate because it models table
ManufacturingDataModel MDM;
public Repositories( ManufacturingDataModel mdm)
{
MDM = mdm;
}
public List<T> GetHardware()
{
//Creating new list and adding data from db in it
List<T> data = new List<T>();
foreach(var i in MDM.Set<T>())
{
data.Add(i);
}
return data;
}
public void AddHardware(T item)
{
MDM.Set<T>().Add(item);
MDM.SaveChanges();
}
public void RemoveHardware(T item)
{
MDM.Set<T>().Remove(item);
MDM.SaveChanges();
}
public void UpdateHardware(T item)
{
MDM.Set<T>().AddOrUpdate(item);
MDM.SaveChanges();
}
public class Test : Repositories<Hardware>
{
public Test(ManufacturingDataModel mdm) : base(mdm)
{
}
static void Main(string[] args)
{
ManufacturingDataModel MDM = new ManufacturingDataModel();
Test t = new Test(MDM);
Hardware hardware = new Hardware();
hardware.Nodes = 55;
hardware.Repeaters = 46;
hardware.Hubs = 82;
t.AddHardware(hardware); // WORKS
t.RemoveHardware(hardware); //DOES NOT WORK
I'm trying to make a working repository to add/remove items from my db table. The issue I'm facing is that the add method works perfectly but the remove does not. The issue with the remove method as shown in the picture is that the object does not exist.
I've tried different things such as change my remove method to DeleteObject and Attach but nothing seems to be working.
Any ideas?
ADO --> code first --> EF --> Repo class
If you want make it work, once the object has been inserted retrieve it from the database and pass it in the RemoveHardware method.
As you are passing an object without the primary key, you are getting that error.
You have first to fetch the item you want to remove and the remove it:
public void RemoveHardware(T item)
{
var dbItem = MDM.Set<T>().FirstOrDefault(x=>x.Id == item.Id);
if(dbItem!=null)
{
MDM.Set<T>().Remove(dbItem);
MDM.SaveChanges();
}
}
Update
As it is stated here, DbSet.Remove
Marks the given entity as Deleted such that it will be deleted from
the database when SaveChanges is called. Note that the entity must
exist in the context in some other state before this method is called.
In your case the item is not in your context and this is the reason you see the following error:
The object cannot be deleted because it was not found in the ObjectStateManager
If the item was in the context then as correctly Rainman has pointed out in his comment below, we woulnd't have to make the extra roundtrip to the database to fetch the item.
You need to set unique Id (mostly primary key) for Hardware entity.
It is something like that;
Hardware hardware = new Hardware();
hardware.Id = 1 //Set your unique Id here
hardware.Nodes = 55; //Not necessary
hardware.Repeaters = 46; //Not necessary
hardware.Hubs = 82; //Not necessary
RemoveHardware(hardware);
// So edit your RemoveHardware method too like this
public void RemoveHardware(T item)
{
MDM.Set<T>().Attach(item);
MDM.Set<T>().Remove(item);
MDM.SaveChanges();
}

ObservableCollection not returning the new data after it is set

When populating an observable collection, I can see that the "return" is not being called when I "set" the new data in the collection. It does work if I set the data from a different location in the program so I must be not understanding some nuance of the way it works. The part that works is when I take out the commented code under "This works", "ChooseFile()" does not. In the debugger I can see the OptionsToChoose has data in both cases. When it works the XAML is updated correctly.
class ScripterViewModel : BindableBase
{
public ScripterViewModel()
{
ScripterModel scripterModel = new ScripterModel();
ObservableCollection<string> tabsChoice = new ObservableCollection<string>();
tabsChoice.Add("Tabs");
tabsChoice.Add("Buttons");
Tabs = tabsChoice;
this.OpenFileBtn = new DelegateCommand(chooseFile, canChooseFile).ObservesProperty(() => OpenFile);
this.SaveFileBtn = new DelegateCommand(saveFile, canSaveFile).ObservesProperty(() => SaveFile);
//This works
//var myJSONDoc = JsonConvert.DeserializeObject<JSONclass>(File.ReadAllText(#"C:\Users\mike\Documents\Haas\Scripter\settings.json"));
//OptionsToChoose = new ObservableCollection<Tabbed>(myJSONDoc.TabbedBtns);
}
public void chooseFile()
{
var myJSONDoc = JsonConvert.DeserializeObject<JSONclass>(File.ReadAllText(#"C:\Users\mike\Documents\Haas\Scripter\settings.json"));
OptionsToChoose = new ObservableCollection<Tabbed>(myJSONDoc.TabbedBtns);
}
public ObservableCollection<Tabbed> _optionsToChoose = new ObservableCollection<Tabbed>();
public ObservableCollection<Tabbed> OptionsToChoose
{
get
{
return _optionsToChoose;
}
set
{
_optionsToChoose = value;
}
}
}
When you are creating the OptionsToChoose in the constructor it will be initialized when the viewmodel is used by the view.
In the example that is not working, you are just replacing the ObservableCollection with a new one instead clearing it and adding the items. Therefore you need to notify that the property has been changed like V.Leon pointed out in his answer.
Or just clear the existing collection and populate it with the values from the json.
var myJSONDoc = JsonConvert.DeserializeObject<JSONclass>(File.ReadAllText(#"C:\Users\mike\Documents\Haas\Scripter\settings.json"));
OptionsToChoose.Clear();
foreach (var item in myJSONDoc.TabbedBtns)
{
OptionsToChoose.Add(item);
}
You are not raising PropertyChanged event in the setter of OptionsToChoose. You already extend BindableBase, so raising PropertyChanged event can be done by replacing your current OptionsToChoose property implementation with the following:
public ObservableCollection<Tabbed> OptionsToChoose
{
get
{
return _optionsToChoose;
}
set
{
SetProperty(ref _optionsToChoose, value);
}
}
See BindableBase.SetProperty Method
Ideally, you should not change the whole reference of ObservableCollection after it is binded. Instead clear items in it and then add new items in it.
public ObservableCollection<Tabbed> _optionsToChoose = new ObservableCollection<Tabbed>();
public ObservableCollection<Tabbed> OptionsToChoose
{
get
{
return _optionsToChoose;
}
}
OptionsToChoose.Clear();
OptionsToChoose.Add(foo);
As has already been brought up, given your code you would need to make the property for your collection raise PropertyChanged if you were resetting the collection. That said ObservableCollection is really not an ideal collection type to use. What I would recommend is including MvvmHelpers in your project and using the ObservableRangeCollection
public class MyPageViewModel : BindableBase
{
public MyPageViewModel()
{
OptionsToChoose = new ObservableRangeCollection<Tabbed>();
SomeCommand = new DelegateCommand(OnSomeCommandExecuted);
}
public DelegateCommand SomeCommand { get; }
public ObservableRangeCollection<Tabbed> OptionsToChoose { get; }
private void OnSomeCommandExecuted()
{
// get some updated data
IEnumerable<Tabbed> foo = DoFoo();
OptionsToChoose.ReplaceRange(foo);
}
}
You get a couple of benefits there. One you're not allocating and deallocating your collection. Also the ObservableRangeCollection updates the full list before raising PropertyChanged or CollectionChanged events this results in few UI notifications and better app performance.

Query ObservableCollection with Linq MVVM

I want to create a query with linq on my ObservableCollection but it doesn't really work how T tried it.
I have a Model Entry which has {note, information, isActive} as parameters. So I now want to simply just get all the Entries where isActive is true. I don't use it on my dataprovider (once the data gets loaded) because I need to load every entry into the program.
So I thought about to override the getter inside my entries ObservableCollection:
public ObservableCollection<Note> _entries { get; set; }
public ObservableCollection<Note> entries
{
get
{
return new ObservableCollection<Note>(from entry in this._entries
where entry.isActive == true
select entry);
}
set { this._entries = value; }
}
But as you might guess this doesn't work.
Regards
Try
get
{
List<Notes> list = _entries.Where(e=>e.isActive).ToList();
return new ObservableCollection<Note>(list) ;
}
Rather than editing it in the get, try updating the refinedEntries in the entries' setter. My Linq statement may need work but it encapsulates what I'm trying to suggest.
Essentially keep a copy of everything even the inactive records in entries and another collection to contain only the active records. In this case I'm calling it refinedEntries.
private ObservableCollection<Note> _entries;
public ObservableCollection<Note> entries
{
get{return _entries;}
set
{
_entries = value;
RefinedEntries = new ObservableCollection(_entries.Where(e=>e.isActive).Select(e => e));
}
}
public ObservableCollection<Note> refinedEntries {get;set;}
I would also suggest updating refinedEntries when CollectionChangedEvent fires. In this case the only time refinedEntries is updated is when entries is set to a new instance.
When you instantiate an new collection for entries, subscribe to its CollectionChangedEvent. For example if you instantiate the collection in the Model's constructor you could use the following..
entries = new ObservableCollection<Note>();
entries.CollectionChangedEvent += new NotifyCollectionChangedEventHandler((sender,args) =>
{
RefinedEntries = new ObservableCollection(_entries.Where(e=>e.isActive).Select(e => e));
//Notify the UI that an update has been made.
OnPropertyChanged("RefinedEntries");
});

An object with the same key already exists in the ObjectStateManager. when using AsNoTracking

There are many errors here in SO, but this scenario I think its different.
I had some code that was working fine, I update one entity, etc. But the problem is that it was being cached, when I disabled caching with AsNoTracking(), then it started to throw this exception;
What I do its the following:
1. In the page I get a EcoBonusRequest.
2. The user clicks a button and it modifies the currentstatus proeprty, and it addsa a list item to a collection of workflowhistories, (an associated collection of the ecobonusrequestobject).
3. I call the udpate method, setting state to modified, here it throwed the exception.
Here is my code>
EcoBonusRequestRepository.cs
public void UpdateEcoBonusRequest(EcoBonusRequest ecoBonusRequest)
{
_context.EcoBonusRequests.Attach(ecoBonusRequest);
_context.Entry(ecoBonusRequest).State = EntityState.Modified;
}
EcoBonusRequestBL
public void Update(EcoBonusRequest ecoBonusRequest)
{
DALFacade.UpdateEcoBonusRequest(ecoBonusRequest);
}
EcoBonusRequest Page
public void ChangeStatus(EcoBonusRequest ecoBonusRequest, string stepname, string who, string choosenoption)
{
ecoBonusRequest.WorkflowHistories = new List<WorkflowHistory>
{
new WorkflowHistory()
{
Date = DateTime.Now,
What = choosenoption,
StepName = stepname,
Who = who
}
};
ecoBonusRequest.CurrentStatus = _currentStepBlo.StepName;
var ecoBonusRequestBL = new EcoBonusRequestBL();
ecoBonusRequestBL.Update(ecoBonusRequest);
}
This is how I get the object first: (I am not going to paste all code from all layers here)
protected override void OnInit(EventArgs e)
{
var requestBaseId = RequestBaseId;
if (requestBaseId != null)
{
EcoBonusRequest request = GetDBRequest(requestBaseId.Value);
In the dal
public IQueryable<EcoBonusRequest> FindEcoBonusRequests(System.Linq.Expressions.Expression<Func<EcoBonusRequest, bool>> predicate)
{
return _context.EcoBonusRequests.AsNoTracking().Where(predicate);
}
Yeah this is a fun one, the issue here comes from trying to re-attach an entity which is already attached to the context. This can come from a logic error or perhaps you are reusing your context without clearing the attached objects. There is a local collection on the database which you can use to see what items are currently attached to the context.
ctx.YourEntityCollection.Local

Categories

Resources