MVVM ObservableCollection Bind TwoWay - mvvm

I have been working with MVVM and wondering how to make use of ObservableCollection
to bind to an ItemsSource in TwoWay?
For example, I have an Custom Drawing UserControl called SmartDraw, in which the ItemsSource refers to the list of Custom Graphics and it is bound to an ObservableCollection Graphics in the View Model.
If I add a CustomGraphic in the view model, the ItemsSource in the SmartDraw will know that there is an addition of CustomGraphic and then do some other function calls. It is normal.
However, the SmartDraw is also a Canvas which enable user to draw graphics on it using mouse. The number of CustomGraphic will change according to the user drawing. So, how could I know the ObservableCollection is changed by the UI(SmartDraw)?
Here is my code:
ViewModel:
public ObservableCollection<CustomGraphic> Graphics { get; set; }
Thank you so much.

Not sure whether this answers your question ... but here is how you would track changes to an observable collection in general.
To detect changes to an observable collection (not changes to properties of the items within the collection) you subscribe to the CollectionChanged event of the ObservableCollection.
private ObservableCollection<ViewModel> _collection;
public ObservableCollection<ViewModel> Collection {
get { return _collection; }
set {
if (_collection != value) {
// de-register on collection changed
if (_collection != null)
_collection.CollectionChanged -= this.Collection_CollectionChanged;
_collection = value;
// re-register on collection changed
if (_collection != null)
_collection.CollectionChanged += this.Collection_CollectionChanged;
}
}
private void Collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
// e.NewItems contains the added items
break;
case NotifyCollectionChangedAction.Remove:
// e.OldItems contains the removed items
break;
case NotifyCollectionChangedAction.Replace:
// e.NewItems contains the new items,
// e.OldItems contains the removed items
break;
case NotifyCollectionChangedAction.Reset:
// the collection has been cleared
break;
}
}
If you need to track changes to the objects within the collection you have to build a extended ObservableCollection that subscribes to the PropertyChanged events of the items and then raises an event if one of these properties has changed.
public class ObservableCollectionEx<TValue> : ObservableCollectionAddRange<TValue>
{
public ObservableCollectionEx() : base() { }
public ObservableCollectionEx(IEnumerable<TValue> values)
: base(values)
{
this.EnsureEventWiring();
}
public ObservableCollectionEx(List<TValue> list)
: base(list)
{
this.EnsureEventWiring();
}
public event EventHandler<ItemChangedEventArgs> ItemChanged;
protected override void InsertItem(int index, TValue item)
{
base.InsertItem(index, item);
var npc = item as INotifyPropertyChanged;
if (npc != null)
npc.PropertyChanged += this.HandleItemPropertyChanged;
}
protected override void RemoveItem(int index)
{
var item = this[index];
base.RemoveItem(index);
var npc = item as INotifyPropertyChanged;
if (npc != null)
npc.PropertyChanged -= this.HandleItemPropertyChanged;
}
protected override void SetItem(int index, TValue item)
{
var oldItem = this[index];
base.SetItem(index, item);
var npcOld = item as INotifyPropertyChanged;
if (npcOld != null)
npcOld.PropertyChanged -= this.HandleItemPropertyChanged;
var npcNew = item as INotifyPropertyChanged;
if (npcNew != null)
npcNew.PropertyChanged += this.HandleItemPropertyChanged;
}
protected override void ClearItems()
{
var items = this.Items.ToList();
base.ClearItems();
foreach (var npc in items.OfType<INotifyPropertyChanged>().Cast<INotifyPropertyChanged>())
npc.PropertyChanged -= this.HandleItemPropertyChanged;
}
private void HandleItemPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (typeof(TValue).IsAssignableFrom(sender.GetType()))
{
var item = (TValue)sender;
var pos = this.IndexOf(item);
this.OnItemChanged(item, pos, args.PropertyName);
}
}
protected virtual void OnItemChanged(TValue item, int index, string propertyName)
{
if (this.ItemChanged != null)
this.ItemChanged(this, new ItemChangedEventArgs(item, index, propertyName));
}
private void EnsureEventWiring()
{
foreach (var npc in this.Items.OfType<INotifyPropertyChanged>().Cast<INotifyPropertyChanged>())
{
npc.PropertyChanged -= this.HandleItemPropertyChanged;
npc.PropertyChanged += this.HandleItemPropertyChanged;
}
}
public class ItemChangedEventArgs : EventArgs
{
public ItemChangedEventArgs(TValue item, int index, string propertyName)
{
this.Item = item;
this.Index = index;
this.PropertyName = propertyName;
}
public int Index { get; private set; }
public TValue Item { get; private set; }
public string PropertyName { get; private set; }
}
}

