Преобразовать библиотеку из ObjectContext в DbContext - PullRequest
2 голосов
/ 08 декабря 2011

У меня есть библиотека ( на основе кода, найденного в старом сообщении в блоге ), которая позволяет мне очень легко обернуть фасад вокруг доступа к данным с помощью Entity Framework. Он использует ObjectContext и работает достаточно хорошо для моих целей.

Но сейчас мы с интересом исследуем код, сначала используя DbContext, и, конечно, хотели бы повторно использовать / адаптировать как можно больше наших существующих усилий.

Все прошло нормально, наивно конвертируя нашу разрешающую библиотеку Facade с IObjectContextAdapter, пока мы не попытались использовать наш фасад, когда была получена следующая ошибка:

Тип «Сотрудник» нельзя использовать в качестве параметра типа «TEntity» в универсальном типе или методе «DbContextManagement.FacadeBase». Не существует неявного преобразования ссылок из «Сотрудника» в «System.Data.Objects.DataClasses.EntityObject»

MSDN говорит:

Производные типы EntityObject не поддерживаются API DbContext, чтобы использовать эти типы сущностей, необходимо использовать API ObjectContext.

Хорошо, но как мне тогда завершить мой рефакторинг, чтобы обойти эту неспособность?

Вот некоторый код (введены разрывы строк):

FacadeBase.cs

namespace DbContextManagement
{
    using System;
    using System.Collections;
    using System.Configuration;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using System.Data.Metadata.Edm;
    using System.Data.Objects.DataClasses;
    using System.Linq;
    using System.Reflection;

