Entity Framework Core not saving updates - sql-server

I have a paintings web app that uses ASP.NET Core, Angular, EF Core, SQL Server, AutoMapper with a repository pattern.
The issue is that when I try to update a single painting from the painting table, it does not save to the database. I tried other tables in this same method to see if it was a problem with the flow but they save successfully.
Through swagger I call the put method, this calls the painting controller, goes into the repository, and the repository returns the updated object but when I go to the database nothing updates. If I call the get action from swagger I also DO NOT see the updates.
When I add breakpoints to see the data everything looks fine from start to end but it just does not save to the database. To test I even tried to remove auto mapper logic and manually created an object inside of the update method and set the existing object properties to these hard coded values to see it was the incoming data but still no luck. Again, for testing I tried updating other tables and those worked.
Controller
[HttpPut("{paintingId:int}")]
public async Task<IActionResult> UpdatePaintingAsync(int paintingId, [FromBody] UpdatePaintingRequest updatePaintingRequest)
{
try
{
if (await repository.Exists(paintingId))
{
var updatedPaiting = await repository.UpdatePainting(paintingId, mapper.Map<DataModels.Painting>(updatePaintingRequest));
if (updatedPaiting != null)
{
return Ok(updatePaintingRequest);
}
}
return NotFound();
}
catch (Exception ex)
{
logger.LogError($"Failed to update painting: {ex}");
return BadRequest("Failed to update painting");
}
}
Update method from repository
public async Task<Painting> UpdatePainting(int paintingId, Painting request)
{
var existingPainting = await GetPaintingByIdAsync(paintingId);
if (existingPainting != null)
{
existingPainting.Name = request.Name;
existingPainting.Description = request.Description;
existingPainting.ImageUrl = request.ImageUrl;
existingPainting.IsOriginalAvailable = request.IsOriginalAvailable;
existingPainting.IsPrintAvailable = request.IsPrintAvailable;
existingPainting.IsActive = request.IsActive;
await context.SaveChangesAsync();
return existingPainting;
}
return null;
}
Get painting to update
public async Task<Painting> GetPaintingByIdAsync(int paintingId)
{
return await context.Painting
.Include(x => x.PaintingCategories)
.ThenInclude(c => c.Category)
.AsNoTracking()
.Where(x => x.PaintingId == paintingId)
.FirstOrDefaultAsync();
}
Model (exact same on DAO and DTO)
public class Painting
{
public int PaintingId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string ImageUrl { get; set; }
public bool IsOriginalAvailable { get; set; }
public bool IsPrintAvailable { get; set; }
public bool IsActive { get; set; }
public ICollection<PaintingCategory> PaintingCategories { get; set; }
}
Context
public class JonathanKrownContext : DbContext
{
public JonathanKrownContext(DbContextOptions<JonathanKrownContext> options) : base(options)
{
}
public DbSet<Painting> Painting { get; set; }
}
ModelBuilder.Entity
modelBuilder.Entity("JonathanKrownArt.API.DataModels.Painting", b =>
{
b.Property<int>("PaintingId")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Description")
.HasColumnType("nvarchar(max)");
b.Property<string>("ImageUrl")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<bool>("IsOriginalAvailable")
.HasColumnType("bit");
b.Property<bool>("IsPrintAvailable")
.HasColumnType("bit");
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.HasKey("PaintingId");
b.ToTable("Painting");
});

Your problem is that you used AsNoTracking when fetching the entity, and thus, context doesn't keep track of the changes anymore. So you need either to attach it before saving or remove AsNoTracking.
If you don't want to attach the entity, you need to change GetPaintingByIdAsync to this:
public async Task<Painting> GetPaintingByIdAsync(int paintingId)
{
return await context.Painting
.Include(x => x.PaintingCategories)
.ThenInclude(c => c.Category)
.Where(x => x.PaintingId == paintingId)
.FirstOrDefaultAsync();
}
If you want to keep AsNoTracking then in your UpdatePainting you need to add:
context.Painting.Update(existingPainting);
before you call save.
Update method does the following:
Begins tracking the given entity in the Modified state such that it
will be updated in the database when SaveChanges() is called.
So change your method to this:
public async Task<Painting> UpdatePainting(int paintingId, Painting request)
{
var existingPainting = await GetPaintingByIdAsync(paintingId);
if (existingPainting != null)
{
existingPainting.Name = request.Name;
existingPainting.Description = request.Description;
existingPainting.ImageUrl = request.ImageUrl;
existingPainting.IsOriginalAvailable = request.IsOriginalAvailable;
existingPainting.IsPrintAvailable = request.IsPrintAvailable;
existingPainting.IsActive = request.IsActive;
context.Painting.Update(existingPainting);
await context.SaveChangesAsync();
return existingPainting;
}
return null;
}