Related

How to implement code first with existing database

I am using entityframework 5 with code first model and existing database.I am implementing repository pattern.I have a BaseEntity class like below:
public abstract partial class BaseEntity
{
/// <summary>
/// Gets or sets the entity identifier
/// </summary>
public virtual int Id { get; set; }
public override bool Equals(object obj)
{
return Equals(obj as BaseEntity);
}
private static bool IsTransient(BaseEntity obj)
{
return obj != null && Equals(obj.Id, default(int));
}
private Type GetUnproxiedType()
{
return GetType();
}
public virtual bool Equals(BaseEntity other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (!IsTransient(this) &&
!IsTransient(other) &&
Equals(Id, other.Id))
{
var otherType = other.GetUnproxiedType();
var thisType = GetUnproxiedType();
return thisType.IsAssignableFrom(otherType) ||
otherType.IsAssignableFrom(thisType);
}
return false;
}
public override int GetHashCode()
{
if (Equals(Id, default(int)))
return base.GetHashCode();
return Id.GetHashCode();
}
public static bool operator ==(BaseEntity x, BaseEntity y)
{
return Equals(x, y);
}
public static bool operator !=(BaseEntity x, BaseEntity y)
{
return !(x == y);
}
protected virtual void SetParent(dynamic child)
{
}
protected virtual void SetParentToNull(dynamic child)
{
}
protected void ChildCollectionSetter<T>(ICollection<T> collection, ICollection<T> newCollection) where T : class
{
if (CommonHelper.OneToManyCollectionWrapperEnabled)
{
collection.Clear();
if (newCollection != null)
newCollection.ToList().ForEach(x => collection.Add(x));
}
else
{
collection = newCollection;
}
}
protected ICollection<T> ChildCollectionGetter<T>(ref ICollection<T> collection, ref ICollection<T> wrappedCollection) where T : class
{
return ChildCollectionGetter(ref collection, ref wrappedCollection, SetParent, SetParentToNull);
}
protected ICollection<T> ChildCollectionGetter<T>(ref ICollection<T> collection, ref ICollection<T> wrappedCollection, Action<dynamic> setParent, Action<dynamic> setParentToNull) where T : class
{
if (CommonHelper.OneToManyCollectionWrapperEnabled)
return wrappedCollection ?? (wrappedCollection = (collection ?? (collection = new List<T>())).SetupBeforeAndAfterActions(setParent, SetParentToNull));
return collection ?? (collection = new List<T>());
}
}
Now I have a table Customer which has CustomerId as primary key .How should I map such like fields with Id of BaseEntity.If I have a composite key how should I map this to the BaseEntity ID.
Please Help me.
Make sure you set the initialize for your DBContext to false:
context.Database.Initialize(false);
Here is a good article on database initializers for code first:
http://www.codeguru.com/csharp/article.php/c19999/Understanding-Database-Initializers-in-Entity-Framework-Code-First.htm

Handling Related Data when using Entity Framework Code First

