Click here to Skip to main content
15,881,898 members
Articles / Desktop Programming / Windows Forms

Entity Framework in WinForms

Rate me:
Please Sign up or sign in to vote.
4.89/5 (142 votes)
28 Jul 2014CPOL29 min read 942.7K   58.1K   425  
A component that makes it easy to use Entity Framework in WinForms projects, including design-time binding support.
using System;
using System.Globalization;
using System.Reflection;
using System.Data;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Forms;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace EFWinforms
{
    /// <summary>
    /// Exposes an ObjectSet in the current ObjectContext as a bindable data source.
    /// </summary>
    /// <remarks>
    /// This class implements the IListSource interface and returns an IBindingList 
    /// built on top of the underlying ObjectSet.
    /// </remarks>
    public class EntitySet :
        IListSource,
        IQueryable
    {
        //-------------------------------------------------------------------------
        #region ** fields

        EntityDataSource _ds;       // EntityDataSource that created this set
        IQueryable _query;          // the value of the property
        IEntityBindingList _list;   // default view for this set
        PropertyInfo _pi;           // the property on the object context that gets the objects in this set
        Type _elementType;          // the type of object in this set
        ListDictionary _dctLookup;  // lookup dictionary (used to show and edit related entities in grid cells)

        #endregion

        //-------------------------------------------------------------------------
        #region ** ctor

        /// <summary>
        /// Initializes a new instance of a <see cref="EntitySet"/>.
        /// </summary>
        /// <param name="ds"><see cref="EntityDataSource"/> that owns the entities.</param>
        /// <param name="pi"><see cref="PropertyInfo"/> used to retrieve the set from the context.</param>
        internal EntitySet(EntityDataSource ds, PropertyInfo pi)
        {
            var type = pi.PropertyType;
            Debug.Assert(
                type.IsGenericType &&
                type.GetGenericTypeDefinition() == typeof(ObjectSet<>) &&
                type.GetGenericArguments().Length == 1);

            _ds = ds;
            _pi = pi;
            _elementType = type.GetGenericArguments()[0];
        }

        #endregion

        //-------------------------------------------------------------------------
        #region ** object model

        /// <summary>
        /// Gets the <see cref="EntityDataSource"/> that owns this entity set.
        /// </summary>
        public EntityDataSource DataSource
        {
            get { return _ds; }
        }
        /// <summary>
        /// Gets the name of this entity set.
        /// </summary>
        public string Name
        {
            get { return _pi != null ? _pi.Name : null; }
        }
        /// <summary>
        /// Gets the type of entity in this entity set.
        /// </summary>
        /// <remarks>
        /// Name chosen for consistency with EntitySet.ElementType 
        /// (EntityType would seem more appropriate).
        /// </remarks>
        public Type ElementType
        {
            get { return _elementType; }
        }
        /// <summary>
        /// Gets the <see cref="IQueryable"/> object that retrieves the entities in this set.
        /// </summary>
        public IQueryable Query
        {
            get 
            {
                if (_query == null && _ds.ObjectContext != null && _pi != null)
                {
                    _query = _pi.GetValue(_ds.ObjectContext, null) as IQueryable;
                }
                return _query; 
            }
        }
        /// <summary>
        /// Gets a list of the entities in the set that have not been deleted or detached.
        /// </summary>
        public IEnumerable ActiveEntities
        {
            get { return GetActiveEntities(Query); }
        }
        /// <summary>
        /// Gets a list of the entities in the set that have not been deleted or detached.
        /// </summary>
        internal static IEnumerable GetActiveEntities(IEnumerable query)
        {
            if (query != null)
            {
                foreach (object item in query)
                {
                    var state = item is EntityObject
                        ? ((EntityObject)item).EntityState
                        : EntityState.Unchanged;
                    switch (state)
                    {
                        case EntityState.Deleted:
                        case EntityState.Detached:
                            break;
                        default:
                            yield return item;
                            break;
                    }
                }
            }
        }
        /// <summary>
        /// Cancels any pending changes on this entity set.
        /// </summary>
        internal void CancelChanges()
        {
            if (_list != null && Query != null)
            {
                _ds.ObjectContext.Refresh(RefreshMode.StoreWins, Query);
                _list.Refresh();
            }
        }
        /// <summary>
        /// Refreshes this set's view by re-loading from the database.
        /// </summary>
        public void RefreshView()
        {
            if (_list != null && Query != null)
            {
                _ds.ObjectContext.Refresh(RefreshMode.ClientWins, Query);
                _list.Refresh();
            }
        }
        /// <summary>
        /// Gets an <see cref="IBindingListView"/> that can be used as a data source for bound controls.
        /// </summary>
        public IBindingListView List
        {
            get { return GetBindingList(); }
        }
        /// <summary>
        /// Gets a dictionary containing entities as keys and their string representation as values.
        /// </summary>
        /// <remarks>
        /// The data map is useful for displaying and editing entities in grid cells.
        /// </remarks>
        public ListDictionary LookupDictionary
        {
            get
            {
                if (_dctLookup == null)
                {
                    _dctLookup = BuildLookupDictionary();
                }
                return _dctLookup;
            }
        }
        #endregion

        //-------------------------------------------------------------------------
        #region ** IListSource

        bool IListSource.ContainsListCollection
        {
            get { return false; }
        }
        IList IListSource.GetList()
        {
            return GetBindingList();
        }

        #endregion

        //-------------------------------------------------------------------------
        #region ** implementation

        // gets an IBindingListView for this entity set
        IBindingListView GetBindingList()
        {
            if (_list == null)
            {
                var listType = typeof(EntityBindingList<>);
                listType = listType.MakeGenericType(this.ElementType);
                _list = (IEntityBindingList)Activator.CreateInstance(listType, _ds, this.Query);
                _list.ListChanged += _list_ListChanged;
            }
            return _list;
        }

        // update data map when list changes
        void _list_ListChanged(object sender, ListChangedEventArgs e)
        {
            if (_dctLookup != null)
            {
                // clear old dictionary
                _dctLookup.Clear();

                // build new dictionary
                var map = BuildLookupDictionary(_list);
                foreach (var kvp in map)
                {
                    _dctLookup.Add(kvp.Key, kvp.Value);
                }
            }
        }

        // build a data map for this entity set
        ListDictionary BuildLookupDictionary()
        {
            return BuildLookupDictionary(ActiveEntities);
        }
        ListDictionary BuildLookupDictionary(IEnumerable entities)
        {
            // if the entity implements "ToString", then use it
            var mi = _elementType.GetMethod("ToString");
            if (mi != null && mi.DeclaringType == _elementType)
            {
                var list = new List<KVPair>();
                foreach (object item in entities)
                {
                    list.Add(new KVPair(item, item.ToString()));
                }
            }

            // use "DefaultProperty"
            var atts = _elementType.GetCustomAttributes(typeof(DefaultPropertyAttribute), false);
            if (atts != null && atts.Length > 0)
            {
                var dpa = atts[0] as DefaultPropertyAttribute;
                var pi = _elementType.GetProperty(dpa.Name);
                if (pi != null && pi.PropertyType == typeof(string))
                {
                    var list = new List<KVPair>();
                    foreach (object item in entities)
                    {
                        list.Add(new KVPair(item, (string)pi.GetValue(item, null)));
                    }
                    return BuildLookupDictionary(list);
                }
            }

            // no default property: look for properties of type string with 
            // names that contain "Name" or "Description"
            foreach (var pi in _elementType.GetProperties())
            {
                if (pi.PropertyType == typeof(string))
                {
                    if (pi.Name.IndexOf("Name", StringComparison.OrdinalIgnoreCase) > -1 ||
                        pi.Name.IndexOf("Description", StringComparison.OrdinalIgnoreCase) > -1)
                    {
                        var list = new List<KVPair>();
                        foreach (object item in entities)
                        {
                            list.Add(new KVPair(item, (string)pi.GetValue(item, null)));
                        }
                        return BuildLookupDictionary(list);
                    }
                }
            }

            // no dice
            return null;
        }
        ListDictionary BuildLookupDictionary(List<KVPair> list)
        {
            // sort list display value
            list.Sort();

            // create data map
            var map = new ListDictionary();
            foreach (var kvp in list)
            {
                map.Add(kvp.Key, kvp.Value);
            }

            // done
            return map;
        }
        class KVPair : IComparable
        {
            public KVPair(object key, string value)
            {
                Key = key;
                Value = value;
            }
            public object Key { get; set; }
            public string Value { get; set; }
            int IComparable.CompareTo(object obj)
            {
 	            return string.Compare(this.Value, ((KVPair)obj).Value, StringComparison.OrdinalIgnoreCase);
            }
        }

        #endregion

        //-------------------------------------------------------------------------
        #region ** IQueryable

        Type IQueryable.ElementType
        {
            get { return _elementType; }
        }
        Expression IQueryable.Expression
        {
            get { return Query.Expression; }
        }
        IQueryProvider IQueryable.Provider
        {
            get { return Query.Provider; }
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return Query.GetEnumerator();
        }

        #endregion
    }
    /// <summary>
    /// Collection of EntitySet objects.
    /// </summary>
    public class EntitySetCollection : ObservableCollection<EntitySet>
    {
        public EntitySet this[string name]
        {
            get
            {
                var index = this.IndexOf(name);
                return index > -1 ? this[index] : null;
            }
        }
        public bool Contains(string name)
        {
            return IndexOf(name) > -1;
        }
        public int IndexOf(string name)
        {
            for (int i = 0; i < Count; i++)
            {
                if (this[i].Name == name)
                    return i;
            }
            return -1;
        }
    }
    /// <summary>
    /// Dictionary that implements IListSource (used for implementing lookup dictionaries)
    /// </summary>
    public class ListDictionary : Dictionary<object, string>, IListSource
    {
        public bool ContainsListCollection
        {
            get { throw new NotImplementedException(); }
        }
        public IList GetList()
        {
            var list = new List<KeyValuePair<object, string>>();
            foreach (var item in this)
            {
                list.Add(item);
            }
            return list;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Brazil Brazil
Software Architect/Developer with several years experience creating and delivering software.

Full-stack Web development (including React, Firebase, TypeScript, HTML, CSS), Entity Framework, C#, MS SQL Server.

Passionate about new technologies and always keen to learn new things as well as improve on existing skills.

Comments and Discussions