I think using AsNoTracking() is a good practice and you should use it wherever you can but in case of Update you need to attach the entity to context by this EF will know this entity should be updated.
So for solve your problem just add one line to code like this:
//other lines
context.Attach(existingPainting); //<--- by this line you tell EF to track the entity
context.Painting.Update(existingPainting);
await context.SaveChangesAsync();

Related

EF Core 6 "normal" update method doesn't respect RowVersion expected behavior?

I have a .NET6 API project that allows users to fetch resources from a database (SQL Server), and update them on a web client, and submit the updated resource back for saving to db. I need to notify users if another user has already updated the same resource during editing. I tried using EF IsRowVersion property for this concurrency check.
I noticed that "normal" update procedure (just getting the entity, changing properties and saving) does not respect the RowVersion expected behavior. But if I get the entity using AsNoTracking and use the db.Update method, the concurrency check works as expected. What could be the reason, and is the db.Update the only way to force the RowVersion check? That method has the downside that it tries to update every property, not just those that have changed. Simplified and runnable console app example below:
using Microsoft.EntityFrameworkCore;
Guid guid;
using (PeopleContext db = new())
{
Person p = new() { Name = "EF", Age = 30 };
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
db.People.Add(p);
await db.SaveChangesAsync();
guid = p.Id;
}
using (PeopleContext db = new())
{
Person p = await db.People.FirstAsync(x => x.Id == guid);
p.Name = "FE";
p.RowVersion = Convert.FromBase64String("AAAAAADDC9I=");
await db.SaveChangesAsync(); // Does not throw even though RowVersion is incorrect
}
using (PeopleContext db = new())
{
Person p = await db.People.AsNoTracking().FirstAsync(x => x.Id == guid);
p.Name = "EFFE";
p.RowVersion = Convert.FromBase64String("AAAAAAGGC9I=");
db.People.Update(p);
await db.SaveChangesAsync(); // Throws DbUpdateConcurrencyException as expected, but updates all properties
}
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
}
public class PeopleContext : DbContext
{
public PeopleContext(){}
public DbSet<Person> People => Set<Person>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=EFRowVersionDb;Integrated Security=True;");
optionsBuilder.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information);
optionsBuilder.EnableSensitiveDataLogging();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>(entity =>
{
entity.Property(e => e.RowVersion)
.IsRequired()
.IsRowVersion();
});
}
}
I solved the problem by overriding the SaveChangesAsync method like this:
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
foreach (var item in ChangeTracker.Entries().Where(x=>x.State == EntityState.Modified))
{
item.OriginalValues["RowVersion"] = item.CurrentValues["RowVersion"];
}
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
I override that signature method cause the one without boolean calls that method. Same thing on sync version.

Entity framework 6 not updating foreign key relation

