将库从ObjectContext转换为DbContext

时间:2011-12-07 22:47:49

标签: entity-framework entity-framework-4.1 objectcontext dbcontext

我有一个库(based on code found in an old blog post),它允许我使用Entity Framework非常轻松地围绕我的数据访问包装外观。它使用ObjectContext,并且表现得非常好。

但是现在,我们很兴奋地首先使用DbContext调查代码,当然希望尽可能多地重用/适应我们现有的工作。

当收到以下错误时,我们尝试使用我们的外观,一切都变得可以天真地转换我们的Facade启用库和IObjectContextAdapter:

  

类型'Employee'不能在泛型类型或方法'DbContextManagement.FacadeBase'中用作类型参数'TEntity'。没有从'Employee'到'System.Data.Objects.DataClasses.EntityObject'的隐式引用转换

MSDN说:

  

DbContext API不支持EntityObject派生类型,要使用这些实体类型,必须使用ObjectContext API。

那很好,但是我怎么能继续完成我的重构以绕过这种无能为力呢?

这是一些代码(引入了换行符):

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 个答案:

答案 0 :(得分:3)

如果您的实体派生自EntityObject,并且您要重复使用的代码依赖于基于EntityObject的实体,则它是一个显示停止。在您的实体是POCO(没有EntityObject父级)之前,您不能使用DbContext API。

顺便说一下。您可以使用仅ObjectContext(和POCO)的代码映射,而无需使用DbContext。你只需要:

  • 为描述映射的每个POCO实体/复杂类型创建基于EntityTypeConfigurationComplexTypeConfiguration的类
  • 使用DbModelBuilder收集配置并致电Build以获取DbModel实例
  • 致电Compile个实例DbModel以获取DbCompiledModel
  • 为应用程序的生命周期缓存已编译的模型
  • 当您需要在已编译的模型
  • 上进行新的ObjectContext实例调用CreateObjectContext

请注意,代码映射具有更多有限的功能集,因此不能通过代码映射实现EDMX中当前所有功能。

答案 1 :(得分:2)

对上面给出的链接中的原始代码的作者非常大的点头,这是我最终的结果。它通过基本方案,但我还没有尝试更深入的导航和相关查询。即使存在问题,我认为它将是错误修复而不是显示停止。

到此为止。以下是可重用的,适用于多个上下文,意味着您可以访问数据库并在客户端的两行实际代码中获取一些内容。

DataModel (类库项目)

使用DbContext POCO等的EF Code First项目

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

Facade (班级图书馆计划)

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

因此,使用非常简单,您可以在范围内使用多个上下文和外观。使用这种方法,您编写的唯一代码是自定义查询。不再无休止地更新具有存储库引用和创作copy-cat存储库的UnitOfWorks。我认为它很棒,但请注意这是测试版代码,我确信它有一个需要堵塞的大洞:)

感谢所有人,特别是Ladislav对此以及我提出的许多其他相关问题的耐心和帮助。我希望这段代码很有意思。我在上面的博客上联系了作者,但还没有回复,这些天我觉得他已经进入了NHibernate。

理查德