Showing posts with label Entity Framework. Show all posts
Showing posts with label Entity Framework. Show all posts

Entity Framework Custom Profile Provider

The standard aspnetdb based System.Web.Profile provider stores profile properties in a way that doesn't allow you to efficiently query users by their profile properties.

Here's an example of how the data is stored in the standard aspnet_Profile table:

UserId PropertyNames PropertyValuesString
5BAE09D2-B5B0-45A1-8A2D-FFCBC75E5DCA ExampleA:S:0:4:ExampleB:S:4:8:ExampleC:S:12:6: TE52TE5212Q5TE5212
E32EDEF0-D07E-417E-B68E-FF70E2B1D9E5 ExampleA:S:0:4:ExampleB:S:4:8:ExampleC:S:12:6: TJQGZJQG4MT5ZJQG4M

To get around this limitation I've created a brand new table and my own custom profile provider which uses Entity Framework for data access and includes an extra method for querying against profile properties.

Here's what my aspnet_Profile2 table looks like:

Column Name Data Type Allow Nulls
(PK)Id int
(FK)UserId uniqueidentifier
ExampleA nvarchar(4) X
ExampleB nvarchar(6) X
LastUpdatedTime datetime

The Id, UserId and LastUpdatedTime are mandatory columns which are required by my custom profile provider. ExampleA and ExampleB are my profile property columns.

Here's what my web.config looks like, first of all don't forget your connection string:

<connectionStrings>
    <add name="ApplicationServices" connectionString="data source=server;Initial Catalog=aspnetdb;User Id=sa;Password=password;MultipleActiveResultSets=true" providerName="System.Data.SqlClient" />
</connectionStrings>

Next inside the system.web section here is the modified profile declaration. Important things to note hear are that we need to set a defaultProvider and to inherit from our assembly so that strongly named profile properties can be used. It's also important to give your application a name to avoid deployment issues further down the line. I've commented out the original default profile provider.

    <profile inherits="Acme.MyApplication.ProfileCommon,MyApplication" defaultProvider="EfTableProfileProvider">
      <providers>
        <clear />        
        <add name="EfTableProfileProvider" type="Acme.MyApplication.EfTableProfileProvider,Facade" connectionStringName="ApplicationServices" applicationName="/MyApplication"/>
        <!--<add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/MyApplication" />-->        
      </providers>
    </profile>

Here is the ProfileCommon class that inherits form ProfileBase and defines our strongly named profile properties. It also has an expression builder so that we can query profiles by properties. This is the class that should be added to the 'inherits' property in the web.config.

public class ProfileCommon : ProfileBase
    {
        public static ProfileCommon GetProfile()
        {
            return Create(HttpContext.Current.Request.IsAuthenticated ?
                   HttpContext.Current.User.Identity.Name : HttpContext.Current.Request.AnonymousID,
                   HttpContext.Current.Request.IsAuthenticated) as ProfileCommon;
        }

        public static ProfileCommon GetUserProfile(string username)
        {
            return Create(username) as ProfileCommon;
        }

        public static ProfileCommon GetUserProfile()
        {
            return Create(Membership.GetUser().UserName) as ProfileCommon;
        }

        [CustomProviderData("ExampleA;nvarchar")]
        public virtual string WinDSSId
        {
            get
            {
                return ((string)(this.GetPropertyValue("ExampleA")));
            }
            set
            {
                this.SetPropertyValue("ExampleA", value);
            }
        }

        [CustomProviderData("ExampleB;nvarchar")]
        public virtual string SAPId
        {
            get
            {
                return ((string)(this.GetPropertyValue("ExampleB")));
            }
            set
            {
                this.SetPropertyValue("ExampleB", value);
            }
        }

        static IQueryable<T> ETForStartsWith<T>(IQueryable<T> query, string propertyValue, PropertyInfo propertyInfo)
        {
            ParameterExpression e = Expression.Parameter(typeof(T), "e");
            MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
            ConstantExpression c = Expression.Constant(propertyValue, typeof(string));
            MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
            Expression call = Expression.Call(m, mi, c);

            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, e);
            return query.Where(lambda);
        }
    }

Here is the Entity Framework class that maps to my aspnetdb_Profile2 table. The User object can be found in my previous blog post about creating a custom membership provider, which also contains the data context class.

    [Table("aspnet_Profile2")]
    public class Profile2
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
        [ForeignKey("UserId")]
        public virtual User User { get; set; }
        public Guid UserId { get; set; }
        public string ExmapleA { get; set; }
        public string ExampleB { get; set; }
        public DateTime LastUpdatedDate { get; set; }
    }