Hello I have a strange issue here. I have a Project model that has a foreign key to the Company model. The thing is that when I attach my Project property in order to update it, then it updates all other primitive fields, except for the Company.
The Project model looks like this:
public class Project
{
[Key]
public int ID { get; set; }
[Index(IsUnique = true)]
public string Name { get; set; }
public virtual Company Company { get; set; }
public bool ExportProjectName { get; set; }
public DateTime CreatedAt { get; set; }
}
Then what I do, is I have a ProjectsViewModel, that gets all Projects from the Database. I wrap each of them then in a ProjectViewModel which exposes some fields of the Project and also has a saving method. I think the ProjectsViewModel implementation may not be that important so I'll paste only the two lines that populate the ProjectViewModels:
var projects = dbcontext.Projects.Include(x => x.Company).ToList().Select(x => new ProjectViewModel(x));
this.ListOfProjects = new ObservableCollection<ProjectViewModel>(projects);
Then I have the ProjectViewModel. Take a look at the SaveProject method:
public class ProjectViewModel : INotifyPropertyChanged
{
private Project _project;
public Project Project
{
get { return _project; }
set
{
_project = value;
NotifyPropertyChanged("Project");
}
}
public int ID
{
get { return Project.ID; }
set
{
Project.ID = value;
NotifyPropertyChanged("ID");
}
}
public string Name
{
get { return Project.Name; }
set
{
Project.Name = value;
NotifyPropertyChanged("Name");
}
}
public Company Company
{
get { return Project.Company; }
set
{
Project.Company = value;
NotifyPropertyChanged("Company");
}
}
public ProjectViewModel(Project project)
{
this.Project = project;
}
public void SaveProject()
{
using (DbContext dbcontext = new DbContext())
{
// At this state this.Project and this.Company exist in the database
dbcontext.Companies.Attach(this.Company);
dbcontext.Projects.Attach(this.Project);
dbcontext.Entry(this.Project).State = EntityState.Modified;
dbcontext.SaveChanges();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
}
And that's it. I have the SaveProject bound to a command, which I just trigger.
And then in the logs I can only see such a query:
Opened connection at 08/12/2018 17:47:21 +01:00
Started transaction at 08/12/2018 17:47:21 +01:00
UPDATE "public"."Projects" SET "Name"=#p_0,"ExportProjectName"=#p_1,"CreatedAt"=#p_2 WHERE "ID" = #p_3
So all properties are there, except for the Company. If that's important - I'm using PostgreSQL with NpgSql. I saw many question on SO in regards to the related object not being updated itself, but I couldn't find any question where the relation would be only broken. Hope somebody can help!
EDIT:
BTW the code below would work, but I do not want to assign all properties by myself and would want to avoid getting the object from the DB one more time. And I want to know, why the relation is not updated in the first case, as it doesn't make sense for me.
dbcontext.Companies.Attach(this.Company);
var p = dbcontext.Projects.Single(x => x.ID == this.ID);
p.Name = this.Name;
p.Company = this.Company;
dbcontext.SaveChanges();

EF Core force refresh Entity from Database (force fetching values from DB)

I would like to how to load/fetch the current database values for an entity ("force reload from database") with Entity Framework Core.
My property looks like the following:
[Column(TypeName = "decimal(16, 2)")]
public decimal Fee { get; set; }
If the Fee is saved with a higher precision than the one specified in the Column Attribute e.g. 1,2888 the Database round it to two decimal placed but the entity I save does not get Updated.
So I tried to reload the values from the database to show the "correct current" values in this UI but neither of the following worked:
// removed from tracked entities and fetch from db
dbContext.Entry(entity).State = EntityState.Detached;
dbContext.Find(...);
// call reload
dbContext.Entry(entity).Reload();
Expected the value to be 1,29 after the refresh/reload but it always stays 1,2888. I have looked up the value in the database and it was 1,29 and also the next request would return 1,29 but I did not manage to return the correct value in the same request.
Is there a way to "force refresh" an Entity from the database?
--- Edit ---
The Problem was that I had an Entity with Navigation Properties and the decimal was on the navigation property which was not reloaded when calling .Reload() on the entity itself.
Code
using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace WebApplication1
{
public class Entity
{
public long Id { get; set; }
public Fee Fee { get; set; }
}
public class Fee
{
public long Id { get; set; }
[Column(TypeName = "decimal(16, 2)")]
public decimal Value { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Entity> Entities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(#"connectionString");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
public class Program
{
public static void Main()
{
AsyncMethod().GetAwaiter().GetResult();
}
private static async Task AsyncMethod()
{
using (var context = new MyContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
}
using (var context = new MyContext())
{
var entity = new Entity {Fee = new Fee {Value = 12.3456789m}};
context.Add(entity);
await context.SaveChangesAsync();
Console.WriteLine($"Fee value after SaveChanges() {entity.Fee.Value}");
await context.Entry(entity).ReloadAsync();
Console.WriteLine($"Fee value after Reload() {entity.Fee.Value}");
}
using (var context = new MyContext())
{
var entity = await context.Entities.OrderByDescending(x => x.Id).Include(x => x.Fee).FirstAsync();
Console.WriteLine($"Fee value after Disposing and Recreating Context {entity.Fee.Value}");
}
}
}
}
Output:
Fee value after SaveChanges() 12,3456789
Fee value after Reload() 12,3456789
Fee value after Disposing and Recreating Context 12,35
Possible solution in limited situations
Sometimes AsNoTracking can be a solution if you just need to load data and not maintain a whole context of objects. https://learn.microsoft.com/en-us/ef/core/querying/tracking
For instance I just had a case where I needed to :
Load a single row
Start off a hangfire task (running in its own context)
Check the status after a few seconds in the original method
By using AsNoTracking I was able to load the same row twice and not see anything cached.
var shipmentData = await context.Shipments.AsNoTracking().Single(s => s.ShipmentId == shipmentId);
HOWEVER If you actually need to save this row back this method can get confusing to manage or risk losing changes.
Another option, is to use context.ChangeTracker.Clear();.
This wipes out all locally cached/tracked changes so be mindful of doing this.
If you just want to remove one entity, then context.ChangeTracker.Context.Remove(myEntity)

Send IEnumerable from web api controller to angular js

I'm trying to send a IEnumerable from a web api controller to a AngularJs controller.
The code I was using was
Web Api:
readonly InventoryEntities _db = new InventoryEntities();
public IEnumerable<FDVOEligibilityRequest> Get()
{
return _db.FDVOEligibilityRequests.AsEnumerable();
}
AngularJS:
//get all customer information
$http.get("/api/Customer/").success(function (data) {
$scope.requests = data;
$scope.loading = false;
})
.error(function () {
$scope.error = "An Error has occured while loading posts!";
$scope.loading = false;
});
This worked fine, but now I'm using linq to include related tables and it doesn't work. The angularJs code is the same.
What am I doing wrong?
readonly InventoryEntities _db = new InventoryEntities();
public IEnumerable<FDVOEligibilityRequest> Get()
{
return _db.FDVOEligibilityRequests
.Include("FDVOEligibilityRequestMandatoryField")
.Include("FDVOEligibilityRequestDCCField").AsEnumerable();
}
I do get the data I want in the controller, but when i try to send it back to angular, I get a 500 (Internal Server Error) angular.js:10722
This is what FDVOEligibilityRequest looks like in the new Web Api controller
public partial class FDVOEligibilityRequest
{
public int Id { get; set; }
public string TestName { get; set; }
public string GroupName { get; set; }
public int MandatoryFieldsID { get; set; }
public int DCCFieldsID { get; set; }
public virtual FDVOEligibilityRequestMandatoryField FDVOEligibilityRequestMandatoryField { get; set; }
public virtual FDVOEligibilityRequestDCCField FDVOEligibilityRequestDCCField { get; set; }
}
If it's 500 and happens after you successfully make a return from your action then it can be an exception during serialization.
I would suggest to check that there are no circular references between FDVOEligibilityRequest and FDVOEligibilityRequestMandatoryField/FDVOEligibilityRequestDCCField.
Or try to diagnose it using code from here:
string Serialize<T>(MediaTypeFormatter formatter, T value)
{
// Create a dummy HTTP Content.
Stream stream = new MemoryStream();
var content = new StreamContent(stream);
/// Serialize the object.
formatter.WriteToStreamAsync(typeof(T), value, stream, content, null).Wait();
// Read the serialized string.
stream.Position = 0;
return content.ReadAsStringAsync().Result;
}
try {
var val = _db.FDVOEligibilityRequests
.Include("FDVOEligibilityRequestMandatoryField")
.Include("FDVOEligibilityRequestDCCField").AsEnumerable();
var str = Serialize(new JsonMediaTypeFormatter(), val);
}
catch (Exception x)
{ // break point here
}
ps: the common suggestion in this case is to use DTOs instead of EF objects with something like Automapper.

Pros / Cons to Different Binding Approaches using MVVM and RIA Services

I have been building an application, which uses the LoadOperation's Entities to return an IEnumerable which becomes the source of a CollectionViewSource in my View Model. I am now discovering the potential pitfall to this approach, when adding Entities in my Silverlight client, I cannot see these entities, unless I either submit the New Entity, then reload, or Maintain a separate collection of items, which I am binding to.
What I really see as my options are:
Add an ObservableCollection to use as the Source of the CollectionViewSource property in my ViewModel - this way I can add to both the DomainContext and the ObservableCollection at the same time to keep the collections in sync.
Change the Binding to the EntitySet directly, and add a filtering event handler to provide the filtering on the CollectionViewSource.
If anyone has tips or thoughts about pros/cons of each, I would greatly appreciate it. In particular, I am wondering, if there are performance and/or programming benefits in favor of one or the other?
I am taking this one approach at a time. First, I am going to show a point of reference to dicuss this with, then I will highlight the different changes necessary to support each methodology.
The basis for my demo is a single, authenticated domain service which returns a single entity of Resource. I will expose 4 commands (save, undo, add, and delete), plus a Collection, and a Property to hold the SelectedResource.
2 Different classes implement this interface (1 for blending, 1 for production). The production is the only one I will discuss here. Notice the action(lo.Entities) in the GetMyResources function:
public class WorkProvider
{
static WorkContext workContext;
public WorkProvider()
{
if (workContext == null)
workContext = new WorkContext();
}
public void AddResource(Resource resource)
{
workContext.Resources.Add(resource);
}
public void DelResource(Resource resource)
{
workContext.Resources.Remove(resource);
}
public void UndoChanges()
{
workContext.RejectChanges();
}
public void SaveChanges(Action action)
{
workContext.SubmitChanges(so =>
{
if (so.HasError)
// Handle Error
throw so.Error;
else
action();
}, null);
}
public void GetMyResources(Action<IEnumerable<Resource>> action)
{
var query = workContext.GetResourcesQuery()
.Where(r => r.UserName == WebContext.Current.User.Name);
workContext.Load(query, LoadBehavior.MergeIntoCurrent, lo =>
{
if (lo.HasError)
// Handle Error
throw lo.Error;
else
action(lo.Entities);
}, null);
}
}
In the ViewModel, I have the following Implementation:
public class HomeViewModel : ViewModelBase
{
WorkProvider workProvider;
public HomeViewModel()
{
workProvider = new WorkProvider();
}
// _Source is required when returning IEnumerable<T>
ObservableCollection<Resource> _Source;
public CollectionViewSource Resources { get; private set; }
void setupCollections()
{
Resources = new CollectionViewSource();
using (Resources.DeferRefresh())
{
_Source = new ObservableCollection<Resource>();
Resources.Source = _Source;
Resources.GroupDescriptions.Add(new PropertyGroupDescription("Title"));
Resources.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
Resources.SortDescriptions.Add(new SortDescription("Rate", ListSortDirection.Ascending));
}
}
void loadMyResources()
{
workProvider.GetMyResources(results =>
{
using (Resources.DeferRefresh())
{
// This is required when returning IEnumerable<T>
_Source.Clear();
foreach (var result in results)
{
if (!_Source.Contains(result))
_Source.Add(result);
}
}
});
}
Resource _SelectedResource;
public Resource SelectedResource
{
get { return _SelectedResource; }
set
{
if (_SelectedResource != value)
{
_SelectedResource = value;
RaisePropertyChanged("SelectedResource");
}
}
}
public RelayCommand CmdSave { get; private set; }
public RelayCommand CmdUndo { get; private set; }
public RelayCommand CmdAdd { get; private set; }
public RelayCommand CmdDelete { get; private set; }
void setupCommands()
{
CmdSave = new RelayCommand(() =>
{
workProvider.SaveChanges(() =>
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
System.Windows.MessageBox.Show("Saved");
});
});
});
CmdUndo = new RelayCommand(() =>
{
workProvider.UndoChanges();
// This is required when returning IEnumerable<T>
loadMyResources();
});
CmdAdd = new RelayCommand(() =>
{
Resource newResource = new Resource()
{
ResourceID = Guid.NewGuid(),
Rate = 125,
Title = "Staff",
UserName = "jsmith"
};
// This is required when returning IEnumerable<T>
_Source.Add(newResource);
workProvider.AddResource(newResource);
});
CmdDelete = new RelayCommand(() =>
{
// This is required when returning IEnumerable<T>
_Source.Remove(_SelectedResource);
workProvider.DelResource(_SelectedResource);
});
}
}
The alternate method would involve changing the WorkProvider class as follows (notice the action(workContext.Resources) that is returned:
public void GetMyResources(Action<IEnumerable<Resource>> action)
{
var query = workContext.GetResourcesQuery()
.Where(r => r.UserName == WebContext.Current.User.Name);
workContext.Load(query, LoadBehavior.MergeIntoCurrent, lo =>
{
if (lo.HasError)
// Handle Error
throw lo.Error;
else
// Notice Changed Enumeration
action(workContext.Resources);
}, null);
}
And the changes to the viewmodel are as follows (notice the removal of the _Source ObservableCollection):
public class HomeViewModel : ViewModelBase
{
WorkProvider workProvider;
public HomeViewModel()
{
workProvider = new WorkProvider();
}
public CollectionViewSource Resources { get; private set; }
void setupCollections()
{
Resources = new CollectionViewSource();
using (Resources.DeferRefresh())
{
Resources.Filter += (s,a) =>
{
a.Accepted = false;
if (s is Resource)
{
Resource res = s as Resource;
if (res.UserName == WebContext.Current.User.Name)
a.Accepted = true;
}
};
Resources.GroupDescriptions.Add(new PropertyGroupDescription("Title"));
Resources.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
Resources.SortDescriptions.Add(new SortDescription("Rate", ListSortDirection.Ascending));
}
}
void loadMyResources()
{
workProvider.GetMyResources(results =>
{
using (Resources.DeferRefresh())
{
Resources.Source = results;
}
});
}
Resource _SelectedResource;
public Resource SelectedResource
{
get { return _SelectedResource; }
set
{
if (_SelectedResource != value)
{
_SelectedResource = value;
RaisePropertyChanged("SelectedResource");
}
}
}
public RelayCommand CmdSave { get; private set; }
public RelayCommand CmdUndo { get; private set; }
public RelayCommand CmdAdd { get; private set; }
public RelayCommand CmdDelete { get; private set; }
void setupCommands()
{
CmdSave = new RelayCommand(() =>
{
workProvider.SaveChanges(() =>
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
System.Windows.MessageBox.Show("Saved");
});
});
});
CmdUndo = new RelayCommand(() =>
{
workProvider.UndoChanges();
Resources.View.Refresh();
});
CmdAdd = new RelayCommand(() =>
{
Resource newResource = new Resource()
{
ResourceID = Guid.NewGuid(),
Rate = 125,
Title = "Staff",
UserName = "jsmith"
};
workProvider.AddResource(newResource);
});
CmdDelete = new RelayCommand(() =>
{
workProvider.DelResource(_SelectedResource);
});
}
}
While the second approach definately requires adding the filter event handler in the configuration of the CollectionViewSource, and could be seen as filtering data 2 times (1 time at the server, and the second time by the CollectionViewSource), it does off the following benefits: There is a single collection - which makes management of collection notifications simpler and easier. The collection is the actual collection which will be submitted to the server, which makes managing adds/deletes simpler, since there are not opportunities for forgetting to add/remove entities from the correct collection to initiate the add/delete function when submitting back.
The one last thing I need to confirm is the following: On a collectionviewsource, it is my understanding that you should use DeferRefresh() when making multiple changes that affect the view. This just prevents unnecessary refreshes from occuring when internal changes may cause refreshes such as configuring sorting, grouping, etc. It is also important to call .View.Refresh() when we expect the UI to process some update changes. The .View.Refresh() is probably more important to note than the DeferRefresh(), since it actually causes a UI update, as opposed to a prevent unexpected UI updates.
I don't know if this will help others, but I hope so. I definately spent some time working through these and trying to understand this. If you have clarifications or other things to add, please feel free to do so.
Ryan, it might be worth your while to take a look through this post on collection binding (and some of the related ones). Your implementation is certainly a reasonable one, but I can see it wrestles with a few of the issues that have already been resolved at the framework level.

Resources