I have two Classes: LicenseType and EntityType.
[Table("LicenseType")]
public class LicenseType : ComplianceBase, INotifyPropertyChanged
{
private List<Certification> _certifications = new List<Certification>();
private List<EntityType> _entityTypes = new List<EntityType>();
public List<EntityType> EntityTypes
{
get { return _entityTypes; }
set { _entityTypes = value; }
}
public List<Certification> Certifications
{
get { return _certifications; }
set { _certifications = value; }
}
}
and
[Table("EntityType")]
public class EntityType : ComplianceBase, INotifyPropertyChanged
{
private List<LicenseType> _licenseTypes = new List<LicenseType>();
public List<LicenseType> LicenseTypes
{
get { return _licenseTypes; }
set
{
_licenseTypes = value;
// OnPropertyChanged();
}
}
}
The both derive from ComplianceBase,
public class ComplianceBase
{
private int _id;
private string _name;
private string _description;
public string Description
{
get { return _description; }
set
{
if (_description == value) return;
_description = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public int Id
{
get { return _id; }
set
{
if (value == _id) return;
_id = value;
OnPropertyChanged();
}
}
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
What I want is to be able to do is associate an EntityType with one or more LicenseTypes, so for instance, an EntityType "Primary Lender" could be associated with say two LicenseTypes, "Lender License" and "Mortgage License". In this situation, I want one record in the EntityType table, "Primary Lender" and two records in my LicenseType table: "Lender License" and "Mortgage License".
The code for adding related LicenseTypes to my EntityType is done by calling:
_currentEntity.LicenseTypes.Add(licenseType);
and then calling _context.SaveChanges();
There is an additional table, "EntityTypeLicenseTypes" that serves as the lookup table to relate these two tables. There are two records to join the EntityType with the two related LicenseTypes.
And this works. However, my code also adds (it duplicates) the LicenseType record and adds it in the LicenseType table for those records that are being associated.
How can I stop this from happening?
In order to avoid the duplication you must attach the licenseType to the context:
_context.LicenseTypes.Attach(licenseType);
_currentEntity.LicenseTypes.Add(licenseType);
_context.SaveChanges();

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);
}
}

Entity Framework options to map list of strings or list of int (List<string>)

