MVVM sync List<string> - mvvm

I think this answer is the solution to my problem but I am struggling to understand how to apply it to my problem. Like the other post, I have a two collections I want to keep in sync. My model object has collections of strings:
public class Person {
public int PersonId {get; set; }
public string PersonName { get; set; }
public List<string> PersonNicknames { get; set; }
}
I wrap this model object in its own ViewModel (PersonViewModel). To allow the Nicknames to be edited I also wrap them in their own NicknameViewModel. PersonViewModel then exposes an ObservableCollection<NicknameViewModel> NicknameViewModelCollection which is populated at construction:
foreach (string stringItem in _person.PersonNicknames)
{
var nicknameViewModel = new NicknameViewModel(stringItem);
this.NicknameViewModelCollection.Add(nicknameViewModel);
}
When a string is added, removed or changed in PersonViewModel.NicknameViewModelCollection the change is not reflected in the Model collection (i.e. Person.Nicknames). Whenever the user modifies, edits or deletes the string item I need to update the Model collection. I don't understand how the linked answer works or how to apply it to this problem. An example would be amazing... I'm just at a loss here.

This is my standard solution for what you are searching for. It has a bit of overhead for your scenario, because it works with a ViewModel type that has a field for it's context, etc. Anyway, the sollution should become obvious. The collection syncs OneWayToSource in general and TwoWay if the model collection itself is observable. Does this help you? If not, please ask...
/// <summary>
/// Observable collection of ViewModels that pushes changes to a related collection of models
/// </summary>
/// <typeparam name="TViewModel">Type of ViewModels in collection</typeparam>
/// <typeparam name="TModel">Type of models in underlying collection</typeparam>
public class VmCollection<TViewModel, TModel> : ObservableCollection<TViewModel>
where TViewModel : class, IViewModel, new()
where TModel : class
{
private readonly object _context;
private readonly ICollection<TModel> _models;
private bool _synchDisabled;
/// <summary>
/// Constructor
/// </summary>
/// <param name="models">List of models to synch with</param>
/// <param name="context"></param>
/// <param name="autoFetch">
/// Determines whether the collection of ViewModels should be
/// fetched from the model collection on construction
/// </param>
public VmCollection(ICollection<TModel> models, object context = null, bool autoFetch = true)
{
_models = models;
_context = context;
// Register change handling for synchronization
// from ViewModels to Models
CollectionChanged += ViewModelCollectionChanged;
// If model collection is observable register change
// handling for synchronization from Models to ViewModels
if (models is ObservableCollection<TModel>)
{
var observableModels = models as ObservableCollection<TModel>;
observableModels.CollectionChanged += ModelCollectionChanged;
}
// Fecth ViewModels
if (autoFetch) FetchFromModels();
}
/// <summary>
/// CollectionChanged event of the ViewModelCollection
/// </summary>
public override sealed event NotifyCollectionChangedEventHandler CollectionChanged
{
add { base.CollectionChanged += value; }
remove { base.CollectionChanged -= value; }
}
/// <summary>
/// Load VM collection from model collection
/// </summary>
public void FetchFromModels()
{
// Deactivate change pushing
_synchDisabled = true;
// Clear collection
Clear();
// Create and add new VM for each model
foreach (TModel model in _models)
AddForModel(model);
// Reactivate change pushing
_synchDisabled = false;
}
private void ViewModelCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Return if synchronization is internally disabled
if (_synchDisabled) return;
// Disable synchronization
_synchDisabled = true;
// Synchronize collection of Models
if (e.NewItems != null)
foreach (var v in e.NewItems.OfType<IViewModel<TModel>>())
v.AddModelTo(_models);
if (e.OldItems != null)
foreach (var v in e.OldItems.OfType<IViewModel<TModel>>())
v.RemoveModelFrom(_models);
//Enable synchronization
_synchDisabled = false;
}
private void ModelCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_synchDisabled) return;
// Synchronize collection of ViewModels
if (e.NewItems != null)
foreach (TModel m in e.NewItems.OfType<TModel>()) this.AddIfNotNull(CreateViewModel(m));
if (e.OldItems != null) foreach (TModel m in e.OldItems) this.RemoveIfContains(GetViewModelOfModel(m));
}
private TViewModel CreateViewModel(TModel model)
{
return ViewModelCache.Get<TViewModel>.ForExistingModel(model, _context);
}
private TViewModel GetViewModelOfModel(TModel model)
{
return Items.OfType<IViewModel<TModel>>().FirstOrDefault(v => v.IsViewModelOf(model)) as TViewModel;
}
/// <summary>
/// Adds a new ViewModel for the specified Model instance
/// </summary>
/// <param name="model">Model to create ViewModel for</param>
public void AddForModel(TModel model)
{
Add(CreateViewModel(model));
}
/// <summary>
/// Adds a new ViewModel with a new model instance of the specified type,
/// which is the ModelType or derived from the Model type
/// </summary>
/// <typeparam name="TSpecificModel">Type of Model to add ViewModel for</typeparam>
public void AddNew<TSpecificModel>() where TSpecificModel : TModel, new()
{
var m = new TSpecificModel();
Add(CreateViewModel(m));
}
}