Finally here's the class that overrides the default profile provider. It has an extra method called FindProfilesByPropertyValue which takes a string representing the name of the property to search and another string containing the value to match.

    public class EfTableProfileProvider : ProfileProvider
    {
        private string _appName;
        private Guid _appId;
        private bool _appIdSet;
        private string _sqlConnectionString;

        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
                throw new ArgumentNullException("config");
            if (String.IsNullOrEmpty(name))
                name = "EfTableProfileProvider";
            if (string.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "Entity Framework Table Profile Provider");
            }
            base.Initialize(name, config);

            string temp = config["connectionStringName"];
            if (String.IsNullOrEmpty(temp))
                throw new ProviderException("connectionStringName not specified");
            _sqlConnectionString = GetConnectionString(temp);
            if (String.IsNullOrEmpty(_sqlConnectionString))
            {
                throw new ProviderException("connectionStringName not specified");
            }

            _appName = config["applicationName"];
            if (string.IsNullOrEmpty(_appName))
                _appName = GetDefaultAppName();

            if (_appName.Length > 256)
            {
                throw new ProviderException("Application name too long");
            }

            config.Remove("connectionStringName");
            config.Remove("applicationName");
            if (config.Count > 0)
            {
                string attribUnrecognized = config.GetKey(0);
                if (!String.IsNullOrEmpty(attribUnrecognized))
                    throw new ProviderException("Unrecognized config attribute:" + attribUnrecognized);
            }
        }

        internal static string GetConnectionString(string specifiedConnectionString)
        {
            if (String.IsNullOrEmpty(specifiedConnectionString))
                return null;

            ConnectionStringSettings connObj = ConfigurationManager.ConnectionStrings[specifiedConnectionString];
            if (connObj != null)
                return connObj.ConnectionString;

            return null;
        }

        internal static string GetDefaultAppName()
        {
            try
            {
                string appName = HostingEnvironment.ApplicationVirtualPath;
                if (String.IsNullOrEmpty(appName))
                {
                    appName = System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName;
                    int indexOfDot = appName.IndexOf('.');
                    if (indexOfDot != -1)
                    {
                        appName = appName.Remove(indexOfDot);
                    }
                }

                if (String.IsNullOrEmpty(appName))
                {
                    return "/";
                }
                else
                {
                    return appName;
                }
            }
            catch (SecurityException)
            {
                return "/";
            }
        }

        public override string ApplicationName
        {
            get { return _appName; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("ApplicationName");
                if (value.Length > 256)
                {
                    throw new ProviderException("Application name too long");
                }
                _appName = value;
                _appIdSet = false;
            }
        }

        private Guid AppId
        {
            get
            {
                if (!_appIdSet)
                {
                    try
                    {
                        using (Data.MembershipContext context = new Data.MembershipContext())
                        {
                            Application application = null;
                            application = context.ApplicationData
                                .Where(a => a.LoweredApplicationName.Equals(ApplicationName.ToLower()))
                                .FirstOrDefault();

                            if (application == null)
                            {
                                application = new Application()
                                    {
                                        ApplicationName = ApplicationName,
                                        ApplicationId = Guid.NewGuid(),
                                        Description = "Entity Framework Table Profile Provider",
                                        LoweredApplicationName = ApplicationName.ToLower()
                                    };
                                context.ApplicationData.Add(application);
                                context.SaveChanges();
                            }
                            _appId = application.ApplicationId;
                            _appIdSet = true;
                        }

                    }
                    catch (Exception ex)
                    {
                        Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
                    }

                }
                return _appId;
            }
        }

        private static string s_legalChars = "_@#$";
        private static void EnsureValidTableOrColumnName(string name)
        {
            for (int i = 0; i < name.Length; ++i)
            {
                if (!Char.IsLetterOrDigit(name[i]) && s_legalChars.IndexOf(name[i]) == -1)
                    throw new ProviderException("Table and column names cannot contain: " + name[i]);
            }
        }

        private void GetProfileDataFromTable(SettingsPropertyCollection properties, SettingsPropertyValueCollection svc, string username)
        {
            List<ProfileColumnData> columnData = new List<ProfileColumnData>(properties.Count);

            int columnCount = 0;
            foreach (SettingsProperty prop in properties)
            {
                SettingsPropertyValue value = new SettingsPropertyValue(prop);
                svc.Add(value);

                string persistenceData = prop.Attributes["CustomProviderData"] as string;
                if (String.IsNullOrEmpty(persistenceData))
                {
                    continue;
                }
                string[] chunk = persistenceData.Split(new char[] { ';' });
                if (chunk.Length != 2)
                {
                    continue;
                }
                string columnName = chunk[0];

                SqlDbType datatype = (SqlDbType)Enum.Parse(typeof(SqlDbType), chunk[1], true);

                columnData.Add(new ProfileColumnData(columnName, value, null, datatype));

                ++columnCount;
            }

            try
            {
                Profile2 profile;
                Guid userId;
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    profile = context.TableProfileData
                        .Where(p => p.User.UserName.ToLower().Equals(username))
                        .FirstOrDefault();
                    userId = context.UserData
                        .Where(u => u.UserName.Equals(username))
                        .Select(u => u.UserId)
                        .FirstOrDefault();
                }
                if (profile != null)
                {
                    for (int i = 0; i < columnData.Count; ++i)
                    {
                        ProfileColumnData colData = columnData[i];
                        colData.PropertyValue.PropertyValue = profile.GetType().GetProperty(columnData[i].ColumnName).GetValue(profile, null);
                        colData.Value = colData.PropertyValue.PropertyValue;
                        SettingsPropertyValue propValue = colData.PropertyValue;
                    }
                    UpdateLastActivityDate(userId);
                }
            }
            catch (Exception ex)
            {
                Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
            }
        }

        private static void UpdateLastActivityDate(Guid userId)
        {
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var user = context.UserData
                        .Where(u => u.UserId.Equals(userId))
                        .FirstOrDefault();
                    if (user != null)
                    {
                        user.LastActivityDate = DateTime.Now;
                        context.Entry(user).State = EntityState.Modified;
                        context.SaveChanges();
                    }
                }
            }
            catch (Exception ex)
            {
                Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
            }
        }

        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
        {
            SettingsPropertyValueCollection svc = new SettingsPropertyValueCollection();

            if (collection == null || collection.Count < 1 || context == null)
                return svc;

            string username = (string)context["UserName"];
            if (String.IsNullOrEmpty(username))
                return svc;

            GetProfileDataFromTable(collection, svc, username);

            return svc;
        }

        private struct ProfileColumnData
        {
            public string ColumnName;
            public SettingsPropertyValue PropertyValue;
            public object Value;
            public SqlDbType DataType;

            public ProfileColumnData(string col, SettingsPropertyValue pv, object val, SqlDbType type)
            {
                EnsureValidTableOrColumnName(col);
                ColumnName = col;
                PropertyValue = pv;
                Value = val;
                DataType = type;
            }
        }

        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
        {
            string username = (string)context["UserName"];
            bool userIsAuthenticated = (bool)context["IsAuthenticated"];

            if (username == null || username.Length < 1 || collection.Count < 1)
                return;

            try
            {
                bool anyItemsToSave = false;

                foreach (SettingsPropertyValue pp in collection)
                {
                    if (pp.IsDirty)
                    {
                        if (!userIsAuthenticated)
                        {
                            bool allowAnonymous = (bool)pp.Property.Attributes["AllowAnonymous"];
                            if (!allowAnonymous)
                                continue;
                        }
                        anyItemsToSave = true;
                        break;
                    }
                }

                if (!anyItemsToSave)
                    return;

                List<ProfileColumnData> columnData = new List<ProfileColumnData>(collection.Count);

                foreach (SettingsPropertyValue pp in collection)
                {
                    if (!userIsAuthenticated)
                    {
                        bool allowAnonymous = (bool)pp.Property.Attributes["AllowAnonymous"];
                        if (!allowAnonymous)
                            continue;
                    }

                    if (!pp.IsDirty)
                        continue;

                    string persistenceData = pp.Property.Attributes["CustomProviderData"] as string;

                    if (String.IsNullOrEmpty(persistenceData))
                    {
                        continue;
                    }
                    string[] chunk = persistenceData.Split(new char[] { ';' });
                    if (chunk.Length != 2)
                    {
                        continue;
                    }
                    string columnName = chunk[0];

                    SqlDbType datatype = (SqlDbType)Enum.Parse(typeof(SqlDbType), chunk[1], true);

                    object value = null;


                    if (pp.Deserialized && pp.PropertyValue == null)
                    {
                        value = DBNull.Value;
                    }
                    else
                    {
                        value = pp.PropertyValue;
                    }

                    columnData.Add(new ProfileColumnData(columnName, pp, value, datatype));
                }

                Guid userId = Guid.Empty;

                try
                {
                    using (Data.MembershipContext dataContext = new Data.MembershipContext())
                    {
                        Profile2 profile = null;
                        userId = dataContext.UserData
                            .Where(u => u.LoweredUserName.Equals(username.ToLower())
                                && u.ApplicationId.Equals(AppId))
                            .Select(u => u.UserId)
                            .FirstOrDefault();
                        if (userId != null)
                        {
                            profile = dataContext.TableProfileData
                                    .Where(p => p.UserId.Equals(userId))
                                    .FirstOrDefault();
                            if (profile != null)
                            {
                                profile.LastUpdatedDate = DateTime.Now;
                                foreach (ProfileColumnData data in columnData)
                                {
                                    switch (data.ColumnName)
                                    {
                                        case "SAPId":
                                            profile.SAPId = data.Value.ToString();
                                            break;
                                        case "WinDSSId":
                                            profile.WinDSSId = data.Value.ToString();
                                            break;
                                    }
                                }
                                dataContext.Entry(profile).State = EntityState.Modified;
                            }
                            else
                            {
                                profile = new Profile2();
                                profile.UserId = userId;
                                profile.LastUpdatedDate = DateTime.Now;
                                foreach (ProfileColumnData data in columnData)
                                {
                                    switch (data.ColumnName)
                                    {
                                        case "SAPId":
                                            profile.SAPId = data.Value.ToString();
                                            break;
                                        case "WinDSSId":
                                            profile.WinDSSId = data.Value.ToString();
                                            break;
                                    }
                                }
                                dataContext.TableProfileData.Add(profile);
                            }
                        }
                        dataContext.SaveChanges();
                    }
                }
                catch (Exception ex)
                {
                    Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
                }
            }
            catch (Exception ex)
            {
                Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
            }
        }

        public override int DeleteProfiles(ProfileInfoCollection profiles)
        {
            if (profiles == null)
            {
                throw new ArgumentNullException("profiles");
            }

            if (profiles.Count < 1)
            {
                throw new ArgumentException("Profiles collection is empty");
            }

            string[] usernames = new string[profiles.Count];

            int iter = 0;
            foreach (ProfileInfo profile in profiles)
            {
                usernames[iter++] = profile.UserName;
            }

            return DeleteProfiles(usernames);
        }

        public override int DeleteProfiles(string[] usernames)
        {
            if (usernames == null || usernames.Length < 1)
            {
                return 0;
            }
            int numProfilesDeleted = 0;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var profiles = context.TableProfileData
                        .Where(p => usernames.Contains(p.User.UserName))
                        .ToList();
                    numProfilesDeleted = profiles.Count;
                    profiles.ForEach(p => context.TableProfileData.Remove(p));
                    context.SaveChanges();
                }
            }
            catch (Exception ex)
            {
                Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
            }
            return numProfilesDeleted;
        }

        private List<Profile2> GetInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            List<Profile2> profiles = null;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    switch (authenticationOption)
                    {
                        case ProfileAuthenticationOption.Anonymous:
                            profiles = context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.LastActivityDate <= userInactiveSinceDate
                                    && p.User.IsAnonymous == true)
                                .ToList();
                            break;
                        case ProfileAuthenticationOption.Authenticated:
                            profiles = context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.LastActivityDate <= userInactiveSinceDate
                                    && p.User.IsAnonymous == false)
                                .ToList();
                            break;
                        case ProfileAuthenticationOption.All:
                            profiles = context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.LastActivityDate <= userInactiveSinceDate)
                                .ToList();
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
            }
            return profiles;
        }

        public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            int numProfilesDeleted = 0;
            List<Profile2> profiles = null;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    profiles = GetInactiveProfiles(authenticationOption, userInactiveSinceDate);
                    if (profiles != null)
                    {
                        numProfilesDeleted = profiles.Count();
                        profiles.ForEach(p => context.TableProfileData.Remove(p));
                        context.SaveChanges();
                    }
                }
            }
            catch (Exception ex)
            {
                Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
            }
            return numProfilesDeleted;
        }

        public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            int numProfiles = 0;
            List<Profile2> profiles = null;
            try
            {
                profiles = GetInactiveProfiles(authenticationOption, userInactiveSinceDate);
                numProfiles = profiles.Count();
            }
            catch (Exception ex)
            {
                Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
            }
            return numProfiles;
        }

        public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords)
        {
            List<Profile2> profiles = new List<Profile2>();
            ProfileInfoCollection profileCollection = new ProfileInfoCollection();
            if (pageIndex < 0)
                throw new ArgumentException("pageIndex");
            if (pageSize < 1)
                throw new ArgumentException("pageSize");
            int lowerBound = pageIndex * pageSize;

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    switch (authenticationOption)
                    {
                        case ProfileAuthenticationOption.Anonymous:
                            profiles = context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.IsAnonymous == true)
                                .OrderBy(p => p.User.UserName)
                                .Skip(lowerBound)
                                .Take(pageSize)
                                .ToList();
                            break;
                        case ProfileAuthenticationOption.Authenticated:
                            profiles = context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.IsAnonymous == false)
                                .OrderBy(p => p.User.UserName)
                                .Skip(lowerBound)
                                .Take(pageSize)
                                .ToList();
                            break;
                        case ProfileAuthenticationOption.All:
                            profiles = context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId))
                                .OrderBy(p => p.User.UserName)
                                .Skip(lowerBound)
                                .Take(pageSize)
                                .ToList();
                            break;
                    }
                }
                profiles.ForEach(p => profileCollection.Add(new ProfileInfo(p.User.UserName, p.User.IsAnonymous, p.User.LastActivityDate, p.LastUpdatedDate, 0)));
            }
            catch (Exception ex)
            {
                Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
            }
            totalRecords = profiles.Count;
            return profileCollection;
        }

        public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            List<Profile2> profiles = new List<Profile2>();
            ProfileInfoCollection profileCollection = new ProfileInfoCollection();
            if (pageIndex < 0)
                throw new ArgumentException("pageIndex");
            if (pageSize < 1)
                throw new ArgumentException("pageSize");
            int lowerBound = pageIndex * pageSize;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    switch (authenticationOption)
                    {
                        case ProfileAuthenticationOption.Anonymous:
                            context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.IsAnonymous == true
                                    && p.User.LastActivityDate <= userInactiveSinceDate)
                                .OrderBy(p => p.User.UserName)
                                .Skip(lowerBound)
                                .Take(pageSize)
                                .ToList();
                            break;
                        case ProfileAuthenticationOption.Authenticated:
                            context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.IsAnonymous == false
                                    && p.User.LastActivityDate <= userInactiveSinceDate)
                                .OrderBy(p => p.User.UserName)
                                .Skip(lowerBound)
                                .Take(pageSize)
                                .ToList();
                            break;
                        case ProfileAuthenticationOption.All:
                            context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.LastActivityDate <= userInactiveSinceDate)
                                .OrderBy(p => p.User.UserName)
                                .Skip(lowerBound)
                                .Take(pageSize)
                                .ToList();
                            break;
                    }
                }
                profiles.ForEach(p => profileCollection.Add(new ProfileInfo(p.User.UserName, p.User.IsAnonymous, p.User.LastActivityDate, p.LastUpdatedDate, 0)));
            }
            catch (Exception ex)
            {
                Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
            }
            totalRecords = profiles.Count;
            return profileCollection;
        }

        public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            List<Profile2> profiles = new List<Profile2>();
            ProfileInfoCollection profileCollection = new ProfileInfoCollection();
            if (pageIndex < 0)
                throw new ArgumentException("pageIndex");
            if (pageSize < 1)
                throw new ArgumentException("pageSize");
            int lowerBound = pageIndex * pageSize;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    switch (authenticationOption)
                    {
                        case ProfileAuthenticationOption.Anonymous:
                            context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.IsAnonymous == true
                                    && p.User.UserName.Equals(usernameToMatch))
                                .OrderBy(p => p.User.UserName)
                                .Skip(lowerBound)
                                .Take(pageSize)
                                .ToList();
                            break;
                        case ProfileAuthenticationOption.Authenticated:
                            context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.IsAnonymous == false
                                    && p.User.UserName.Equals(usernameToMatch))
                                .OrderBy(p => p.User.UserName)
                                .Skip(lowerBound)
                                .Take(pageSize)
                                .ToList();
                            break;
                        case ProfileAuthenticationOption.All:
                            context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.UserName.Equals(usernameToMatch))
                                .OrderBy(p => p.User.UserName)
                                .Skip(lowerBound)
                                .Take(pageSize)
                                .ToList();
                            break;
                    }
                }
                profiles.ForEach(p => profileCollection.Add(new ProfileInfo(p.User.UserName, p.User.IsAnonymous, p.User.LastActivityDate, p.LastUpdatedDate, 0)));
            }
            catch (Exception ex)
            {
                Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
            }
            totalRecords = profiles.Count;
            return profileCollection;
        }

        public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            List<Profile2> profiles = new List<Profile2>();
            ProfileInfoCollection profileCollection = new ProfileInfoCollection();
            if (pageIndex < 0)
                throw new ArgumentException("pageIndex");
            if (pageSize < 1)
                throw new ArgumentException("pageSize");
            int lowerBound = pageIndex * pageSize;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    switch (authenticationOption)
                    {
                        case ProfileAuthenticationOption.Anonymous:
                            context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.IsAnonymous == true
                                    && p.User.UserName.Equals(usernameToMatch)
                                    && p.User.LastActivityDate <= userInactiveSinceDate)
                                .OrderBy(p => p.User.UserName)
                                .Skip(lowerBound)
                                .Take(pageSize)
                                .ToList();
                            break;
                        case ProfileAuthenticationOption.Authenticated:
                            context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.IsAnonymous == false
                                    && p.User.UserName.Equals(usernameToMatch)
                                    && p.User.LastActivityDate <= userInactiveSinceDate)
                                .OrderBy(p => p.User.UserName)
                                .Skip(lowerBound)
                                .Take(pageSize)
                                .ToList();
                            break;
                        case ProfileAuthenticationOption.All:
                            context.TableProfileData
                                .Where(p => p.User.ApplicationId.Equals(AppId)
                                    && p.User.UserName.Equals(usernameToMatch)
                                    && p.User.LastActivityDate <= userInactiveSinceDate)
                                .OrderBy(p => p.User.UserName)
                                .Skip(lowerBound)
                                .Take(pageSize)
                                .ToList();
                            break;
                    }
                }
                profiles.ForEach(p => profileCollection.Add(new ProfileInfo(p.User.UserName, p.User.IsAnonymous, p.User.LastActivityDate, p.LastUpdatedDate, 0)));
            }
            catch (Exception ex)
            {
                Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
            }
            totalRecords = profiles.Count;
            return profileCollection;
        }        

        public ProfileInfoCollection FindProfilesByPropertyValue(string PropertyName, object PropertyValue)
        {
            List<Profile2> profiles = new List<Profile2>();
            ProfileInfoCollection profileCollection = new ProfileInfoCollection();
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    PropertyInfo propertyInfo = typeof(Profile2).GetProperty(PropertyName); 
                    ParameterExpression e = Expression.Parameter(typeof(Profile2), "e");
                    MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
                    ConstantExpression c = Expression.Constant(PropertyValue, typeof(string));
                    MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
                    Expression call = Expression.Call(m, mi, c);
                    Expression<Func<Profile2, bool>> lambda = Expression.Lambda<Func<Profile2, bool>>(call, e);

                    profiles = context.TableProfileData
                        .Where(p => p.User.ApplicationId.Equals(AppId))
                        .Where(lambda)
                        .OrderBy(p => p.User.UserName)
                        .ToList();

                    profiles.ForEach(p => profileCollection.Add(new ProfileInfo(p.User.UserName, p.User.IsAnonymous, p.User.LastActivityDate, p.LastUpdatedDate, 0)));
                }                
            }
            catch (Exception ex)
            {
                Log.LogException(ex.Message, ex, LogCategory.Membership, TraceEventType.Error);
            }
            return profileCollection;
        }
    }

