将库从 ObjectContext 转换为 DbContext

发布于 2024-12-20 03:13:54 字数 5376 浏览 2 评论 0原文

我有一个库(基于旧博客文章中的代码),它允许我使用实体框架非常轻松地围绕我的数据访问包装外观。它使用 ObjectContext 并且对于我的目的来说表现得足够好。

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

一切都很顺利,天真地用 IObjectContextAdapter 转换我们的 Facade 支持库,直到我们尝试使用我们的 Facade,此时收到以下错误:

类型“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; }
    }
}

I have a library (based on code found in an old blog post) that allows me to very easily wrap a façade around my data access using Entity Framework. It uses ObjectContext and has performed well enough for my purposes.

But now, we are excitedly investigating code first using DbContext and of course would like to reuse / adapt as much of our existing effort as possible.

Everything went ok naively converting our Facade enabling library with IObjectContextAdapter until we tried to utilise our facade, when the following error was received:

The type 'Employee' cannot be used as type parameter 'TEntity' in the generic type or method 'DbContextManagement.FacadeBase'. There is no implicit reference conversion from 'Employee' to 'System.Data.Objects.DataClasses.EntityObject'

MSDN says:

EntityObject derived types are not supported by the DbContext API, to use these entity types you must use the ObjectContext API.

That's fine, but how would I then go ahead completing my refactor to bypass this inability?

Here's some code (line breaks introduced):

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

渡你暖光 2024-12-27 03:13:54

如果您的实体派生自 EntityObject,并且您想要重用的代码依赖于基于 EntityObject 的实体,那么它就是一个阻碍。在您的实体是 POCO(无 EntityObject 父级)之前,您无法使用 DbContext API。

顺便提一句。您可以仅使用与 ObjectContext(和 POCO)进行代码映射,而无需使用 DbContext。您只需要:

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

请注意,代码映射的功能集更加有限,因此并不是您当前拥有的所有功能EDMX将可以通过代码映射来实现。

If your entities are derived from EntityObject and the code you want to reuse is dependent on EntityObject based entities then it is a show stopper. You cannot use DbContext API until your entities are POCOs (no EntityObject parent).

Btw. you can use code only mapping with ObjectContext (and POCOs) without ever using DbContext. You just need to:

  • Create EntityTypeConfiguration or ComplexTypeConfiguration based class for each your POCO entity / complex type describing the mapping
  • Use DbModelBuilder to collect your configurations and call Build to get DbModel instance
  • Call Compile on your DbModel instance to get DbCompiledModel
  • Cache compiled model for lifetime of your application
  • When you need a new ObjectContext instance call CreateObjectContext on compiled model

Just be aware that code mapping has much more limited feature set so not everything you currently have in EDMX will be possible to achieve through code mapping.

送君千里 2024-12-27 03:13:54

感谢上面给出的链接中原始代码的作者,这就是我最终得到的结果。它通过了基本场景,但我尚未尝试更深入的导航和相关查询。即使出现问题,我认为这也将是错误修复而不是显示停止。

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

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(控制台应用程序) Project)

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。我认为这很棒,但请注意,这是测试版代码,我确信它在某个地方有一个大洞需要堵塞:)

感谢所有人,特别是拉迪斯拉夫对这个问题以及我提出的许多其他相关问题的耐心和帮助。我希望这段代码引起人们的兴趣。我通过上面的博客联系了作者,但还没有回复,这些天我认为他对 NHibernate 很感兴趣。

理查德

With a very large nod to the author of the original code in the link given above, here is what I ended up with. It passes basic scenarios but I haven't yet tried out deeper navigational and related queries. Even if there is an issue, I reckon it will be bug fix rather than show stop.

Here goes. The following is reusable, works with multiple contexts and means you can go to the database and get something back in 2 lines of actual code in the client.

DataModel (Class Library Project)

Your EF Code First project with DbContext POCOs, etc.

DbContextManagement (Class Library Project)

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 (Class Library Project)

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 (Console Application Project)

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

So, usage is very simple and you can work with multiple contexts and facades within a scope. With this approach, the only code you are writing is custom queries. No more endless updating UnitOfWorks with repository references and authoring copy-cat repositories. I think it's great but be aware this is beta code and I'm sure it has a large hole somewhere that will need plugging :)

Thank you to all and in particular Ladislav for patience and help on this and many other related questions I ask. I hope this code is of interest. I contacted the author at the blog above but no reply yet and these days I think he's into NHibernate.

Richard

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文