    public abstract class FacadeBase<TDbContext, TEntity>
        where TDbContext : DbContext, new()
        where TEntity : EntityObject
    {
        protected TDbContext DbContext
        {
            get
            {
                if (DbContextManager == null)
                {
                    this.InstantiateDbContextManager();
                }

                return DbContextManager.GetDbContext<TDbContext>();
            }
        }

        private DbContextManager DbContextManager { get; set; }

        public virtual void Add(TEntity newObject)
        {
            var context = ((IObjectContextAdapter)this.DbContext).ObjectContext;

            string entitySetName;

            if (newObject.EntityKey != null)
            {
                entitySetName = newObject.EntityKey.EntitySetName;
            }
            else
            {
                string entityTypeName = newObject.GetType().Name;

                var container = context.MetadataWorkspace.GetEntityContainer(
                                    context.DefaultContainerName, 
                                    DataSpace.CSpace);

                entitySetName = (from meta in container.BaseEntitySets
                                    where meta.ElementType.Name == 
                                       entityTypeName
                                    select meta.Name).First();
            }

            context.AddObject(entitySetName, newObject);
        }

        public virtual void Delete(TEntity obsoleteObject)
        {
            var context = ((IObjectContextAdapter)this.DbContext).ObjectContext;

            context.DeleteObject(obsoleteObject);
        }

        private void InstantiateDbContextManager()
        {
            var objectContextManagerConfiguration = 
               ConfigurationManager.GetSection("DbContext") as Hashtable;

            if (objectContextManagerConfiguration != null && 
                objectContextManagerConfiguration.ContainsKey("managerType"))
            {
                var managerTypeName = 
                   objectContextManagerConfiguration["managerType"] as string;

                if (string.IsNullOrEmpty(managerTypeName))
                {
                    throw new ConfigurationErrorsException(
                        "The managerType attribute is empty.");
                }

                managerTypeName = managerTypeName.Trim().ToLower();

                try
                {
                    var frameworkAssembly = 
                         Assembly.GetAssembly(typeof(DbContextManager));

                    var managerType = 
                         frameworkAssembly.GetType(managerTypeName, true, true);

                    this.DbContextManager = 
                        Activator.CreateInstance(managerType) as DbContextManager;
                }
                catch (Exception e)
                {
                    throw new ConfigurationErrorsException(
                        "The managerType specified in the 
                            configuration is not valid.", e);
                }
            }
            else
            {
                throw new ConfigurationErrorsException(
    "A Facade.DbContext tag or its managerType attribute
    is missing in the configuration.");
            }
        }
    }
}

EmployeeFacade.cs

namespace Facade
{
    using System.Collections.Generic;
    using System.Linq;
    using DataModel;
    using DataModel.Entities;
    using DbContextManagement;

    public sealed class EmployeeFacade : FacadeBase<FleetContext, Employee>
    {
        public Employee GetById(int? employeeId)
        {
            return employeeId == null
                ? null
                : this.DbContext.Employees.FirstOrDefault(m => m.Id == employeeId);
        }
    }
}

Employee.cs

namespace DataModel.Entities
{
    public class Employee
    {
        public int Id { get; set; }
        public string Surname { get; set; }
        public string Forename { get; set; }
        public string EmployeeNumber { get; set; }
    }
}

Ответы [ 2 ]

3 голосов
/ 08 декабря 2011

Если ваши сущности получены из EntityObject, а код, который вы хотите использовать повторно, зависит от сущностей, основанных на EntityObject, то это ограничитель показа.Вы не можете использовать API DbContext, пока ваши сущности не станут POCO (без EntityObject родительского элемента).

Кстати.Вы можете использовать сопоставление только кода с ObjectContext (и POCO), даже не используя DbContext.Вам просто нужно:

  • Создать класс EntityTypeConfiguration или ComplexTypeConfiguration для каждого вашего объекта POCO / сложного типа, описывающего отображение
  • Используйте DbModelBuilder для сбора ваших конфигураций ивызов Build для получения DbModel экземпляра
  • вызов Compile для вашего DbModel экземпляра для получения DbCompiledModel
  • кэшированной скомпилированной модели для времени жизни вашего приложения
  • Когда вам нужен новый ObjectContext вызов экземпляра CreateObjectContext для скомпилированной модели

Просто имейте в виду, что для сопоставления кода установлен гораздо более ограниченный набор функций, поэтому не все, что у вас есть в EDMX, будетдобиться с помощью сопоставления кода.

2 голосов
/ 13 января 2012

С очень большим поклоном автору оригинального кода по ссылке, приведенной выше, вот что я закончил. Он проходит основные сценарии, но я еще не пробовал более глубокие навигационные и связанные запросы. Даже если есть проблема, я считаю, что это будет исправление ошибки, а не остановка шоу.

Здесь идет. Следующее можно использовать повторно, работает с несколькими контекстами и означает, что вы можете перейти к базе данных и получить что-то обратно в 2 строки реального кода на клиенте.

DataModel (проект библиотеки классов)

Ваш проект EF Code First с DOCContext POCO и т. Д.

DbContextManagement (проект библиотеки классов)

DbContextManager.cs

namespace DbContextManagement
{
    using System.Data.Entity;

    /// <summary>
    /// Abstract base class for all other DbContextManager classes.
    /// </summary>
    public abstract class DbContextManager
    {
        /// <summary>
        /// Returns a reference to an DbContext instance.
        /// </summary>
        /// <typeparam name="TDbContext">The type of the db context.</typeparam>
        /// <returns>The current DbContext</returns>
        public abstract TDbContext GetDbContext<TDbContext>() 
            where TDbContext : DbContext, new();
    }
}

DbContextScope.cs

namespace DbContextManagement
{
    using System;
    using System.Collections.Generic;
    using System.Data.Common;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using System.Linq;
    using System.Threading;

    /// <summary>
    /// Defines a scope wherein only one DbContext instance is created, and shared by all of those who use it. 
    /// </summary>
    /// <remarks>Instances of this class are supposed to be used in a using() statement.</remarks>
    public class DbContextScope : IDisposable
    {
        /// <summary>
        /// List of current DbContexts (supports multiple contexts).
        /// </summary>
        private readonly List<DbContext> contextList;

        /// <summary>
        /// DbContext scope definitiion.
        /// </summary>
        [ThreadStatic]
        private static DbContextScope currentScope;

        /// <summary>
        /// Holds a value indicating whether the context is disposed or not.
        /// </summary>
        private bool isDisposed;

        /// <summary>
        /// Initializes a new instance of the <see cref="DbContextScope"/> class.
        /// </summary>
        /// <param name="saveAllChangesAtEndOfScope">if set to <c>true</c> [save all changes at end of scope].</param>
        protected DbContextScope(bool saveAllChangesAtEndOfScope)
        {
            if (currentScope != null && !currentScope.isDisposed)
            {
                throw new InvalidOperationException("DbContextScope instances cannot be nested.");
            }

            this.SaveAllChangesAtEndOfScope = saveAllChangesAtEndOfScope;

            this.contextList = new List<DbContext>();

            this.isDisposed = false;

            Thread.BeginThreadAffinity();

            currentScope = this;
        }

        /// <summary>
        /// Gets or sets a value indicating whether to automatically save all object changes at end of the scope.
        /// </summary>
        /// <value><c>true</c> if [save all changes at end of scope]; otherwise, <c>false</c>.</value>
        private bool SaveAllChangesAtEndOfScope { get; set; }

        /// <summary>
        /// Save all object changes to the underlying datastore.
        /// </summary>
        public void SaveAllChanges()
        {
            var transactions = new List<DbTransaction>();

            foreach (var context in this.contextList
                .Select(dbcontext => ((IObjectContextAdapter)dbcontext)
                    .ObjectContext))
            {
                context.Connection.Open();

                var databaseTransaction = context.Connection.BeginTransaction();

                transactions.Add(databaseTransaction);

                try
                {
                    context.SaveChanges();
                }
                catch
                {
                    /* Rollback & dispose all transactions: */
                    foreach (var transaction in transactions)
                    {
                        try
                        {
                            transaction.Rollback();
                        }
                        catch
                        {
                            // "Empty general catch clause suppresses any errors."
                            // Haven't quite figured out what to do here yet.
                        }
                        finally
                        {
                            databaseTransaction.Dispose();
                        }
                    }

                    transactions.Clear();

                    throw;
                }
            }

            try
            {
                /* Commit all complete transactions: */
                foreach (var completeTransaction in transactions)
                {
                    completeTransaction.Commit();
                }
            }
            finally
            {
                /* Dispose all transactions: */
                foreach (var transaction in transactions)
                {
                    transaction.Dispose();
                }

                transactions.Clear();

                /* Close all open connections: */
                foreach (var context in this.contextList
                    .Select(dbcontext => ((IObjectContextAdapter)dbcontext).ObjectContext)
                    .Where(context => context.Connection.State != System.Data.ConnectionState.Closed))
                {
                    context.Connection.Close();
                }
            }
        }

        /// <summary>
        /// Disposes the DbContext.
        /// </summary>
        public void Dispose()
        {
            // Monitor for possible future bugfix.
            // CA1063 : Microsoft.Design : Provide an overridable implementation of Dispose(bool) 
            // on 'DbContextScope' or mark the type as sealed. A call to Dispose(false) should 
            // only clean up native resources. A call to Dispose(true) should clean up both managed 
            // and native resources.
            if (this.isDisposed)
            {
                return;
            }

            // Monitor for possible future bugfix.
            // CA1063 : Microsoft.Design : Modify 'DbContextScope.Dispose()' so that it calls 
            // Dispose(true), then calls GC.SuppressFinalize on the current object instance 
            // ('this' or 'Me' in Visual Basic), and then returns.
            currentScope = null;

            Thread.EndThreadAffinity();

            try
            {
                if (this.SaveAllChangesAtEndOfScope && this.contextList.Count > 0)
                {
                    this.SaveAllChanges();
                }
            }
            finally
            {
                foreach (var context in this.contextList)
                {
                    try
                    {
                        context.Dispose();
                    }
                    catch (ObjectDisposedException)
                    {
                        // Monitor for possible future bugfix.
                        // CA2202 : Microsoft.Usage : Object 'databaseTransaction' can be disposed 
                        // more than once in method 'DbContextScope.SaveAllChanges()'. 
                        // To avoid generating a System.ObjectDisposedException you should not call 
                        // Dispose more than one time on an object.
                    }
                }

                this.isDisposed = true;
            }
        }

        /// <summary>
        /// Returns a reference to a DbContext of a specific type that is - or will be -
        /// created for the current scope. If no scope currently exists, null is returned.
        /// </summary>
        /// <typeparam name="TDbContext">The type of the db context.</typeparam>
        /// <returns>The current DbContext</returns>
        protected internal static TDbContext GetCurrentDbContext<TDbContext>()
            where TDbContext : DbContext, new()
        {
            if (currentScope == null)
            {
                return null;
            }

            var contextOfType = currentScope.contextList
                .OfType<TDbContext>()
                .FirstOrDefault();

            if (contextOfType == null)
            {
                contextOfType = new TDbContext();

                currentScope.contextList.Add(contextOfType);
            }

            return contextOfType;
        }
    }
}

FacadeBase.cs

namespace DbContextManagement
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Data.Entity;
    using System.Reflection;

    /// <summary>
    /// Generic base class for all other Facade classes.
    /// </summary>
    /// <typeparam name="TDbContext">The type of the db context.</typeparam>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <typeparam name="TEntityKey">The type of the entity key.</typeparam>
    /// <remarks>Not sure the handling of TEntityKey is something I've worked with properly.</remarks>
    public abstract class FacadeBase<TDbContext, TEntity, TEntityKey>
        where TDbContext : DbContext, new()
        where TEntity : class
    {
        /// <summary>
        /// Gets the db context.
        /// </summary>
        public TDbContext DbContext
        {
            get
            {
                if (DbContextManager == null)
                {
                    this.InstantiateDbContextManager();
                }

                return DbContextManager != null 
                    ? DbContextManager.GetDbContext<TDbContext>() 
                    : null;
            }
        }

        /// <summary>
        /// Gets or sets the DbContextManager.
        /// </summary>
        /// <value>The DbContextManager.</value>
        private DbContextManager DbContextManager { get; set; }

        /// <summary>
        /// Adds a new entity object to the context.
        /// </summary>
        /// <param name="newObject">A new object.</param>
        public virtual void Add(TEntity newObject)
        {
            this.DbContext.Set<TEntity>().Add(newObject);
        }

        /// <summary>
        /// Deletes an entity object.
        /// </summary>
        /// <param name="obsoleteObject">An obsolete object.</param>
        public virtual void Delete(TEntity obsoleteObject)
        {
            this.DbContext.Set<TEntity>().Remove(obsoleteObject);
        }

        /// <summary>
        /// Gets all entities for the given type.
        /// </summary>
        /// <returns>DbContext Set of TEntity.</returns>
        public virtual IEnumerable<TEntity> GetAll()
        {
            return this.DbContext.Set<TEntity>();
        }

        /// <summary>
        /// Gets the entity by the specified entity key.
        /// </summary>
        /// <param name="entityKey">The entity key.</param>
        /// <returns>Entity matching specified entity key or null if not found.</returns>
        public virtual TEntity GetByKey(TEntityKey entityKey)
        {
            return this.DbContext.Set<TEntity>().Find(entityKey);
        }

        /// <summary>
        /// Deletes the entity for the specified entity key.
        /// </summary>
        /// <param name="entityKey">The entity key.</param>
        public virtual void DeleteByKey(TEntityKey entityKey)
        {
            var entity = this.DbContext.Set<TEntity>().Find(entityKey);

            if (entity != null)
            {
                this.DbContext.Set<TEntity>().Remove(entity);
            }
        }

        /// <summary>
        /// Instantiates a new DbContextManager based on application configuration settings.
        /// </summary>
        private void InstantiateDbContextManager()
        {
            /* Retrieve DbContextManager configuration settings: */
            var contextManagerConfiguration = ConfigurationManager.GetSection("DbContext") as Hashtable;

            if (contextManagerConfiguration == null)
            {
                throw new ConfigurationErrorsException("A Facade.DbContext tag or its managerType attribute is missing in the configuration.");
            }

            if (!contextManagerConfiguration.ContainsKey("managerType"))
            {
                throw new ConfigurationErrorsException("dbManagerConfiguration does not contain key 'managerType'.");
            }

            var managerTypeName = contextManagerConfiguration["managerType"] as string;

            if (string.IsNullOrEmpty(managerTypeName))
            {
                throw new ConfigurationErrorsException("The managerType attribute is empty.");
            }

            managerTypeName = managerTypeName.Trim().ToUpperInvariant();

            try
            {
                /* Try to create a type based on it's name: */
                var frameworkAssembly = Assembly.GetAssembly(typeof(DbContextManager));

                var managerType = frameworkAssembly.GetType(managerTypeName, true, true);

                /* Try to create a new instance of the specified DbContextManager type: */
                this.DbContextManager = Activator.CreateInstance(managerType) as DbContextManager;
            }
            catch (Exception e)
            {
                throw new ConfigurationErrorsException("The managerType specified in the configuration is not valid.", e);
            }
        }
    }
}

ScopedDbContextManager.cs

namespace DbContextManagement
{
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;