Here are a couple of Test Methods that demonstrate how to use the custom profile provider. The tests also use the custom membership provider that I blogged about in a previous post.

 [TestMethod]
        public void CreateProfile()
        {
            EfMembershipProvider membershipProvider = (EfMembershipProvider)Membership.Provider;
            MembershipCreateStatus status = new MembershipCreateStatus();
            EfMembershipUser user = (EfMembershipUser)membershipProvider.CreateUser(
                "testcreateprofile",
                "password",
                "",
                "",
                "",
                true,
                Guid.NewGuid(),
                out status);
            ProfileCommon profile = ProfileCommon.GetUserProfile("testcreateprofile");            
            profile.ExampleA = "1234";
            profile.Save();
            profile = ProfileCommon.GetUserProfile("testcreateprofile");
            string ExampleA = profile.ExampleA;
            membershipProvider.DeleteUser("testcreateprofile", true);
            Assert.AreEqual("1234", profile.ExampleA);
        }

        [TestMethod]
        public void FindProfilesByPropertyValue()
        {
            int count = 0;
            EfMembershipProvider membershipProvider = (EfMembershipProvider)Membership.Provider;
            EfTableProfileProvider profileProvider = (EfTableProfileProvider)ProfileManager.Provider;
            MembershipCreateStatus status = new MembershipCreateStatus();
            EfMembershipUser user = (EfMembershipUser)membershipProvider.CreateUser(
                "testfindprofilebypropertyvalue",
                "password",
                "",
                "",
                "",
                true,
                Guid.NewGuid(),
                out status);
            ProfileCommon profile = ProfileCommon.GetUserProfile("testfindprofilebypropertyvalue");
            profile.ExampleA = "4321";
            profile.Save();
            ProfileInfoCollection profilePropertyCollection = profileProvider.FindProfilesByPropertyValue("ExampleA", "4321");
            count = profilePropertyCollection.Count;
            membershipProvider.DeleteUser("testcreateprofile", true);
            Assert.IsTrue(count > 0);
        }