I want to store an object that contains a List of primitives using EF.
public class MyObject {
public int Id {get;set;}
public virtual IList<int> Numbers {get;set;}
}
I know that EF cannot store this, but I'd like to know possible solutions to solve this problem.
The 2 Solutions I can think of are:
1.Create a Dummy object that has an Id and the Integervalue, e.g.
public class MyObject {
public int Id {get;set;}
public virtual IList<MyInt> Numbers {get;set;}
}
public class MyInt {
public int Id {get;set;}
public int Number {get;set;}
}
2.Store the list values as a blob, e.g.
public class MyObject {
public int Id {get;set;}
/// use NumbersValue to persist/load the list values
public string NumbersValue {get;set;}
[NotMapped]
public virtual IList<int> Numbers {
get {
return NumbersValue.split(',');
}
set {
NumbersValue = value.ToArray().Join(",");
}
}
}
The Problem with the 2. approach is, that I have to create a Custom IList implementation to keep track if someone modifies the returned collection.
Is there a better solution for this?
Although I do not like to answer my own question, but here is what solved my problem:
After I found this link about Complex Types I tried several implementations, and after some headache I ended up with this.
The List values get stored as a string on the table directly, so it's not required to perform several joins in order to get the list entries. Implementors only have to implement the conversation for each list entry to a persistable string (see the Code example).
Most of the code is handled in the Baseclass (PersistableScalarCollection). You only have to derive from it per datatype (int, string, etc) and implement the method to serialize/deserialize the value.
It's important to note, that you cannot use the the generic baseclass directly (when you remove the abstract). It seems that EF cannot work with that. You also have to make sure to annotate the derived class with the [ComplexType] attribute.
Also note that it seems not to be possible to implement a ComplexType for IList<T> because EF complains about the Indexer (therefore I went on with ICollection).
It's also important to note, that since everything is stored within one column, you cannot search for values in the Collection (at least on the database). In this case you may skip this implementation or denormalize the data for searching.
Example for a Collection of integers:
/// <summary>
/// ALlows persisting of a simple integer collection.
/// </summary>
[ComplexType]
public class PersistableIntCollection : PersistableScalarCollection<int> {
protected override int ConvertSingleValueToRuntime(string rawValue) {
return int.Parse(rawValue);
}
protected override string ConvertSingleValueToPersistable(int value) {
return value.ToString();
}
}
Usage example:
public class MyObject {
public int Id {get;set;}
public virtual PersistableIntCollection Numbers {get;set;}
}
This is the baseclass that handles the persistence aspect by storing the list entries within a string:
/// <summary>
/// Baseclass that allows persisting of scalar values as a collection (which is not supported by EF 4.3)
/// </summary>
/// <typeparam name="T">Type of the single collection entry that should be persisted.</typeparam>
[ComplexType]
public abstract class PersistableScalarCollection<T> : ICollection<T> {
// use a character that will not occur in the collection.
// this can be overriden using the given abstract methods (e.g. for list of strings).
const string DefaultValueSeperator = "|";
readonly string[] DefaultValueSeperators = new string[] { DefaultValueSeperator };
/// <summary>
/// The internal data container for the list data.
/// </summary>
private List<T> Data { get; set; }
public PersistableScalarCollection() {
Data = new List<T>();
}
/// <summary>
/// Implementors have to convert the given value raw value to the correct runtime-type.
/// </summary>
/// <param name="rawValue">the already seperated raw value from the database</param>
/// <returns></returns>
protected abstract T ConvertSingleValueToRuntime(string rawValue);
/// <summary>
/// Implementors should convert the given runtime value to a persistable form.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
protected abstract string ConvertSingleValueToPersistable(T value);
/// <summary>
/// Deriving classes can override the string that is used to seperate single values
/// </summary>
protected virtual string ValueSeperator {
get {
return DefaultValueSeperator;
}
}
/// <summary>
/// Deriving classes can override the string that is used to seperate single values
/// </summary>
protected virtual string[] ValueSeperators {
get {
return DefaultValueSeperators;
}
}
/// <summary>
/// DO NOT Modeify manually! This is only used to store/load the data.
/// </summary>
public string SerializedValue {
get {
var serializedValue = string.Join(ValueSeperator.ToString(),
Data.Select(x => ConvertSingleValueToPersistable(x))
.ToArray());
return serializedValue;
}
set {
Data.Clear();
if (string.IsNullOrEmpty(value)) {
return;
}
Data = new List<T>(value.Split(ValueSeperators, StringSplitOptions.None)
.Select(x => ConvertSingleValueToRuntime(x)));
}
}
#region ICollection<T> Members
public void Add(T item) {
Data.Add(item);
}
public void Clear() {
Data.Clear();
}
public bool Contains(T item) {
return Data.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex) {
Data.CopyTo(array, arrayIndex);
}
public int Count {
get { return Data.Count; }
}
public bool IsReadOnly {
get { return false; }
}
public bool Remove(T item) {
return Data.Remove(item);
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator() {
return Data.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator() {
return Data.GetEnumerator();
}
#endregion
}
I'm using EF Core and had a similar problem but solved it in a simpler way.
The idea is to store the list of integers as a comma separated string in the database. I do that by specifying a ValueConverter in my entity type builder.
public class MyObjectBuilder : IEntityTypeConfiguration<MyObject>
{
public void Configure(EntityTypeBuilder<MyObject> builder)
{
var intArrayValueConverter = new ValueConverter<int[], string>(
i => string.Join(",", i),
s => string.IsNullOrWhiteSpace(s) ? new int[0] : s.Split(new[] { ',' }).Select(v => int.Parse(v)).ToArray());
builder.Property(x => x.Numbers).HasConversion(intArrayValueConverter);
}
}
More information can be found here: https://entityframeworkcore.com/knowledge-base/37370476/how-to-persist-a-list-of-strings-with-entity-framework-core-
Bernhard's answer is brilliant. I just couldn't help but refine it a little. Here's my two cents:
[ComplexType]
public abstract class EFPrimitiveCollection<T> : IList<T>
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual int Id { get; set; }
const string DefaultValueSeperator = "|";
readonly string[] DefaultValueSeperators = new string[] { DefaultValueSeperator };
[NotMapped]
private List<T> _data;
[NotMapped]
private string _value;
[NotMapped]
private bool _loaded;
protected virtual string ValueSeparator => DefaultValueSeperator;
protected virtual string[] ValueSeperators => DefaultValueSeperators;
[ShadowColumn, MaxLength]
protected virtual string Value // Change this to public if you prefer not to use the ShadowColumnAttribute
{
get => _value;
set
{
_data.Clear();
_value = value;
if (string.IsNullOrWhiteSpace(_value))
return;
_data = _value.Split(ValueSeperators, StringSplitOptions.None)
.Select(x => ConvertFromString(x)).ToList();
if (!_loaded) _loaded = true;
}
}
public EFPrimitiveCollection()
{
_data = new List<T>();
}
void UpdateValue()
{
_value = string.Join(ValueSeparator.ToString(),
_data.Select(x => ConvertToString(x))
.ToArray());
}
public abstract T ConvertFromString(string value);
public abstract string ConvertToString(T value);
#region IList Implementation
public int Count
{
get
{
EnsureData();
return _data.Count;
}
}
public T this[int index]
{
get
{
EnsureData();
return _data[index];
}
set
{
EnsureData();
_data[index] = value;
}
}
public bool IsReadOnly => false;
void EnsureData()
{
if (_loaded)
return;
if (string.IsNullOrWhiteSpace(_value))
return;
if (_data.Count > 0) return;
if (!_loaded) _loaded = true;
_data = _value.Split(ValueSeperators, StringSplitOptions.None)
.Select(x => ConvertFromString(x)).ToList();
}
public void Add(T item)
{
EnsureData();
_data.Add(item);
UpdateValue();
}
public bool Remove(T item)
{
EnsureData();
bool res = _data.Remove(item);
UpdateValue();
return res;
}
public void Clear()
{
_data.Clear();
UpdateValue();
}
public bool Contains(T item)
{
EnsureData();
return _data.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
EnsureData();
_data.CopyTo(array, arrayIndex);
}
public int IndexOf(T item)
{
EnsureData();
return _data.IndexOf(item);
}
public void Insert(int index, T item)
{
EnsureData();
_data.Insert(index, item);
UpdateValue();
}
public void RemoveAt(int index)
{
EnsureData();
_data.RemoveAt(index);
UpdateValue();
}
public void AddRange(IEnumerable<T> collection)
{
EnsureData();
_data.AddRange(collection);
UpdateValue();
}
public IEnumerator<T> GetEnumerator()
{
EnsureData();
return _data.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
EnsureData();
return _data.GetEnumerator();
}
#endregion
}
With that base class you can have as many derivations as you like:
[ComplexType]
public class EFIntCollection : EFPrimitiveCollection<int>
{
public override int ConvertFromString(string value) => int.Parse(value);
public override string ConvertToString(int value) => value.ToString();
}
[ComplexType]
public class EFInt64Collection : EFPrimitiveCollection<long>
{
public override long ConvertFromString(string value) => long.Parse(value);
public override string ConvertToString(long value) => value.ToString();
}
[ComplexType]
public class EFStringCollection : EFPrimitiveCollection<string>
{
string _separator;
protected override string ValueSeparator => _separator ?? base.ValueSeparator;
public override string ConvertFromString(string value) => value;
public override string ConvertToString(string value) => value;
public EFStringCollection()
{
}
public EFStringCollection(string separator)
{
_separator = separator;
}
}
EFPrimitiveCollection works just like a list, so you shouldn't have any issues using it like a normal List. Also the data is loaded on demand.
Here's an example:
if (store.AcceptedZipCodes == null)
store.AcceptedZipCodes = new EFStringCollection();
store.AcceptedZipCodes.Clear();
store.AcceptedZipCodes.AddRange(codes.Select(x => x.Code));
Shadow Column
This attribute is being used to abstract away the Value property. If you do not see the need to do this, simply remove it and make the Value property public.
More information can be found on the ShadowColumnAttribute in my answer here
The simple solution would be in your DataContext (a class that implements DbContext) add this override:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<MyObject>()
.Property(p => p.Numbers)
.HasConversion(
toDb => string.Join(",", toDb),
fromDb => fromDb.Split(',').Select(Int32.Parse).ToList() ?? new List<int>());
}

