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.
Related
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
I am making my way through various todo list tutorials while learning react and entity framework. As some background I have made my way though Microsoft's todo list todo tutorial; although I have replaced the front end part of that with my own front end. It was all working fine, until I've tried to extend it and hit the issue I will outline below.
I have updated the EF model to include private set fields for the added benefits (becoming read only after it is initialised etc). This is shown in the code below.
public class TodoItem
{
public long id { get; private set; }
public string title { get; private set; }
public bool IsComplete { get; private set; }
// Define constructor
public TodoItem(long newId, string newTitle)
{
id = newId;
title = newTitle;
IsComplete = false;
}
public void ToggleComplete()
{
IsComplete = !IsComplete;
}
}
The post action from the controller is shown below. I have included some debug printouts as these show where the field is already showing the title as null.
I believe this is the section of code I am struggling with and would like to know what mistakes I am making or what the best practices are!
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem item)
{
// returns null if model field set to private
System.Diagnostics.Debug.WriteLine("item title: " + item.title);
// Create new item passing in arguments for constructor
TodoItem newItem = new TodoItem(item.id, item.title);
_context.TodoItems.Add(newItem);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetTodoItem), new { id = newItem.id }, newItem);
}
The frontend method (js) where the post request is made is shown below:
const addTodoMethod = (title) => {
// Create new item
const item = {
title: title,
id: Date.now(),
isComplete: false,
}
// Update state
const newTodos = [...todos, item];
setTodos(newTodos);
// Can use POST requiest to add to db
axios.post('https://localhost:44371/api/todo/',
item)
.then(res=> {
console.log("Added item. Title: ", title);
})
.catch(function (error) {
console.log(error);
})}
I hope I've explained the problem well enough. Let me know if there is anything else needed!
I have updated the EF model to include private set fields for the added benefits (becoming read only after it is initialised etc).
There are two problems in what you did. The first one is that the Models must have a parameter-less constructor, and the second one that the properties must be public, both getter and setter.
The best you can do right now is to stop using your database entity for user input and create a ViewModel class:
public class TodoItemViewModel
{
public long id { get; set; }
public string title { get; set; }
public bool IsComplete { get; set; }
}
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItemViewModel model)
{
var item = new TodoItem(item.id, item.title);
...
}
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 have a PurchaseOrder aggregate root which has Two methods FinalizeOrder and CancellOrder, they both record events: OrderFinaziled and OrderCancelled. I am stuck on modeling order repository, can i use those events inside repository pattern to update entity in database? i don't wont after each change to save whole aggregate root, i want to save only the field that was changed, i am using SqlClient, no ORMs.
my Aggregate root base class:
public class AggregateRootBase<TID> : EntityBase<TID>
{
public AggregateRootBase(TID id) : base(id)
{
}
private readonly List<IDomainEvent> recordedEvents = new List<IDomainEvent>();
public IEnumerable<IDomainEvent> GetEvents()
{
return recordedEvents;
}
public void MarkEventsAsProcessed()
{
recordedEvents.Clear();
}
protected void RecordEvent(IDomainEvent #event)
{
recordedEvents.Add(#event);
}
}
PurchaseOrder class (skipped most properties):
public class PurchaseOrder : AggregateRootBase<int>
{
public PurchaseOrder(int id) : base(id)
{
IsFinalized = false;
IsCancelled = false;
}
public bool IsFinalized { get; set; }
public bool IsCancelled { get; set; }
public void FinalizeOrder()
{
IsFinalized = true;
RecordEvent(new OrderFinalized(Id,IsFinalized));
}
public void CancellOrder()
{
IsCancelled = true;
RecordEvent(new OrderCancelled(Id,IsCancelled));
}
}
and repository:
public class PurchaseOrderRepository
{
void Save(PurchaseOrder purchaseOrder)
{
var events = purchaseOrder.GetEvents();
foreach (var evt in events)
{
if(evt.GetType() == typeof(OrderFinalized))
// use event args and update field using SqlCommand
else if (evt.GetType() == typeof(OrderCancelled))
// use event args and Update field Using SqlCommand
}
}
}
i also have an EventDispatcher which dispatches events (Email Notification) after AggregateRoot successful persistence.
If you want to use domain events to save your changes, you are talking about projecting events to your aggregate state. There are some tools that can help with that. What you are trying to do resembles the pattern matching, a usual feature of most functional languages. To make life easier you might want to check something like Projac. We use it for projections and it works very nicely. It also has SQL Server specific implementation.
One example that you can find there:
public class PortfolioProjection : SqlProjection
{
public PortfolioProjection()
{
When<PortfolioAdded>(#event =>
TSql.NonQueryStatement(
"INSERT INTO [Portfolio] (Id, Name) VALUES (#P1, #P2)",
new { P1 = TSql.Int(#event.Id), P2 = TSql.NVarChar(#event.Name, 40) }
));
When<PortfolioRemoved>(#event =>
TSql.NonQueryStatement(
"DELETE FROM [Portfolio] WHERE Id = #P1",
new { P1 = TSql.Int(#event.Id) }
));
When<PortfolioRenamed>(#event =>
TSql.NonQueryStatement(
"UPDATE [Portfolio] SET Name = #P2 WHERE Id = #P1",
new { P1 = TSql.Int(#event.Id), P2 = TSql.NVarChar(#event.Name, 40) }
));
}
}
Then you can initialise projector instance:
_projector = new SqlProjector(
Resolve.WhenEqualToHandlerMessageType(new PortfolioProjection()),
new TransactionalSqlCommandExecutor(
new ConnectionStringSettings(
"projac",
#"Data Source=(localdb)\ProjectsV12;Initial Catalog=ProjacUsage;Integrated Security=SSPI;",
"System.Data.SqlClient"),
IsolationLevel.ReadCommitted));
And then project events:
void Save(PurchaseOrder purchaseOrder) =>
_projector.Project(purchaseOrder.GetEvents());
You might also check the event sourcing pattern although it adds quite lot of complexity.