Entity Framework Custom Membership Provider

Here's an example of a custom MembershipProvider that uses Entity Framework 4.2 for data access.

First you will need to create an aspnetdb database on your SQL Server. On the SQL Server open a command prompt and run the following command:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regsql.exe

In the code below I have created a custom MembershipUser object in which I've included the following columns that I added to the aspnetdb aspnet_Users table:

Id (int, not null) Identity Column
FirstName (nvarchar(256), null)
LastName (nvarchar(256), null)

Add a connection string to your web config to connect to the aspnetdb:

<connectionStrings>
    <add name="ApplicationServices" connectionString="data source=server;Initial Catalog=aspnetdb;User Id=sa;Password=password;MultipleActiveResultSets=true" providerName="System.Data.SqlClient" />
</connectionStrings>

The name of the connection string is important as it is used when declaring the EF data context.

The keys that are used to encrypt passwords and password questions need to be generated and added to the web.config. This is important if your application runs in a server farm.

<system.web>
    <machineKey validationKey="EF3DFB100FD7C5FCC8915EEC705968659FB2982C466B9D67694471F6543C60CE244E5FF8CF049C759784F73CFC2D4E05B1D75D92134C27C1DA644A5D32D82D50"
                decryptionKey="A71798F098622A67A6DFA5472AA46C8E83AF7462DA19BF8B2E23C1B113608F7F"
                validation="SHA1"
                decryption="AES"/>
</system.web>

Here is a snippet of code from a Console application that can be used to generate the 128bit and 64bit keys:

static void GenerateMachineKey(string[] argv)
        {
            int len = 128;
            if (argv.Length > 0)
                len = int.Parse(argv[0]);
            byte[] buff = new byte[len / 2];
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            rng.GetBytes(buff);
            StringBuilder sb = new StringBuilder(len);
            for (int i = 0; i < buff.Length; i++)
                sb.Append(string.Format("{0:X2}", buff[i]));
            Console.WriteLine(sb);
        }

Here's the Membership section from the web.config

<membership userIsOnlineTimeWindow="20" defaultProvider="EfMembershipProvider">
      <providers>
        <clear />
        <add name="EfMembershipProvider"
             type="Company.Namespace.EfMembershipProvider,Assembly" 
             connectionStringName="ApplicationServices"
             enablePasswordRetrieval="false" 
             enablePasswordReset="true" 
             requiresQuestionAndAnswer="false" 
             requiresUniqueEmail="false" 
             maxInvalidPasswordAttempts="5" 
             minRequiredPasswordLength="6" 
             minRequiredNonalphanumericCharacters="0" 
             passwordAttemptWindow="10" 
             applicationName="/facade" 
             description="Entity Framework Membership Provider" />
      </providers>
</membership>

It's important to always give an applicationName, by default this will be the web root of your application which can lead to confusion and problems when you deploy your solution to a live server environment.

You can customise the Profile and RoleManager Providers too, but I've left these as using the standard System.Web.Profile.SqlProfileProvider and System.Web.Security.SqlRoleProvider types.

Here's my data context class that defines the data sets (tables) from the aspnetdb and my custom membership provider class. The "ApplicationServices" in the constructor is the name of the connection string to the aspnetdb database from the web.config

        public class MembershipContext : DbContext
        {
            public MembershipContext()
                : base("ApplicationServices")
            { }

            public DbSet<User> UserData { get; set; }
            public DbSet<Member> MembershipData { get; set; }
            public DbSet<Application> ApplicationData { get; set; }
            public DbSet<UserRoles> UserRoleData { get; set; }
            public DbSet<Profile> ProfileData { get; set; }
        }

Here's the class file (Assembly) that defines the classes used in the data context, overrides the MembershipProvider properties and methods and implements a custom MembershipUser object to include the extra columns I added to the database.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
using System.Web.Configuration;
using System.Collections.Specialized;
using System.Configuration.Provider;
using System.Configuration;
using System.Data;
using System.Security.Cryptography;
using System.Text;
using System.Data.SqlTypes;

