Apologies for the bad title, hard to sum up.
So what is happening is I am have a form that will load data from the database:
JobModel jobModel = Data.GetJobList(model)[0];
JobModel contains a field for a list of "parts" also known as a "PartModel"
public class JobModel
{
...
public List<PartModel> parts { get; set; }
...
}
So when a user loads up the form I save the data before they begin data entry by assigning this global JobModel to refer back to in later segments of the code. Also the partsModel is located here as well
public static JobModel previousJobModel = new JobModel();
public static List<PartModel> partModels = new List<PartModel>();
public void LoadFormData(int JobID)
{
...
JobModel jobModel = Data.GetJobList(model)[0];
partModels = jobModel.parts;
previousJobModel = jobModel;
...
}
Now what happens is that during a segment of code, the previousJobModel.parts becomes overwritten when the DELETE section of code is executed
private void olvJobPartList_RightClick(object sender, BrightIdeasSoftware.CellRightClickEventArgs e)
{
PartModel model = (PartModel)e.Model;
if (model != null)
{
selectedModel = model;
menuStripOLV.Show(Cursor.Position);
}
}
private void deletePartToolStripMenuItem_Click(object sender, EventArgs e)
{
//previousJobModel.parts Count = 7
var itemToRemove = partModels.Single(r => r.PartNumber == selectedModel.PartNumber && r.partID ==
selectedModel.partID);
partModels.Remove(itemToRemove);
//previousJobModel.parts Count = 6
populateOLV();
}
Few notes: I did put a couple breakpoints in the "delete" function, before the removal of the part from the list, previousJobModel is normal, after it gets screwed up.
I am also getting back into the swing of things with coding in general so I may be missing something dumb here.
Also changing other fields within the job causes no issues to the previous job model, only deleting a part from the list
Related
I'm going to chunk this down to as simple a case as I can, but this happens for everything.
I'm basing most of my data model POCO objects on a BaseDataObject defined as follows:
public class BaseDataObject
{
public int Id { get; set; }
public bool Deleted { get; set; }
}
My code-first data model has a Client object:
public class Client : BaseDataObject
{
public string Name { get; set; }
public virtual Category Category { get; set; }
public virtual Category Subcategory { get; set; }
}
The Category object is pretty simple:
public class Category : BaseDataObject
{
public string Name { get; set; }
}
The required Id property exists in the inherited BaseDataObject.
To add entities, I'm using the following repo:
public class DataRepository<TModel, TContext>
where TModel : BaseDataObject
where TContext : DbContext
{
public int AddItem(T item)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
db.Set<T>().Add(item);
db.SaveChanges();
}
}
// These are important as well.
public List<T> ListItems(int pageNumber = 0)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
// Deleted property is also included in BaseDataObject.
return db.Set<T>().Where(x => !x.Deleted).OrderBy(x => x.Id).Skip(10 * pageNumber).ToList();
}
public T GetSingleItem(int id)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
return db.Set<T>().SingleOrDefault(x => x.Id == id && !x.Deleted);
}
}
}
This adds a new client perfectly fine, but there's something weird about my data model here that's causing Entity Framework to also add 2 new Categories every time I add a client based on which categories I'm selecting on my form.
Here's my form's code:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
try
{
BindDropDownList<Category>(CategoryList);
BindDropDownList<Category>(SubcategoryList);
}
// Error handling things
}
}
private void BindDropDownList<TModel>(DropDownList control) where TModel : BaseDataObject
{
var repo = new DataRepository<TModel, ApplicationDbContext>();
control.DataSource = repo.ListItems();
control.DataTextField = "Name";
control.DataValueField = "Id";
control.DataBind();
control.Items.Insert(0, new ListItem("-- Please select --", "0"));
}
private TModel GetDropDownListSelection<TModel>(DropDownList control) where TModel : BaseDataObject
{
var repo = new DataRepository<TModel, ApplicationDbContext>();
int.TryParse(control.SelectedItem.Value, out int selectedItemId);
return repo.GetSingleItem(selectedItemId);
}
protected void SaveButton_Click(object sender, EventArgs e)
{
try
{
var repo = new DataRepository<Client, ApplicationDbContext();
var selectedCategory = GetDropDownListSelection<Category>(CategoryList);
var selectedSubcategory = GetDropDownListSelection<Category>(SubcategoryList);
var name = NameTextBox.Text;
var client = new Client
{
Name = name,
Category = selectedCategory,
Subcategory = selectedSubcategory
};
repo.AddItem(client);
}
// Error handling things
}
Unless there's something wrong with the way I'm creating the relationship here (using the virtual keyword or something maybe) then I can't see any reason why this would add new Categories to the database as duplicates of existing ones based on the selections I make in the drop down lists.
Why is this happening? What have I got wrong here?
The DbSet<T>.Add method cascades recursively to navigation properties which are not currently tracked by the context and marks them as Added. So when you do
db.Set<T>().Add(item);
it actually marks both Client class referenced Category entities as Added, hence SaveChanges inserts two new duplicate Category records.
The usual solution is to tell EF that entities are existing by attaching them to the context in advance. For instance, if you replace repo.AddItem(client); with
using (var db = new ApplicationDbContext())
{
if (client.Category != null) db.Set<Category>().Attach(client.Category);
if (client.Subcategory != null) db.Set<Category>().Attach(client.Subcategory);
db.Set<Client>().Add(item);
db.SaveChanges();
}
everything will be fine.
The problem is that you use generic repository implementation which does not provide you the necessary control. But that's your design decision issue, not EF. The above is EF intended way to handle such operation. How you can fit it into your design is up to you (I personally would eliminate the generic repository anti-pattern and use directly the db context).
It is really hard to judge from your listing because no FK mappings are included nor the base model details are provided.
However, it would appear that the Category that you assigned to client does not have PK set, and (most likely) only has the Name set, and you have no unique IX on that.
So EF has no reasonable way to work out that this is the right category.
One way to sort it is
protected void SaveButton_Click(object sender, EventArgs e)
{
try
{
var repo = new DataRepository<Client, ApplicationDbContext>();
var selectedCategory = GetDropDownListSelection<Category>(CategoryList);
var selectedSubcategory = GetDropDownListSelection<Category>(SubcategoryList);
var name = NameTextBox.Text;
var client = new Client
{
Name = name,
// either
Category = new DataRepository<Category , ApplicationDbContext>().GetSingleItem(selectedCategory.id),
// or, easier (assuming you have FK properties defined on the model)
CategoryId = selectedCategory.Id,
// repeat as needed
Subcategory = selectedSubcategory
};
repo.AddItem(client);
}
// Error handling things
}
In my C# / WPF application, I have a ListView control, which I populate as follows:
private void Load()
{
DbSet<recordHistory> recordHistory = _db.recordHistories;
var query = from cbHistory in recordHistory
orderby cbHistory.id descending
select new { cbHistory.id, cbHistory.Content, cbHistory.Size, cbHistory.DateAdded };
crRecordHistoryList.ItemsSource = query.ToList();
}
The above works as expected. My ListView control is populated with all the saved records from a SQL database.
When I start debugging the application, it executes as expected. However, when I select one of the ListView items (regardless of which item I select) and click on the Remove button, only the first record gets removed from the database and the ListView control.
Intended behavior is for the selected record to be removed from the database & the listview control...
My Remove method
private void Button_Remove_Click(object sender, RoutedEventArgs e)
{
foreach (var record in _db.recordHistories.Local.ToList())
{
Console.WriteLine("Removing Record Id:" + record.id);
_db.recordHistories.Remove(record);
}
_db.SaveChanges();
this.crRecordHistoryList.Items.Refresh();
this.Load();
}
Furthermore, all subsequent item selection and clicking on the remove button result in nothing being removed from database/listview control)
I have also tried the following (just to get the ID), within the Remove method:
Console.WriteLine("Removing Record Id:" + (crRecordHistoryList.SelectedItem as recordHistory).id);
in which case, I get:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
My recordHistory class (auto generated)
using System;
using System.Collections.Generic;
public partial class recordHistory
{
public int id { get; set; }
public string Content { get; set; }
public string Size { get; set; }
public Nullable<int> DateAdded { get; set; }
}
EDIT: I have figured out why it only removes the first record and then nothing else happens (no matter which item is selected)... it is because instead of getting the record from Local (in my foreach statement), I should simply have the following --- which was my initial attempt, trying to get the ID outputted to Console:
private void Button_Remove_Click(object sender, RoutedEventArgs e)
{
recordHistory testRecord = new recordHistory();
testRecord.id = (recordHistory)crRecordHistoryList.SelectedItem;
_db.recordHistories.Attach(testRecord);
_db.recordHistories.Remove(testRecord);
_db.SaveChanges();
this.crRecordHistoryList.Items.Refresh();
this.Load();
}
However, the following line
testRecord.id = (recordHistory)crRecordHistoryList.SelectedItem;
is throwing an error:
Cannot implicitly convert type 'recordHistory' to 'int'
By the way: the above would work perfectly, if I replace the 2nd line with: testRecord.id = 85; for example.
As such, I have tried changing the aforementioned line to the following, to no avail:
testRecord.id = System.Convert.ToInt32(crRecordHistoryList.SelectedItem);
Any ideas how I can remove the selected record?
Kudos to #pinkfloydx33 for pointing me in the right direction. Per his comment, I ventured onto further-research-rabbit-hole which eventually led to me creating a DTO class and modified my Load and Remove methods as follows--
Load method
private void Load()
{
List<HistoryRecordsDTO> records = (from record in _db.recordHistories
orderby record.id descending
select new HistoryRecordsDTO
{
id = record.id,
Content = record.Content,
Size = record.Size,
DateAdded = record.DateAdded
}).ToList();
crRecordHistoryList.ItemsSource = records;
}
Remove method
private void Button_Remove_Click(object sender, RoutedEventArgs e)
{
recordHistory record = new recordHistory();
record.id = (crRecordHistoryList.SelectedItem as HistoryRecordsDTO).id;
_db.recordHistories.Attach(record);
_db.recordHistories.Remove(record);
_db.SaveChanges();
this.crRecordHistoryList.Items.Refresh();
this.Load();
}
And, my DTO class - HistoryRecordsDTO
public class HistoryRecordsDTO
{
public int id { get; set; }
public string Content { get; set; }
public string Size { get; set; }
public Nullable<int> DateAdded { get; set; }
}
Doing the above solved my problem of removing a selected ListView item.
As being a C#/WPF newbie, I am certain that there are much nicer/optimal/better in general ways to do this... I look forward to other answers and learn from it.
I'm working on a messenger program and I have a timer which constantly deletes and adds new list box items so the list box flickers all the time. I'm trying to make the flickering stop. The reason I'm constantly deleting and adding new list box items is because if a friend logs in, it will change there status from offline to online.
Timer code:
private void Requests_Tick(object sender, EventArgs e)
{
LoadData();
}
LoadData() code:
FriendsLb.BeginUpdate();
_S = new Status();
Image Status = null;
FriendsLb.Items.Clear();
try
{
var query = from o in Globals.DB.Friends
where o.UserEmail == Properties.Settings.Default.Email
select new
{
FirstName = o.FirstName,
LastName = o.LastName,
Email = o.Email,
Status = o.Status,
Display = string.Format("{0} {1} - ({2})", o.FirstName, o.LastName, o.Email)
};
newFriendsLb.DataSource = query.ToList();
newFriendsLb.ClearSelected();
FriendsLb.DrawMode = DrawMode.OwnerDrawVariable;
foreach (object contact in query.ToList())
{
string details = contact.GetType().GetProperty("Display").GetValue(contact, null).ToString();
string email = contact.GetType().GetProperty("Email").GetValue(contact, null).ToString();
string status = _S.LoadStatus(email);
if (status == "Online")
{
Status = Properties.Resources.online;
}
else if (status == "Away")
{
Status = Properties.Resources.busy;
}
else if (status == "Busy")
{
Status = Properties.Resources.away;
}
else if (status == "Offline")
{
Status = Properties.Resources.offline;
}
FriendsLb.Items.Add(new Listbox(_A.LoadFriendAvatar(email), Status, details));
}
contact = query.ToList();
FriendsLb.MeasureItem += FriendsLb_MeasureItem;
FriendsLb.DrawItem += FriendsLb_DrawItem;
FriendsLb.EndUpdate();
Is there a way to update the current list box items constantly rather than constantly deleting and adding new ones?
Here's the GUI:
The are several ways to remove the flicker - all basically involve not completely repopulating the list each time. For this, you want to get the current status for the users and simply update the existing list.
In order for the control to see changes to the list items, rather than an anonymous type, you need a User class so that you can implement INotifyPropertyChanged. This "broadcasts" a notice that a property value has changed. You will also need to use a BindingList<T> so those messages get forwarded to the control. This will also allow additions/deletions from the list to be reflected.
You will also need a concrete way to find each user, so the class will need some sort of ID.
public enum UserStatus { Unknown, Online, Offline, Away, Busy }
class User : INotifyPropertyChanged
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Image StatusImage;
private UserStatus status = UserStatus.Unknown;
public UserStatus Status
{
get{return status;}
set{
if (value != status)
{
status=value;
PropertyChanged(this, new PropertyChangedEventArgs("Status"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public override string ToString()
{
return string.Format("{0}, {1}: {2}", LastName, FirstName, Status);
}
}
Then the collection:
private BindingList<User> Users;
private Image[] StatusImgs; // See notes
The BindingList is then used as the DataSource for the control:
Users = GetUserList();
// display the list contents in the listbox:
lbUsers.DataSource = Users;
timer1.Enabled = true;
Updating the user status just involves resetting the Status on each user which has changed. The BindingList<User> will then notify the control to update the display:
private void UpdateUserStatus()
{
// get current list of user and status
var newStatus = GetCurrentStatus();
User thisUser;
// find the changed user and update
foreach (User u in newStatus)
{
thisUser = Users.FirstOrDefault(q => q.Id == u.Id);
// ToDo: If null, there is a new user in the list: add them.
if (thisUser != null && thisUser.Status != u.Status)
{
thisUser.Status = u.Status;
thisUser.StatusImage = StatusImgs[(int)u.Status];
}
}
}
Results:
Note that there is a potential leak in your app. If you drill into the code to get an image from Resources you will see:
internal static System.Drawing.Bitmap ball_green {
get {
object obj = ResourceManager.GetObject("ball_green", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
GetObject() is creating a new object/image each time you call it, your code doesnt show the old one being Disposed() so, it is likely leaking resources.
Since each online user doesn't need their own unique instance (or a new one when the status changes), load them once into a List or array so they can be reused:
// storage:
private Image[] StatusImgs;
...
// populate:
StatusImgs = new Image[] {Resources.ball_black, Resources.ball_green,
Resources.ball_red, Resources.ball_yellow, Resources.ball_delete};
...
// usage:
thisUser.StatusImage = StatusImgs[(int)u.Status];
You could also change it so the User class updates that itself when the Status changes.
Finally, you might want to consider a simple UserControl for the UI rather than what appears to be an owner drawn Listbox.
If you don't want to change your code structure to eliminate the repeated Clear/Reload cycle, you should suspend UI drawing while you are rebuilding your list using;
using(var d = Dispatcher.DisableProcessing())
{
/* your work... */
}
As suggested here In WPF, what is the equivalent of Suspend/ResumeLayout() and BackgroundWorker() from Windows Forms
In my first view model (renamed to MainViewModel) I have a list of ActionViewModels.
In my xaml i have a listbox which is bound to the list, in the listbox i have a template which binds to properties from the ActionViewModel.
So far so good and everything works.
When selecting one of the listitems i navigate to an ActionViewModel and pass the id with it.
The ActionViewModel retrieves information from a static list in memory from which the MainViewModel also retrieved the information to create the list of actionviewmodels.
So far still so good, i can edit the properties, all the bindings do work fine and i'm all happy.
By clicking the save button the information is gathered and stored in the static list.
When i hit the back button i go back to the list, but unfortunately the values showing there are still the same, is there some way to send a command to reload the items in the list? To pass a complete viewmodel as reference to a new ActionViewModel? Or some property which tells the parent 'this viewmodel in your list has been updated'?
I am sure the above text is a bit confusing, so here is some code to clarify it a bit (hopefully)
MainViewModel.cs
private List<ActionViewModel> _actionViewModels;
public List<ActionViewModel> ActionViewModels
{
get { return _actionViewModels; }
set { _actionViewModels = value; RaisePropertyChanged(() => ActionViewModels); }
}
private Cirrious.MvvmCross.ViewModels.MvxCommand<int> _navigateToAction;
public System.Windows.Input.ICommand NavigateToAction
{
get
{
_navigateToAction = _navigateToAction ?? new Cirrious.MvvmCross.ViewModels.MvxCommand<int>((action) => NavigateToTheDesiredAction(action));
return _navigateToAction;
}
}
private void NavigateToTheDesiredAction(int action)
{
ShowViewModel<ActionViewModel>(new { id = action });
}
// Get DTOs from server or from cache and fill the list of ActionViewModels
public async Task Load()
{
ActionService actionService = new ActionService();
List<ActionViewModel> actionViewModels = new List<ActionViewModel>();
MyActions = await actionService.GetMyActions();
foreach (ActionDTO action in MyActions)
{
ActionViewModel actionViewModel = new ActionViewModel();
await actionViewModel.Load(action.id);
actionViewModels.Add(actionViewModel);
}
ActionViewModels = actionViewModels;
}
ActionViewModel.cs
public int ID
{
get { return TheAction.id; }
set { TheAction.id = value; RaisePropertyChanged(() => ID); }
}
public string Title
{
get { return TheAction.Title; }
set { TheAction.Title = value; RaisePropertyChanged(() => Title); }
}
public async Task Load(int actionId)
{
ActionDTO TheAction = await actionService.GetAction(actionId);
this.ID = TheAction.id;
this.Title = TheAction.Title;
}
private Cirrious.MvvmCross.ViewModels.MvxCommand _save;
public System.Windows.Input.ICommand Save
{
get
{
_save = _save ?? new Cirrious.MvvmCross.ViewModels.MvxCommand(PreSaveModel);
return _save;
}
}
private void PreSaveModel()
{
SaveModel();
}
private async Task SaveModel()
{
ValidationDTO result = await actionService.SaveAction(TheAction);
}
ActionService.cs
public static List<ActionDTO> AllActions = new List<ActionDTO>();
public async Task<ActionDTO> GetAction(int actionId)
{
ActionDTO action = AllActions.FirstOrDefault(a => a.id == actionId);
if (action == null)
{
int tempActionId = await LoadAction(actionId);
if (tempActionId > 0)
return await GetAction(actionId);
else
return new ActionDTO() { Error = new ValidationDTO(false, "Failed to load the action with id " + actionId, ErrorCode.InvalidActionId) };
}
return action;
}
private async Task<int> LoadAction(int actionId)
{
ActionDTO action = await webservice.GetAction(actionId);
AllActions.Add(action);
return action.id;
}
public async Task<ValidationDTO> SaveAction(ActionDTO action)
{
List<ActionDTO> currentList = AllActions;
ActionDTO removeActionFromList = currentList.FirstOrDefault(a => a.id == action.id);
if (removeActionFromList != null)
currentList.Remove(removeActionFromList);
currentList.Add(action);
AllActions = currentList;
return await webservice.SaveAction(action);
}
There are 3 ways I can think of that would allow you to do this.
The ActionService could send out some sort of notification when data changes. One easy way to do this is to use the MvvmCross Messenger plugin. This is the way the CollectABull service works in CollectionService.cs in the N+1 days of mvvmcross videos (for more info watch N=13 in http://mvvmcross.wordpress.com)
This is the approach I generally use. It has low overhead, uses WeakReferences (so doesn't leak memory), it is easily extensible (any object can listen for changes), and it encourages loose coupling of the ViewModel and Model objects
You could implement some kind of Refresh API on the list ViewModel and could call this from appropriate View events (e.g. ViewDidAppear, OnNavigatedTo and OnResume).
I don't generally use this approach for Refreshing known data, but I have used it for enabling/disabling resource intensive objects - e.g. timers
For certain shape of model data (and especially how often it changes), then I can imagine scenarios where this approach might be more efficient than the messenger approach.
You could extend the use of INotifyPropertyChanged and INotifyCollectionChanged back into your model layer.
I've done this a few times and it's worked well for me.
If you do choose this approach, be careful to ensure that all Views do subscribe to change events using WeakReference subscriptions such as those used in MvvmCross binding - see WeakSubscription. If you didn't do this, then it could be possible for the Model to cause Views to persist in memory even after the UI itself has removed them.
I'm trying to create a procedure for undo/redo with entity framework.
I thought of creating a class like this:
public class multiContext
{
public int _id { get; set; }
public undoEntities _context { get; set; }
}
and for each modification create a new multiContext
private void btnSendB_Click(object sender, RoutedEventArgs e)
{
multiContext nContext = new multiContext { _id = multiContextManager.getEntityID(listEntities), _context = new undoEntities};
listEntities.Add(nContext);
foreach (TB1 item in gridA.SelectedItems)
{
item.Status = "B";
nContext._context.Entry(item).State = System.Data.EntityState.Modified;
nContext._context.SaveChanges();
}
refreshGrids();
}
but the problem is that when i SaveChanges(), it change all the context in the list.
how can I save only the actual.
thanks in advance
I thought of this same idea. You have to store each modification in a separate context in stacks for Undo then Redo. Before adding it in, call
this.DBContext.Entry(item).State = System.Data.EntityState.Modified;
Then when you get it out of the undo or redo stack you just have to call this.DBContext.SaveChanges() then reload the textboxes with the new context.