Related

Implicitly set common properties in Entity Framework 6

My application is using EF6 database first approach. All the entities in the database have 2 common properties "CreatedDateTime" and "ModifiedDateTime".
Currently When i do SaveChanges() im explicitly setting these 2 properties based on if i am creating new entity or updating existing entity.
If its a new entity then set both properties else set only ModifiedDateTime property.
I wanted to know if there is a way to implicitly set these 2 properties on Save or update operation?
Update 1
I know i have to override Savechanges() method however the real issue here is SaveChanges needs to have access to these 2 properties. So i only see 2 options here:
1> Use reflection to find if entity has these properties and set it.
2> Modify default T4 generation so that it derives all entities with predefined interface. And this interface will have these 2 properties. SaveChanges() method can check if entity is derived from this interface and set the property.
I defiantly don't want to use option 1 using refection.
Is there any other way or has anyone done this before in DB first approach?
Going with your second approach: Adjust your T4 files to include a reference to an interface (e.g. IChangeTrack):
public interface IChangeTrack
{
/// <summary>
/// When has this entry be created
/// </summary>
[Required]
DateTime CreatedDateTime { get; set; }
/// <summary>
/// When has this entry been modified
/// </summary>
DateTime? ModifiedDateTime { get; set; }
}
Now overwrite your SaveChanges() routine by doing something like this:
/// <summary>
/// Enhance save changes to handle system fields.
/// </summary>
/// <returns></returns>
public override int SaveChanges()
{
HandleChanges();
int changes = base.SaveChanges();
return changes;
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
HandleChanges();
int changes = await base.SaveChangesAsync(cancellationToken);
return changes;
}
private void HandleChanges()
{
ChangeTracker.DetectChanges();
var entries = ChangeTracker.Entries<IChangeTrack>();
if (entries != null)
{
foreach (DbEntityEntry<IChangeTrack> entry in entries)
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreatedDateTime = DateTime.UtcNow
break;
case EntityState.Modified:
entry.Entity.ModifiedDateTime = DateTime.UtcNow;
break;
}
}
}
}

How to log my entity errors detailed