namespace Company.Namespace
{    
    [Table("aspnet_Users")]
    public class User
    {
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
        public Guid ApplicationId { get; set; }
        [Key]
        public Guid UserId { get; set; }
        public string UserName { get; set; }
        public string LoweredUserName { get; set; }
        public string MobileAlias { get; set; }
        public bool IsAnonymous { get; set; }
        public DateTime LastActivityDate { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    [Table("aspnet_Membership")]
    public class Member
    {
        [ForeignKey("Application")]
        public Guid ApplicationId { get; set; }       
        public virtual Application Application { get; set; }
        [Key]
        [ForeignKey("User")]        
        public Guid UserId { get; set; }   
        public virtual User User { get; set; }
        public string Password { get; set; }
        public int PasswordFormat { get; set; }
        public string PasswordSalt { get; set; }
        public string MobilePIN { get; set; }
        public string Email { get; set; }
        public string LoweredEmail { get; set; }
        public string PasswordQuestion { get; set; }
        public string PasswordAnswer { get; set; }
        public bool IsApproved { get; set; }
        public bool IsLockedOut { get; set; }
        public DateTime CreateDate { get; set; }
        public DateTime LastLoginDate { get; set; }
        public DateTime LastPasswordChangedDate { get; set; }
        public DateTime LastLockoutDate { get; set; }
        public int FailedPasswordAttemptCount { get; set; }
        public DateTime FailedPasswordAttemptWindowStart { get; set; }
        public int FailedPasswordAnswerAttemptCount { get; set; }
        public DateTime FailedPasswordAnswerAttemptWindowStart { get; set; }
        public string Comment { get; set; }
    }

    [Table("aspnet_Applications")]
    public class Application
    {
        public string ApplicationName { get; set; }
        public string LoweredApplicationName { get; set; }
        [Key]
        public Guid ApplicationId { get; set; }
        public string Description { get; set; }
    }

    [Table("aspnet_UsersInRoles")]
    public class UserRoles
    {
        [Key]
        [Column(Order=1)]
        public Guid UserId { get; set; }
        [Key]
        [Column(Order = 2)]
        public Guid RoleId { get; set; }
    }

    [Table("aspnet_Profile")]
    public class Profile
    {
        [Key]
        public Guid UserId { get; set; }
        public string PropertyNames { get; set; }
        public string PropertyValuesString { get; set; }
        public byte[] PropertyValuesBinary { get; set; }
        public DateTime LastUpdatedDate { get; set; }
    }

    public class EfMembershipProvider : MembershipProvider
    {
        //
        // Global connection string, generated password length, generic exception message, event log info.
        //

        private int newPasswordLength = 8;
        private string exceptionMessage = "An exception occurred. Please check the Log.";

        //
        // Used when determining encryption key values.
        //

        private MachineKeySection machineKey;

        //
        // If false, exceptions are thrown to the caller. If true,
        // exceptions are written to the event log.
        //

        private bool pWriteExceptionsToEventLog;

        public bool WriteExceptionsToEventLog
        {
            get { return pWriteExceptionsToEventLog; }
            set { pWriteExceptionsToEventLog = value; }
        }

        //
        // System.Configuration.Provider.ProviderBase.Initialize Method
        //        

        public override void Initialize(string name, NameValueCollection config)
        {
            //
            // Initialize values from web.config.
            //

            if (config == null)
                throw new ArgumentNullException("config");

            if (name == null || name.Length == 0)
                name = "EfMembershipProvider";

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "Entity Framework Membership Provider");
            }

            // Initialize the abstract base class.
            base.Initialize(name, config);

            pApplicationName = GetConfigValue(config["applicationName"], System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
            pMaxInvalidPasswordAttempts = Convert.ToInt32(GetConfigValue(config["maxInvalidPasswordAttempts"], "5"));
            pPasswordAttemptWindow = Convert.ToInt32(GetConfigValue(config["passwordAttemptWindow"], "10"));
            pMinRequiredNonAlphanumericCharacters = Convert.ToInt32(GetConfigValue(config["minRequiredNonAlphanumericCharacters"], "1"));
            pMinRequiredPasswordLength = Convert.ToInt32(GetConfigValue(config["minRequiredPasswordLength"], "7"));
            pPasswordStrengthRegularExpression = Convert.ToString(GetConfigValue(config["passwordStrengthRegularExpression"], ""));
            pEnablePasswordReset = Convert.ToBoolean(GetConfigValue(config["enablePasswordReset"], "true"));
            pEnablePasswordRetrieval = Convert.ToBoolean(GetConfigValue(config["enablePasswordRetrieval"], "true"));
            pRequiresQuestionAndAnswer = Convert.ToBoolean(GetConfigValue(config["requiresQuestionAndAnswer"], "false"));
            pRequiresUniqueEmail = Convert.ToBoolean(GetConfigValue(config["requiresUniqueEmail"], "true"));
            pWriteExceptionsToEventLog = Convert.ToBoolean(GetConfigValue(config["writeExceptionsToEventLog"], "true"));

            string temp_format = config["passwordFormat"];
            if (temp_format == null)
            {
                temp_format = "Hashed";
            }

            switch (temp_format)
            {
                case "Hashed":
                    pPasswordFormat = MembershipPasswordFormat.Hashed;
                    break;
                case "Encrypted":
                    pPasswordFormat = MembershipPasswordFormat.Encrypted;
                    break;
                case "Clear":
                    pPasswordFormat = MembershipPasswordFormat.Clear;
                    break;
                default:
                    throw new ProviderException("Password format not supported.");
            }            

            // Get encryption and decryption key information from the configuration.
            //Configuration cfg = WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
            //machineKey = (MachineKeySection)cfg.GetSection("system.web/machineKey");
            machineKey = (MachineKeySection)WebConfigurationManager.GetSection("system.web/machineKey");

            if (machineKey.ValidationKey.Contains("AutoGenerate"))
                if (PasswordFormat != MembershipPasswordFormat.Clear)
                    throw new ProviderException("Hashed or Encrypted passwords are not supported with auto-generated keys.");
        }

        //
        // A helper function to retrieve config values from the configuration file.
        //

        private string GetConfigValue(string configValue, string defaultValue)
        {
            if (String.IsNullOrEmpty(configValue))
                return defaultValue;

            return configValue;
        }

        //
        // System.Web.Security.MembershipProvider properties.
        //

        private string pApplicationName;
        private bool pEnablePasswordReset;
        private bool pEnablePasswordRetrieval;
        private bool pRequiresQuestionAndAnswer;
        private bool pRequiresUniqueEmail;
        private int pMaxInvalidPasswordAttempts;
        private int pPasswordAttemptWindow;
        private MembershipPasswordFormat pPasswordFormat;

        public override string ApplicationName
        {
            get { return pApplicationName; }
            set { pApplicationName = value; }
        }

        public override bool EnablePasswordReset
        {
            get { return pEnablePasswordReset; }
        }


        public override bool EnablePasswordRetrieval
        {
            get { return pEnablePasswordRetrieval; }
        }


        public override bool RequiresQuestionAndAnswer
        {
            get { return pRequiresQuestionAndAnswer; }
        }


        public override bool RequiresUniqueEmail
        {
            get { return pRequiresUniqueEmail; }
        }


        public override int MaxInvalidPasswordAttempts
        {
            get { return pMaxInvalidPasswordAttempts; }
        }


        public override int PasswordAttemptWindow
        {
            get { return pPasswordAttemptWindow; }
        }


        public override MembershipPasswordFormat PasswordFormat
        {
            get { return pPasswordFormat; }
        }

        private int pMinRequiredNonAlphanumericCharacters;

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return pMinRequiredNonAlphanumericCharacters; }
        }

        private int pMinRequiredPasswordLength;

        public override int MinRequiredPasswordLength
        {
            get { return pMinRequiredPasswordLength; }
        }

        private string pPasswordStrengthRegularExpression;

        public override string PasswordStrengthRegularExpression
        {
            get { return pPasswordStrengthRegularExpression; }
        }

        //
        // System.Web.Security.MembershipProvider methods.
        //

        //
        // MembershipProvider.ChangePassword
        //

        public override bool ChangePassword(string username, string oldPwd, string newPwd)
        {
            if (!ValidateUser(username, oldPwd))
                return false;

            ValidatePasswordEventArgs args =
              new ValidatePasswordEventArgs(username, newPwd, true);

            OnValidatingPassword(args);

            if (args.Cancel)
                if (args.FailureInformation != null)
                    throw args.FailureInformation;
                else
                    throw new MembershipPasswordException("Change password canceled due to new password validation failure.");

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    Member member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                          && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        member.Password = EncodePassword(newPwd);
                        member.LastPasswordChangedDate = DateTime.Now;
                        context.Entry(member).State = EntityState.Modified;
                        context.SaveChanges();
                        return true;
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ChangePassword");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return false;
        }