    /// <summary>
    /// Manages multiple db contexts.
    /// </summary>
    public sealed class ScopedDbContextManager : DbContextManager
    {
        /// <summary>
        /// List of Object Contexts.
        /// </summary>
        private List<DbContext> contextList;

        /// <summary>
        /// Returns the DbContext instance that belongs to the current DbContextScope.
        /// If currently no DbContextScope exists, a local instance of an DbContext
        /// class is returned.
        /// </summary>
        /// <typeparam name="TDbContext">The type of the db context.</typeparam>
        /// <returns>Current scoped DbContext.</returns>
        public override TDbContext GetDbContext<TDbContext>()
        {
            var currentDbContext = DbContextScope.GetCurrentDbContext<TDbContext>();

            if (currentDbContext != null)
            {
                return currentDbContext;
            }

            if (this.contextList == null)
            {
                this.contextList = new List<DbContext>();
            }

            currentDbContext = this.contextList.OfType<TDbContext>().FirstOrDefault();

            if (currentDbContext == null)
            {
                currentDbContext = new TDbContext();

                this.contextList.Add(currentDbContext);
            }

            return currentDbContext;
        }
    }
}

UnitOfWorkScope.cs

namespace DbContextManagement
{
    /// <summary>
    /// Defines a scope for a business transaction. At the end of the scope all object changes can be persisted to the underlying datastore. 
    /// </summary>
    /// <remarks>Instances of this class are supposed to be used in a using() statement.</remarks>
    public sealed class UnitOfWorkScope : DbContextScope
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="UnitOfWorkScope"/> class.
        /// </summary>
        /// <param name="saveAllChangesAtEndOfScope">if set to <c>true</c> [save all changes at end of scope].</param>
        public UnitOfWorkScope(bool saveAllChangesAtEndOfScope)
            : base(saveAllChangesAtEndOfScope)
        {
        }
    }
}