Optgroup drop-down support in MVC - Problems with Model Binding

I wonder if anyone can shed some light on this problem..
I've got an option group drop-down for selecting a person's ethnicity – however it’s not storing the value in the model.
ViewModel
[UIHint("EthnicOriginEditorTemplate")]
[DisplayName("Question 6: Ethnic Origin")]
public int EthnicOrigin { get; set; }
Helper : GroupDropList.Cs
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;
namespace Public.Helpers
{
public static class GroupDropListExtensions
{
public static string GroupDropList(this HtmlHelper helper, string name, IEnumerable<GroupDropListItem> data, int SelectedValue, object htmlAttributes)
{
if (data == null && helper.ViewData != null)
data = helper.ViewData.Eval(name) as IEnumerable<GroupDropListItem>;
if (data == null) return string.Empty;
var select = new TagBuilder("select");
if (htmlAttributes != null)
select.MergeAttributes(new RouteValueDictionary(htmlAttributes));
select.GenerateId(name);
var optgroupHtml = new StringBuilder();
var groups = data.ToList();
foreach (var group in data)
{
var groupTag = new TagBuilder("optgroup");
groupTag.Attributes.Add("label", helper.Encode(group.Name));
var optHtml = new StringBuilder();
foreach (var item in group.Items)
{
var option = new TagBuilder("option");
option.Attributes.Add("value", helper.Encode(item.Value));
if (SelectedValue != 0 && item.Value == SelectedValue)
option.Attributes.Add("selected", "selected");
option.InnerHtml = helper.Encode(item.Text);
optHtml.AppendLine(option.ToString(TagRenderMode.Normal));
}
groupTag.InnerHtml = optHtml.ToString();
optgroupHtml.AppendLine(groupTag.ToString(TagRenderMode.Normal));
}
select.InnerHtml = optgroupHtml.ToString();
return select.ToString(TagRenderMode.Normal);
}
}
public class GroupDropListItem
{
public string Name { get; set; }
public List<OptionItem> Items { get; set; }
}
public class OptionItem
{
public string Text { get; set; }
public int Value { get; set; }
}
}
This is my EditorTemplate
<%# Import Namespace="Public.Helpers"%>
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<int>"%>
<%=Html.GroupDropList("EthnicOrigin",
new[]
{
new GroupDropListItem
{
Name = "Ethnicity",
Items = new List<OptionItem>
{
new OptionItem {Value = 0, Text = "Please Select"}
}
},
new GroupDropListItem
{
Name = "a) White",
Items = new List<OptionItem>
{
new OptionItem {Value = 1, Text = "British"},
new OptionItem {Value = 2, Text = "Irish"},
new OptionItem {Value = 3, Text = "Other White (Please specify below)"}
}
},
--snip
}, Model, null)%>
And in the view I'm referencing it as:
<%=Html.EditorFor(x => x.EthnicOrigin, "EthnicOriginEditorTemplate")%>
However it's not passing through the selected Value into the model... has anyone experienced similar problems... many thanks in advance for some pointers.
Your select doesn't have a name attribute and so when you submit the form the selected value is not sent to the server. You need to add a name:
select.GenerateId(name);
select.MergeAttribute("name", name);
Just changed the helper class to get it work for MVC 3 and with nullable int.
Thanks a lot for the class, saves me plenty of time.
public static class GroupDropListExtensions
{
public static MvcHtmlString GroupDropList(this HtmlHelper helper, string name, IEnumerable<GroupDropListItem> data, int? SelectedValue, object htmlAttributes)
{
if (data == null && helper.ViewData != null)
data = helper.ViewData.Eval(name) as IEnumerable<GroupDropListItem>;
if (data == null) return new MvcHtmlString(string.Empty);
var select = new TagBuilder("select");
if (htmlAttributes != null)
select.MergeAttributes(new RouteValueDictionary(htmlAttributes));
select.GenerateId(name);
select.MergeAttribute("name", name);
var optgroupHtml = new StringBuilder();
var groups = data.ToList();
foreach (var group in data)
{
var groupTag = new TagBuilder("optgroup");
groupTag.Attributes.Add("label", helper.Encode(group.Name));
var optHtml = new StringBuilder();
foreach (var item in group.Items)
{
var option = new TagBuilder("option");
option.Attributes.Add("value", helper.Encode(item.Value));
if (SelectedValue != 0 && item.Value == SelectedValue)
option.Attributes.Add("selected", "selected");
option.InnerHtml = helper.Encode(item.Text);
optHtml.AppendLine(option.ToString(TagRenderMode.Normal));
}
groupTag.InnerHtml = optHtml.ToString();
optgroupHtml.AppendLine(groupTag.ToString(TagRenderMode.Normal));
}
select.InnerHtml = optgroupHtml.ToString();
return new MvcHtmlString(select.ToString(TagRenderMode.Normal));
}
}
public class GroupDropListItem
{
public string Name { get; set; }
public List<OptionItem> Items { get; set; }
}
public class OptionItem
{
public string Text { get; set; }
public int Value { get; set; }
}
This is supported natively using SelectListGroup as of ASP.NET MVC 5.2:
var items = new List<SelectListItem>();
var group1 = new SelectListGroup() { Name = "Group 1" };
items.Add(new SelectListItem() { Text = "Item1", Group = group1 });
Then in MVC, do
#Html.DropDownList("select", items)