        //
        // MembershipProvider.ChangePasswordQuestionAndAnswer
        //

        public override bool ChangePasswordQuestionAndAnswer(string username,
                      string password,
                      string newPwdQuestion,
                      string newPwdAnswer)
        {
            if (!ValidateUser(username, password))
                return false;

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    Member member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                          && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        member.PasswordQuestion = newPwdQuestion;
                        member.PasswordAnswer = newPwdAnswer;
                        context.Entry(member).State = EntityState.Modified;
                        context.SaveChanges();
                        return true;
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ChangePasswordQuestionAndAnswer");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }

            return false;
        }

        //
        // MembershipProvider.CreateUser
        //

        public override MembershipUser CreateUser(string username,
                 string password,
                 string email,
                 string passwordQuestion,
                 string passwordAnswer,
                 bool isApproved,
                 object providerUserKey,
                 out MembershipCreateStatus status)
        {
            ValidatePasswordEventArgs args =
              new ValidatePasswordEventArgs(username, password, true);

            OnValidatingPassword(args);

            if (args.Cancel)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if (RequiresUniqueEmail && GetUserNameByEmail(email) != "")
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }

            MembershipUser u = GetUser(username, false);

            if (u == null)
            {
                DateTime createDate = DateTime.Now;

                if (providerUserKey == null)
                {
                    providerUserKey = Guid.NewGuid();
                }
                else
                {
                    if (!(providerUserKey is Guid))
                    {
                        status = MembershipCreateStatus.InvalidProviderUserKey;
                        return null;
                    }
                }

                Application application = null;

                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    application = context.ApplicationData
                        .Where(a => a.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                }

                User user = new User()
                {
                    UserId = (Guid)providerUserKey,
                    UserName = username,
                    LoweredUserName = username.ToLower(),
                    LastActivityDate = createDate,
                    ApplicationId = application.ApplicationId
                };

                Member member = new Member()
                {
                    Password = EncodePassword(password),
                    Email = email,
                    PasswordQuestion = passwordQuestion,
                    PasswordAnswer = EncodePassword(passwordAnswer),
                    PasswordSalt = CreateSalt(),
                    IsApproved = isApproved,                    
                    Comment = string.Empty,
                    CreateDate = createDate,
                    LastPasswordChangedDate = createDate,
                    IsLockedOut = false,
                    FailedPasswordAttemptCount = 0,
                    FailedPasswordAttemptWindowStart = createDate,
                    FailedPasswordAnswerAttemptCount = 0,
                    FailedPasswordAnswerAttemptWindowStart = createDate,
                    Application = application,
                    LastLockoutDate = SqlDateTime.MinValue.Value,
                    LastLoginDate = SqlDateTime.MinValue.Value,
                    User = user
                };

                try
                {
                    using (Data.MembershipContext context = new Data.MembershipContext())
                    {
                        context.Entry(member).State = EntityState.Added;
                        context.Entry(member.Application).State = EntityState.Unchanged;
                        context.SaveChanges();
                        status = MembershipCreateStatus.Success;
                    }
                }
                catch (Exception e)
                {
                    if (WriteExceptionsToEventLog)
                    {
                        WriteToEventLog(e, "CreateUser");
                    }
                    status = MembershipCreateStatus.ProviderError;
                }
                return GetUser(username, false);
            }
            else
            {
                status = MembershipCreateStatus.DuplicateUserName;
            }
            return null;
        }

        //
        // MembershipProvider.DeleteUser
        //

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    Member member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();

                    if (member != null)
                    {
                        User user = context.UserData.Attach(member.User);
                        
                        //Delete Roles
                        var userRoles = context.UserRoleData
                            .Where(ur => ur.UserId.Equals(user.UserId))
                            .ToList();
                        userRoles.ForEach(ur => context.UserRoleData.Remove(ur));
                        context.SaveChanges();
                        
                        //Delete Profile
                        var userProfiles = context.ProfileData
                            .Where(p => p.UserId.Equals(user.UserId))
                            .ToList();
                        userProfiles.ForEach(p => context.ProfileData.Remove(p));
                        context.SaveChanges();

                        context.Entry(user).State = EntityState.Deleted;
                        context.Entry(member).State = EntityState.Deleted;                                                                     
                        context.SaveChanges();
                        return true;
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "DeleteUser");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return false;
        }

        //
        // MembershipProvider.GetAllUsers
        //

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            int startIndex = pageSize * pageIndex;
            MembershipUserCollection users = new MembershipUserCollection();

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var members = context.MembershipData
                        .Where(m => m.Application.ApplicationName.Equals(ApplicationName))
                        .OrderBy(m => m.User.UserName)
                        .Skip(startIndex)
                        .Take(pageSize);
                    totalRecords = members.Count();
                    foreach (Member member in members)
                    {
                        EfMembershipUser user = new EfMembershipUser(
                            Name,
                            member.User.UserName,
                            member.User.UserId,
                            member.Email,
                            member.PasswordQuestion,
                            member.Comment,
                            member.IsApproved,
                            member.IsLockedOut,
                            member.CreateDate,
                            member.LastLoginDate,
                            member.User.LastActivityDate,
                            member.LastPasswordChangedDate,
                            member.LastLockoutDate);
                        user.Id = member.User.Id;
                        user.FirstName = member.User.FirstName;
                        user.LastName = member.User.LastName;
                        users.Add(user);
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetAllUsers ");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return users;
        }       

        //
        // MembershipProvider.GetNumberOfUsersOnline
        //

        public override int GetNumberOfUsersOnline()
        {
            TimeSpan onlineSpan = new TimeSpan(0, System.Web.Security.Membership.UserIsOnlineTimeWindow, 0);
            DateTime compareTime = DateTime.Now.Subtract(onlineSpan);
            int numOnline = 0;

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    numOnline = context.MembershipData
                        .Where(m => m.User.LastActivityDate > compareTime
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .Count();
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetNumberOfUsersOnline");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }

            return numOnline;
        }

        //
        // MembershipProvider.GetPassword
        //

        public override string GetPassword(string username, string answer)
        {
            string password = string.Empty;
            string passwordAnswer = string.Empty;

            if (!EnablePasswordRetrieval)
            {
                throw new ProviderException("Password Retrieval Not Enabled.");
            }

            if (PasswordFormat == MembershipPasswordFormat.Hashed)
            {
                throw new ProviderException("Cannot retrieve Hashed passwords.");
            }

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username) &&
                            m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();

                    if (member != null)
                    {
                        if (member.IsLockedOut)
                            throw new MembershipPasswordException("The supplied user is locked out.");
                        password = member.Password;
                        passwordAnswer = member.PasswordAnswer;
                    }
                    else
                    {
                        throw new MembershipPasswordException("The supplied user name is not found.");
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetPassword");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }

            if (RequiresQuestionAndAnswer && !CheckPassword(answer, passwordAnswer))
            {
                UpdateFailureCount(username, "passwordAnswer");

                throw new MembershipPasswordException("Incorrect password answer.");
            }


            if (PasswordFormat == MembershipPasswordFormat.Encrypted)
            {
                password = UnEncodePassword(password);
            }

            return password;
        }

        //
        // MembershipProvider.GetUser(string, bool)
        //

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            EfMembershipUser user = null;

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    Member member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();

                    if (member != null)
                    {
                        user = new EfMembershipUser(
                            Name,
                            member.User.UserName,
                            member.User.UserId,
                            member.Email,
                            member.PasswordQuestion,
                            member.Comment,
                            member.IsApproved,
                            member.IsLockedOut,
                            member.CreateDate,
                            member.LastLoginDate,
                            member.User.LastActivityDate,
                            member.LastPasswordChangedDate,
                            member.LastLockoutDate);
                        user.Id = member.User.Id;
                        user.FirstName = member.User.FirstName;
                        user.LastName = member.User.LastName;

                        if (userIsOnline)
                        {
                            member.User.LastActivityDate = DateTime.Now;
                            context.Entry(member).State = EntityState.Modified;
                            context.SaveChanges();
                        }
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetUser(String, Boolean)");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }

            return user;
        }

        //
        // MembershipProvider.GetUser(object, bool)
        //

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            EfMembershipUser user = null;

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    Member member = context.MembershipData
                        .Where(m => m.User.UserId.Equals((Guid)providerUserKey)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();

                    if (member != null)
                    {
                        user = new EfMembershipUser(
                            Name,
                            member.User.UserName,
                            member.User.UserId,
                            member.Email,
                            member.PasswordQuestion,
                            member.Comment,
                            member.IsApproved,
                            member.IsLockedOut,
                            member.CreateDate,
                            member.LastLoginDate,
                            member.User.LastActivityDate,
                            member.LastPasswordChangedDate,
                            member.LastLockoutDate);
                        user.Id = member.User.Id;
                        user.FirstName = member.User.FirstName;
                        user.LastName = member.User.LastName;

                        if (userIsOnline)
                        {
                            member.User.LastActivityDate = DateTime.Now;
                            context.Entry(member).State = EntityState.Modified;
                            context.SaveChanges();
                        }
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetUser(String, Boolean)");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }

            return user;
        }

        //
        // MembershipProvider.UnlockUser
        //

        public override bool UnlockUser(string username)
        {
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        member.IsLockedOut = false;
                        member.LastLockoutDate = DateTime.Now;
                        context.Entry(member).State = EntityState.Modified;
                        context.SaveChanges();
                        return true;
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "UnlockUser");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return false;
        }

        //
        // MembershipProvider.GetUserNameByEmail
        //

        public override string GetUserNameByEmail(string email)
        {
            string username = string.Empty;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.Email.Equals(email) && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        username = member.User.UserName;
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetUserNameByEmail");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }

            return username;
        }

        //
        // MembershipProvider.ResetPassword
        //

        public override string ResetPassword(string username, string answer)
        {
            string passwordAnswer = "";

            if (!EnablePasswordReset)
            {
                throw new NotSupportedException("Password reset is not enabled.");
            }

            if (answer == null && RequiresQuestionAndAnswer)
            {
                UpdateFailureCount(username, "passwordAnswer");

                throw new ProviderException("Password answer required for password reset.");
            }

            string newPassword =
              System.Web.Security.Membership.GeneratePassword(newPasswordLength, MinRequiredNonAlphanumericCharacters);

            ValidatePasswordEventArgs args =
              new ValidatePasswordEventArgs(username, newPassword, true);

            OnValidatingPassword(args);

            if (args.Cancel)
                if (args.FailureInformation != null)
                    throw args.FailureInformation;
                else
                    throw new MembershipPasswordException("Reset password canceled due to password validation failure.");

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        if (member.IsLockedOut)
                            throw new MembershipPasswordException("The supplied user is locked out.");

                        passwordAnswer = member.PasswordAnswer;
                    }
                    else
                    {
                        throw new MembershipPasswordException("The supplied user name is not found.");
                    }

                    if (RequiresQuestionAndAnswer && !CheckPassword(answer, passwordAnswer))
                    {
                        UpdateFailureCount(username, "passwordAnswer");

                        throw new MembershipPasswordException("Incorrect password answer.");
                    }

                    member.Password = EncodePassword(newPassword);
                    member.LastPasswordChangedDate = DateTime.Now;
                    context.Entry(member).State = EntityState.Modified;
                    context.SaveChanges();
                    return newPassword;
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ResetPassword");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
        }

        //
        // MembershipProvider.UpdateUser
        //

        public override void UpdateUser(MembershipUser user)
        {
            EfMembershipUser efUser = (EfMembershipUser)user;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.User.UserId.Equals((Guid)efUser.ProviderUserKey))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        member.Comment = efUser.Comment;
                        member.Email = efUser.Email;
                        member.User.FirstName = efUser.FirstName;
                        member.IsApproved = efUser.IsApproved;
                        member.User.LastName = efUser.LastName;
                        context.SaveChanges();
                    }                    
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "UpdateUser");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
        }
       
        //
        // MembershipProvider.ValidateUser
        //

        public override bool ValidateUser(string username, string password)
        {
            bool isValid = false;

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                            && m.Application.ApplicationName.Equals(pApplicationName)
                            && m.IsLockedOut == false)
                        .FirstOrDefault();
                    if (member != null)
                    {
                        member.LastLoginDate = DateTime.Now;
                        if (member.IsApproved)
                        {
                            if (CheckPassword(password, member.Password))
                            {
                                isValid = true;
                                context.Entry(member).State = EntityState.Modified;
                                context.SaveChanges();
                            }
                            else
                            {
                                UpdateFailureCount(username, "password");
                            }
                        }
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ValidateUser");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return isValid;
        }


        //
        // UpdateFailureCount
        //   A helper method that performs the checks and updates associated with
        // password failure tracking.
        //

        private void UpdateFailureCount(string username, string failureType)
        {
            DateTime windowStart = new DateTime();
            int failureCount = 0;

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        if (failureType == "password")
                        {
                            failureCount = member.FailedPasswordAttemptCount;
                            windowStart = member.FailedPasswordAttemptWindowStart;
                        }

                        if (failureType == "passwordAnswer")
                        {
                            failureCount = member.FailedPasswordAnswerAttemptCount;
                            windowStart = member.FailedPasswordAttemptWindowStart;
                        }

                        DateTime windowEnd = windowStart.AddMinutes(PasswordAttemptWindow);

                        if (failureCount == 0 || DateTime.Now > windowEnd)
                        {
                            // First password failure or outside of PasswordAttemptWindow. 
                            // Start a new password failure count from 1 and a new window starting now.

                            if (failureType == "password")
                            {
                                member.FailedPasswordAttemptCount = 1;
                                member.FailedPasswordAttemptWindowStart = DateTime.Now;
                            }

                            if (failureType == "passwordAnswer")
                            {
                                member.FailedPasswordAnswerAttemptCount = 1;
                                member.FailedPasswordAnswerAttemptWindowStart = DateTime.Now;
                            }
                        }
                        else
                        {
                            if (failureCount++ >= MaxInvalidPasswordAttempts)
                            {
                                // Password attempts have exceeded the failure threshold. Lock out
                                // the user.

                                member.IsLockedOut = true;
                                member.LastLockoutDate = DateTime.Now;
                            }
                            else
                            {
                                if (failureType == "password")
                                    member.FailedPasswordAttemptCount = failureCount;

                                if (failureType == "passwordAnswer")
                                    member.FailedPasswordAnswerAttemptCount = failureCount;
                            }
                        }
                        context.Entry(member).State = EntityState.Modified;
                        context.SaveChanges();
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "UpdateFailureCount");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
        }

        //
        // CheckPassword
        //   Compares password values based on the MembershipPasswordFormat.
        //

        private bool CheckPassword(string password, string dbpassword)
        {
            string pass1 = password;
            string pass2 = dbpassword;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Encrypted:
                    pass2 = UnEncodePassword(dbpassword);
                    break;
                case MembershipPasswordFormat.Hashed:
                    pass1 = EncodePassword(password);
                    break;
                default:
                    break;
            }

            if (pass1 == pass2)
            {
                return true;
            }

            return false;
        }


        //
        // EncodePassword
        //   Encrypts, Hashes, or leaves the password clear based on the PasswordFormat.
        //

        private string EncodePassword(string password)
        {
            string encodedPassword = password;

            if (!string.IsNullOrEmpty(password))
            {
                switch (PasswordFormat)
                {
                    case MembershipPasswordFormat.Clear:
                        break;
                    case MembershipPasswordFormat.Encrypted:
                        encodedPassword =
                          Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password)));
                        break;
                    case MembershipPasswordFormat.Hashed:
                        HMACSHA1 hash = new HMACSHA1();
                        hash.Key = HexToByte(machineKey.ValidationKey);
                        encodedPassword =
                          Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password)));
                        break;
                    default:
                        throw new ProviderException("Unsupported password format.");
                }
            }

            return encodedPassword;
        }


        //
        // UnEncodePassword
        //   Decrypts or leaves the password clear based on the PasswordFormat.
        //

        private string UnEncodePassword(string encodedPassword)
        {
            string password = encodedPassword;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Clear:
                    break;
                case MembershipPasswordFormat.Encrypted:
                    password =
                      Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password)));
                    break;
                case MembershipPasswordFormat.Hashed:
                    throw new ProviderException("Cannot unencode a hashed password.");
                default:
                    throw new ProviderException("Unsupported password format.");
            }

            return password;
        }

        //
        // HexToByte
        //   Converts a hexadecimal string to a byte array. Used to convert encryption
        // key values from the configuration.
        //

        private byte[] HexToByte(string hexString)
        {
            byte[] returnBytes = new byte[hexString.Length / 2];
            for (int i = 0; i < returnBytes.Length; i++)
                returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
            return returnBytes;
        }

        /// <summary>
        /// Creates a random 128 password salt
        /// </summary>
        /// <returns></returns>
        private static string CreateSalt()
        {
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            byte[] buff = new byte[32];
            rng.GetBytes(buff);

            return Convert.ToBase64String(buff);
        }


        //
        // MembershipProvider.FindUsersByName
        //

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            int startIndex = pageSize * pageIndex;
            MembershipUserCollection users = new MembershipUserCollection();
            EfMembershipUser user = null;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var members = context.MembershipData
                        .Where(m => m.User.UserName.StartsWith(usernameToMatch.Replace("%", ""))
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .OrderBy(m => m.User.UserName)
                        .Skip(startIndex)
                        .Take(pageSize);

                    totalRecords = members.Count();

                    foreach(Member m in members.ToList())
                    {
                        user = new EfMembershipUser(
                            Name,
                            m.User.UserName,
                            m.User.UserId,
                            m.Email,
                            m.PasswordQuestion,
                            m.Comment,
                            m.IsApproved,
                            m.IsLockedOut,
                            m.CreateDate,
                            m.LastLoginDate,
                            m.User.LastActivityDate,
                            m.LastPasswordChangedDate,
                            m.LastLockoutDate);
                        user.Id = m.User.Id;
                        user.FirstName = m.User.FirstName;
                        user.LastName = m.User.LastName;
                        users.Add(user);
                    }                    
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "FindUsersByName");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return users;
        }
       
        //
        // MembershipProvider.FindUsersByEmail
        //

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            int startIndex = pageSize * pageIndex;
            MembershipUserCollection users = new MembershipUserCollection();
            EfMembershipUser user = null;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var members = context.MembershipData
                        .Where(m => m.Email.Contains(emailToMatch)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .OrderBy(m => m.User.UserName)
                        .Skip(startIndex)
                        .Take(pageSize);

                    totalRecords = members.Count();

                    foreach (Member m in members.ToList())
                    {
                        user = new EfMembershipUser(
                            Name,
                            m.User.UserName,
                            m.User.UserId,
                            m.Email,
                            m.PasswordQuestion,
                            m.Comment,
                            m.IsApproved,
                            m.IsLockedOut,
                            m.CreateDate,
                            m.LastLoginDate,
                            m.User.LastActivityDate,
                            m.LastPasswordChangedDate,
                            m.LastLockoutDate);
                        user.Id = m.User.Id;
                        user.FirstName = m.User.FirstName;
                        user.LastName = m.User.LastName;
                        users.Add(user);
                    }          
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "FindUsersByEmail");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return users;
        }

        //
        // WriteToEventLog
        //   A helper function that writes exception detail to the event log. Exceptions
        // are written to the event log as a security measure to avoid private database
        // details from being returned to the browser. If a method does not return a status
        // or boolean indicating the action succeeded or failed, a generic exception is also 
        // thrown by the caller.
        //

        private void WriteToEventLog(Exception e, string action)
        {
            string message = action + ": An exception occurred communicating with the data source.\n\n";            
            Log.LogException(message, e, LogCategory.Membership, System.Diagnostics.TraceEventType.Error);
         
        }
    }

    public class EfMembershipUser : MembershipUser
    {
        public int Id {get;set;}
        public string FirstName {get;set;}        
        public string LastName {get;set;}        

        public EfMembershipUser(
            string providername,
            string username,
            object providerUserKey,
            string email,
            string passwordQuestion,
            string comment,
            bool isApproved,
            bool isLockedOut,
            DateTime creationDate,
            DateTime lastLoginDate,
            DateTime lastActivityDate,
            DateTime lastPasswordChangedDate,
            DateTime lastLockedOutDate) :
            base(
                providername,
                username,
                providerUserKey,
                email,
                passwordQuestion,
                comment,
                isApproved,
                isLockedOut,
                creationDate,
                lastLoginDate,
                lastActivityDate,
                lastPasswordChangedDate,
                lastLockedOutDate) { }

        public EfMembershipUser() { }

        public void Delete(EfMembershipUser User)
        {
            User user = GetUserById(User.Id);
            Membership.DeleteUser(user.UserName);
        }

        public void Update(EfMembershipUser User)
        {
            throw new NotImplementedException();
        }

        public static User GetUserById(int Id)
        {
            using (Data.MembershipContext context = new Data.MembershipContext())
            {
                return context.UserData
                    .Where(u => u.Id.Equals(Id))
                    .FirstOrDefault();
            }
        }

        public List<EfMembershipUser> Paged(int maximumRows, int startRowIndex, string search, string filter)
        {
            EfMembershipUser user = new EfMembershipUser();
            List<EfMembershipUser> users = new List<EfMembershipUser>();
            using (Data.MembershipContext context = new Data.MembershipContext())
            {
                List<Member> members = new List<Member>();
                if (!string.IsNullOrEmpty(search))
                {                    
                    members = context.MembershipData
                        .Where(m => m.User.UserName.Contains(search))
                        .OrderBy(m => m.User.UserName)
                        .Skip(startRowIndex)
                        .Take(maximumRows)                        
                        .ToList();                    
                }
                else if (!string.IsNullOrEmpty(filter))
                {
                    members = context.MembershipData
                        .Where(m => m.User.UserName.StartsWith(filter))
                        .OrderBy(m => m.User.UserName)
                        .Skip(startRowIndex)
                        .Take(maximumRows)
                        .ToList();
                }
                else
                {
                    members = context.MembershipData
                       .OrderBy(m => m.User.UserName)
                       .Skip(startRowIndex)
                       .Take(maximumRows)
                       .ToList();
                }
                foreach (Member member in members)
                {
                    user = new EfMembershipUser("EfMembershipProvider",
                        member.User.UserName,
                        member.User.UserId,
                        member.Email,
                        member.PasswordQuestion,
                        member.Comment,
                        member.IsApproved,
                        member.IsLockedOut,
                        member.CreateDate,
                        member.LastLoginDate,
                        member.User.LastActivityDate,
                        member.LastPasswordChangedDate,
                        member.LastLockoutDate);
                    user.Id = member.User.Id;
                    user.FirstName = member.User.FirstName;
                    user.LastName = member.User.LastName; 
                    users.Add(user);
                }                    
            }
            return users;
        }

        public int Count(string search, string filter)
        {
            using (Data.MembershipContext context = new Data.MembershipContext())
            {
                if (!string.IsNullOrEmpty(search))
                {
                    return context.UserData
                        .Where(u => u.UserName.Contains(search))
                        .Count();
                }
                else if (!string.IsNullOrEmpty(filter))
                {
                    return context.UserData
                        .Where(u => u.UserName.StartsWith(filter))
                        .Count();
                }
                else
                {
                    return context.UserData
                        .Count();
                }
            }
        } 
    }
}

I've written a small wrapper for the Enterprise Library database log which is included in the Solution but not seen here.

There is also a Unit Test project with about 30 tests for the custom membership provider.