I want to store my entity classes' properties' values as extra if occurred any error. What is the best practises to do this ? I thought, getting values by reflection and write them to database is a good solution but then It creates another question. How can I reach all values including Collections' values, in other words children objects' values. I have tried to reach by using reflection but I stucked,failed. If my solution is wrong, I am open to any suitable solutions, suggestions :)
For example :
public class Author : BaseEntity
{
/// <summary>
/// Gets or sets a value indicating the author's name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets a value indicating the author's surname
/// </summary>
public string Surname { get; set; }
/// <summary>
/// Gets or sets a value indicating the author's birthdate
/// </summary>
public DateTime BirthDate { get; set; }
/// <summary>
/// Gets or sets a value indicating the author's death of date
/// </summary>
public DateTime DeathDate { get; set; }
/// <summary>
/// Gets or sets a value indicating rating entity
/// </summary>
public virtual AuthorRating Rating { get; set; }
/// <summary>
/// Gets or sets a value indicating idenitifier of the author's nationality
/// </summary>
public int NationalityId { get; set; }
/// <summary>
/// Gets or sets a value indicating the author's nationality
/// </summary>
public virtual Nation Nationality { get; set; }
/// <summary>
/// Gets or sets the collection of votes
/// </summary>
public virtual ICollection<AuthorVote> HavingVotes { get; set; }
/// <summary>
/// Gets or sets the collection of quotes which he/she said
/// </summary>
public virtual ICollection<Quote> OwnQuotes { get; set; }
/// <summary>
/// Gets or sets the collection of favourited rows by user
/// </summary>
public virtual ICollection<UserFavouriteAuthor> Favouriteds { get; set; }
}
For example, How can I reach Author.Qutes.QuoteVotes class and their values and their childrens recursively by using reflection ?
To handle a DbEntityValidationException, surround your DbContext related code in a try catch block and use this for your exception
public void AppendValidationErrors(Exception e)
{
DbEntityValidationException ex = e is DbEntityValidationException ?
e as DbEntityValidationException : null ;
if (ex == null) { log.Info(e); return; }
foreach (var eve in ex.EntityValidationErrors)
{
log.Info(string.Format("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State));
foreach (var ve in eve.ValidationErrors)
{
log.Info(string.Format("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage));
}
}
}
To get the property values, you can use EF's DbPropertyValues
public static void PrintValues(DbPropertyValues values)
{
foreach (var propertyName in values.PropertyNames)
{
Console.WriteLine("Property {0} has value {1}",
propertyName, values[propertyName]);
}
}
PrintValues(context.Entry(User).GetDatabaseValues());
If you want to log your database's erroneous behavior, you can inhert from the Entity Framework'sIDbCommandInterceptor and intercept the database's warnings and errors.
/// <summary>
/// The supported logging types
/// </summary>
public enum LogTarget { Log4net, Console, File };
public class DbCommandLogger : IDbCommandInterceptor
{
#region Appender Definitions
/// <summary>
/// Assign a method to append an error
/// </summary>
/// <param name="error">The error to append</param>
private Action<string> appendError;
/// <summary>
/// Assign a method to append a warning
/// </summary>
/// <param name="warning">The warning to append</param>
private Action<string> appendWarning;
#endregion
#region Fields
private static readonly log4net.ILog log = log4net.LogManager.GetLogger("WarningsAndErrorsAppender");
#endregion
#region Construct and Setup
public DbCommandLogger(LogTarget logTarget, string path = "db")
{
SetupAppenders(logTarget, path);
}
/// <summary>
/// Setups appenders according to the specified log target. It can only be accessed via the constructor
/// </summary>
/// <param name="logTarget">The log target</param>
/// <param name="path">The file path. Leave empty if you don't want to specify a path</param>
private void SetupAppenders(LogTarget logTarget, string path = "db")
{
switch (logTarget)
{
case LogTarget.Console:
appendError = Console.Write;
appendWarning = Console.Write;
break;
case LogTarget.File:
appendError = File.CreateText(path + ".Errors.log").WriteLine;
appendWarning = File.CreateText(path + ".Warning.log").WriteLine;
break;
case LogTarget.Log4net:
appendWarning = x => log.Warn(x);
appendError = x => log.Error(x);
break;
default:
appendWarning = x => { };
appendError = x => { };
break;
}
}
#endregion
#region Queries
public void NonQueryExecuting(
DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
LogIfNonAsync(command, interceptionContext);
}
public void NonQueryExecuted(
DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
LogIfError(command, interceptionContext);
}
public void ReaderExecuting(
DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
LogIfNonAsync(command, interceptionContext);
}
public void ReaderExecuted(
DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
LogIfError(command, interceptionContext);
}
public void ScalarExecuting(
DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
LogIfNonAsync(command, interceptionContext);
}
public void ScalarExecuted(
DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
LogIfError(command, interceptionContext);
}
#endregion
#region Log Commands
/// <summary>
/// Log a warning for any command that is executed non-asynchronously
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="command">The command being executed.</param>
/// <param name="interceptionContext">Contextual information associated with the call.</param>
private void LogIfNonAsync<TResult>(
DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
if (!interceptionContext.IsAsync)
{
appendWarning(String.Format("Non-async command used: {0}", command.CommandText));
}
}
/// <summary>
/// Log an error for any command that throws when executed
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="command">The command being executed.</param>
/// <param name="interceptionContext">Contextual information associated with the call.</param>
private void LogIfError<TResult>(
DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
if (interceptionContext.Exception != null)
{
appendError(String.Format("Command {0} failed with exception {1}",
command.CommandText, interceptionContext.Exception));
}
}
#endregion
#region Helpers
[MethodImpl(MethodImplOptions.NoInlining)]
public string GetCurrentMethod()
{
StackTrace st = new StackTrace();
StackFrame sf = st.GetFrame(1);
return sf.GetMethod().Name;
}
#endregion
}
You can then register the interceptor using a DbContext extension method
/// <summary>
/// Logs erroneous database behavior. Set the appender method via the log4net.config file located in the project's target folder
/// </summary>
/// <param name="context">The DbContext object that's being tracked</param>
public static void LogWarningsAndErrors(this DbContext context,LogTarget logTarget)
{
string path = FolderExists("LogFiles") ? "LogFiles\\" + context.ToString() :context.ToString();
DbInterception.Add(new DbCommandLogger(logTarget, path));
}
Finally, you can easily log the errors via your DbContext like this:
context.LogWarningsAndErros(LogTarget.Log4Net);

MVVM : how to make view model set fields of clean model to persist view changes to database

In MVVM application with clean model (not implementing interfaces like INotifyPropertyChabged), the View Model Contains properties bound to the View and these properties get its values from the model object contained in the view model and should set the value of its properties when view changes one of the controls that are bound to these properties.
the propblem is when the view change; the changes are captured by the bound view model properties but the properties can't set the model object fields, the model doesn't change. I need the model fields to accept setting by the view model properties, then i can persist the updated model into the database taking into account that it is a clean model.
Here part of the view model code
public class SubsystemDetailsViewModel: INotifyPropertyChanged, ISubsystemDetailsViewModel
{
#region Fields
//Properties to which View is bound
private int? _serial;
public int? Serial
{
get { return Subsystem.Serial; }
set
{
//Subsystem.Serial=value;
_serial = value;
OnPropertyChanged("Serial");
}
}
private string _type;
public string Type
{
get { return Subsystem.Type; }
set
{
//Subsystem.Type = value;
_type = value;
OnPropertyChanged("Type");
}
}
//remaining properties ....
#endregion
//Service
private readonly ISubsystemService _subsystemService;
//Reference to the View
public ISubsystemDetailsView View { get; set; }
//Event Aggregator Event
private readonly IEventAggregator eventAggregator;
//Commands
public ICommand ShowTPGCommand { get; set; }
public DelegateCommand UpdateCommand { get; set; }
//
private bool _isDirty;
//Constructor ************************************************************************************************
public SubsystemDetailsViewModel(ISubsystemDetailsView View, ISubsystemService subsystemService, IEventAggregator eventAggregator)
{
_subsystemService = subsystemService;
this.View = View;
View.VM = this;
//EA-3
if (eventAggregator == null) throw new ArgumentNullException("eventAggregator");
this.eventAggregator = eventAggregator;
//Commands
this.ShowTPGCommand = new DelegateCommand<PreCommissioning.Model.Subsystem>(this.ShowTestPacks);
this.UpdateCommand = new DelegateCommand(this.UpdateSubsystem, CanUpdateSubsystem);
}
//****************************************************************************************************************
//ICommand-3 Event Handler
//this handler publish the Payload "SelectedSubsystem" for whoever subscribe to this event
private void ShowTestPacks(PreCommissioning.Model.Subsystem subsystem)
{
eventAggregator.GetEvent<ShowTestPacksEvent>().Publish(SelSubsystem);
}
//===============================================================================================
private void UpdateSubsystem()
{
_subsystemService.SaveChanges(Subsystem);
}
private bool CanUpdateSubsystem()
{
return _isDirty;
}
//*******************************************************************************************
public void SetSelectedSubsystem(PreCommissioning.Model.Subsystem subsystem)
{
this.SelSubsystem = subsystem;
}
//************************************************************************************************************
/// <summary>
/// Active subsystem >> the ItemSource for the View
/// </summary>
private PreCommissioning.Model.Subsystem _subsystem;
public PreCommissioning.Model.Subsystem Subsystem
{
get
{
//return this._subsystem;
GetSubsystem(SelSubsystem.SubsystemNo);
return this._subsystem;
}
set
{
if (_subsystem != value)
{
_subsystem = value;
OnPropertyChanged("Subsystem");
}
}
}
//Call the Service to get the Data form the Database
private void GetSubsystem(string SSNo)
{
this._subsystem = _subsystemService.GetSubsystem(SSNo);
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
_isDirty = true;
UpdateCommand.RaiseCanExecuteChanged();
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Subsystem is the model object which is populated using GetSubsystem() method. the view model properties like Serial get its value from the model as shown. i tried to set the model properties as shown in the commented out line in set part of the property but no change happen to the Subsystem object, always keep its original values
If GetSubsystem returns a new subsystem every time, that's your problem. In the 'set' for the properties you're binding to the view, you're calling the public property "Subsystem", not the private field you've created. So, every single time you set a property from the view, you are calling Subsystem.get which calls GetSubsystem(SelSubsystem.SubsystemNo);.
I think, in your ViewModel properties', you want to change it to:
//Properties to which View is bound
public int? Serial
{
get { return _subsystem.Serial; }
set
{
_subsystem.Serial=value; // NOTE THE USE OF THE PRIVATE FIELD RATHER THAN THE PROPERTY
OnPropertyChanged("Serial");
}
}
public string Type
{
get { return _subsystem.Type; }
set
{
_subsystem.Type = value; // NOTE THE USE OF THE PRIVATE FIELD RATHER THAN THE PROPERTY
OnPropertyChanged("Type");
}
You need to have a reference in your view-model to the model and the view-model will pass the values to the model. Your view-model will implement INotifyPropertyChanged and will be the datacontext of your view. In your view-model, write your bound properties like this:
private string yourProperty;
public string YourProperty
{
get { return yourProperty; }
set
{
if (value == yourProperty)
return;
yourProperty= value;
YOUR_MODEL_REFERENCE.YourProperty= yourProperty;
this.RaisePropertyChanged(() => this.YourProperty);
}
}

Querying objects after AddObject before SaveChanges?

In EntityFramework, is that possible to query the objects that have just been added to the context using AddObject but before calling the SaveChanges method?
Thanks
To persist an entity you usually add it to it's DbSet in the context.
For example
var bar = new Bar();
bar.Name = "foo";
var context = new Context();
context.Bars.Add(bar);
Surprisingly, querying context.Bars, the just added entity cannot be found
var howMany = context.Bars.Count(b => b.Name == "foo");
// howMany == 0
After context.SaveChanges() the same line will result 1
The DbSet seems unaware to changes until they're persisted on db.
Fortunately, each DbSet has a Local property that acts like the DbSet itself, but it reflect all in-memory operations
var howMany = context.Bars.Local.Count(b => b.Name == "foo");
// howMany == 1
You can also use Local to add entities
context.Bars.Local.Add(bar);
and get rid of the weird behavior of Entity Framework.
you can query objects like this,
context.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Select(obj => obj.Entity).OfType<TheEntityType>()
this will query the objects which are in added state. If you want other states too you can pass all other states to GetObjectStateEntries method like this.
GetObjectStateEntries(EntityState.Added | EntityState.Modified | EntityState.Unchanged)
In hibernate transient instances are already attached to context. Just stumbled upon this EF restriction.
I did not managed to intersect/union the ObjectSet with its transient entities ObjectSet.Local but for our usecase the below find method is sufficient.
In our cases we create some entities lazy depending on unique criteria during an iteration
Find method
If you are using an repository pattern you can create a method like:
public interface IRepository<T> where T : class, IEntity
{
/// <summary>
/// Finds the unique Entity with the given predicate.
/// Depending on implementation also checks transient / local (unsaved) Entities.
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
IQueryable<T> FindAll(Expression<Func<T, bool>> predicate);
}
public class EfRepository<T> : IRepository<T> where T : class, IEntity
{
protected readonly ObjectContext context;
protected readonly ObjectSet<T> objectSet;
/// <summary>
/// Creates a new repository of the given context.
/// </summary>
/// <param name="context"></param>
public EfRepository(ObjectContext context)
{
if (context == null)
throw new ArgumentException("Context must not be null.");
this.context = context;
this.objectSet = context.CreateObjectSet<T>();
}
/// <summary>
/// Also takes local context into consideration for unsaved changes
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public T Find(Expression<Func<T, bool>> predicate)
{
T result = this.objectSet.Where(predicate).FirstOrDefault();
if (result == null)
result = this.objectSet.Local().Where(predicate).FirstOrDefault();
return result;
}
}

Readonly properties in EF 4.1

I've faced with situation when I need to have EF readonly property in case of 'optimistic update'(you do not load current state of your domain object from database to check what properties are really changed. You just set your object as Modified and update it to database. You avoid redundant select and merge operations in this case).
You can't write something like this : DataContext.Entry(entity).Property(propertyName).IsModified = false;, because setting of 'false' value is not supported and you will get an exception. (in EF 4.1)
I've created a simple structure for registering readonly properties in repository.
So, you can easy Modify just nonreadonly properties.
What do you think about this?
public abstract class RepositoryBase<T> where T : class
{
private const string MethodReferenceErrorFormat = "Expression '{0}' refers to a method, not a property.";
private const string FieldReferenceErrorFormat = "Expression '{0}' refers to a field, not a property.";
protected IList<PropertyInfo> _readOnlyProperties;
/// <summary>
/// This method is used to register readonly property for Entity.
/// </summary>
/// <param name="propertyLambda">Entity property as LambdaExpression</param>
protected void RegisterReadOnlyProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
{
Guard.ArgumentNotNull(propertyLambda, "propertyLambda");
var propertyMember = propertyLambda.Body as MemberExpression;
if (propertyMember == null)
{
var exceptionMessage = string.Format(MethodReferenceErrorFormat, propertyLambda);
throw new ArgumentException(exceptionMessage);
}
var propertyInfo = propertyMember.Member as PropertyInfo;
if (propertyInfo == null)
{
var exceptionMessage = string.Format(FieldReferenceErrorFormat, propertyLambda);
throw new ArgumentException(exceptionMessage);
}
_readOnlyProperties.Add(propertyInfo);
}
/// <summary>
/// This method is used to attach domain object to DbContext and mark it as modified to save changes.
/// </summary>
/// <param name="entity">Detached entity</param>
public void SetModified(T entity)
{
Guard.ArgumentNotNull(entity, "entity");
//Mark whole entity as Modified, when collection of readonly properties is empty.
if(_readOnlyProperties.Count == 0)
{
DataContext.Entry(entity).State = EntityState.Modified;
return;
}
//Attach entity to DbContext.
_dbSet.Attach(entity);
//Mark all properties except readonly as Modified.
var allProperties = entity.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
var propertiesForUpdate = allProperties.Except(_readOnlyProperties);
foreach (var propertyInfo in propertiesForUpdate)
{
DataContext.Entry(entity).Property(propertyInfo.Name).IsModified = true;
}
}
This would work but I don't like the need to register modified properties directly in repository. You can forget about registered properties and code will accidentaly not save some changes - that will be bug which will be hard to find when reusing repository in complex scenarios. I like explicit definition of updated properties each time you call something like Update on your repository. Also I don't like reflection in the code. Unless you modify your code to get reflected data about each entity only once for whole application you are doing it wrong.
I wrote the answer for EFv4 but it can be easily modified to EFv4.1:
public void Update(T entity, params Expression<Func<T, object>>[] properties)
{
_dbSet.Attach(entity);
DbEntityEntry<T> entry = _context.Entry(entity);
foreach (var selector in properties)
{
entry.Property(selector).IsModified = true;
}
}
You will call it like:
repo.Update(entity, e => e.Name, e => e.Description);