Фасад (Проект библиотеки классов)

YourEntityFacade.cs

namespace Facade
{
    using System.Collections.Generic;
    using System.Linq;
    using DataModel;
    using DataModel.Entities;
    using DbContextManagement;

    public class YourEntityFacade : FacadeBase<YourDbContext, YourEntity, int>
    {
        public override IEnumerable<YourEntity> GetAll()
        {
            return base.GetAll()
                .Distinct()
                .ToList();
        }
    }
}

TestConsole (проект консольного приложения)

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="DbContext" type="System.Configuration.SingleTagSectionHandler" />
    </configSections>
    <DbContext managerType="DbContextManagement.ScopedDbContextManager" />
    <connectionStrings>
        <add 
            name="YourDbContext" 
            providerName="System.Data.SqlClient" 
            connectionString="Your connection string" />
    </connectionStrings>
</configuration>

Program.cs

namespace TestConsole
{
    using System;
    using System.Collections.Generic;
    using DataModel.Entities;
    using DbContextManagement;
    using Facade;

    public static class Program
    {
        public static void Main()
        {
            TestGetAll();

            Console.ReadLine();
        }

        private static void TestGetAll()
        {
            Console.WriteLine();
            Console.WriteLine("Test GetAll()");
            Console.WriteLine();

            IEnumerable<YourEntity> yourEntities;

            using (new UnitOfWorkScope(false))
            {
                yourEntities= new YourEntityFacade().GetAll();
            }

            if (yourEntities != null)
            {
                foreach (var yourEntity in yourEntities)
                {
                    Console.WriteLine(
                       string.Format("{0}, {1}", 
                       yourEntity.Id, 
                       yourEntity.Name));
                }
            }
            else
            {
                Console.WriteLine("GetAll() NULL");
            }
        }
    }
}

Итак, использование очень простое, и вы можете работать с несколькими контекстами и фасадами в пределах области действия. При таком подходе единственный код, который вы пишете, - это пользовательские запросы. Больше не нужно бесконечно обновлять UnitOfWorks ссылками на репозитории и создавать репозитории copy-cat. Я думаю, это здорово, но имейте в виду, что это бета-код, и я уверен, что где-то есть большая дыра, которую нужно будет подключить:)

Спасибо всем и, в частности, Ладиславу за терпение и помощь по этому и многим другим смежным вопросам, которые я задаю. Я надеюсь, что этот код представляет интерес. Я связался с автором в блоге выше, но ответа пока нет, и в эти дни я думаю, что он в NHibernate.

Richard

...