实体框架 - 存储库检查(大文本)

Jul*_*ian 3 .net c# asp.net entity-framework repository

我正在使用Entity框架在C#/ ASP.NET中创建一个完整的存储库,但目前我担心我会忽略像处理ObjectContexts这样的东西.在下面的代码行中,您将看到我的完整存储库(至少您需要了解我的问题),我希望有人能够仔细查看并告诉我是否犯了一些错误.

这个项目对我来说非常重要,但我是存储库/ EF模型的新手.

Global.asax中

public class Global : System.Web.HttpApplication
{
    private WebObjectContextStorage _storage;

    public override void Init()
    {
        base.Init();
        _storage = new WebObjectContextStorage(this);
    }

    protected void Application_Start(object sender, EventArgs e)
    {

    }

    protected void Session_Start(object sender, EventArgs e)
    {

    }

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        ObjectContextInitializer.Instance().InitializeObjectContextOnce(() =>
        {
            ObjectContextManager.InitStorage(_storage);
        });
    }

    protected void Application_EndRequest(object sender, EventArgs e)
    {

    }

    protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {

    }

    protected void Application_Error(object sender, EventArgs e)
    {

    }

    protected void Session_End(object sender, EventArgs e)
    {

    }

    protected void Application_End(object sender, EventArgs e)
    {

    }
}
Run Code Online (Sandbox Code Playgroud)

ObjectContextManager

public static class ObjectContextManager
{
    public static void InitStorage(IObjectContextStorage storage)
    {
        if (storage == null) 
        {
            throw new ArgumentNullException("storage");
        }
        if ((Storage != null) && (Storage != storage))
        {
            throw new ApplicationException("A storage mechanism has already been configured for this application");
        }            
        Storage = storage;
    }

    /// <summary>
    /// The default connection string name used if only one database is being communicated with.
    /// </summary>
    public static readonly string DefaultConnectionStringName = "TraceConnection";        

    /// <summary>
    /// Used to get the current object context session if you're communicating with a single database.
    /// When communicating with multiple databases, invoke <see cref="CurrentFor()" /> instead.
    /// </summary>
    public static ObjectContext Current
    {
        get
        {
            return CurrentFor(DefaultConnectionStringName);
        }
    }

    /// <summary>
    /// Used to get the current ObjectContext associated with a key; i.e., the key 
    /// associated with an object context for a specific database.
    /// 
    /// If you're only communicating with one database, you should call <see cref="Current" /> instead,
    /// although you're certainly welcome to call this if you have the key available.
    /// </summary>
    public static ObjectContext CurrentFor(string key)
    {
        if (string.IsNullOrEmpty(key))
        {
            throw new ArgumentNullException("key");
        }

        if (Storage == null)
        {
            throw new ApplicationException("An IObjectContextStorage has not been initialized");
        }

        ObjectContext context = null;
        lock (_syncLock)
        {
            context = Storage.GetObjectContextForKey(key);

            if (context == null)
            {
                context = ObjectContextFactory.GetTraceContext(key);
                Storage.SetObjectContextForKey(key, context);
            }
        }

        return context;
    }

    /// <summary>
    /// This method is used by application-specific object context storage implementations
    /// and unit tests. Its job is to walk thru existing cached object context(s) and Close() each one.
    /// </summary>
    public static void CloseAllObjectContexts()
    {
        foreach (ObjectContext ctx in Storage.GetAllObjectContexts())
        {
            if (ctx.Connection.State == System.Data.ConnectionState.Open)
                ctx.Connection.Close();
        }
    }      

    /// <summary>
    /// An application-specific implementation of IObjectContextStorage must be setup either thru
    /// <see cref="InitStorage" /> or one of the <see cref="Init" /> overloads. 
    /// </summary>
    private static IObjectContextStorage Storage { get; set; }

    private static object _syncLock = new object();
}
Run Code Online (Sandbox Code Playgroud)

ObjectContextInitializer

public class ObjectContextInitializer
{
    private static readonly object syncLock = new object();
    private static ObjectContextInitializer instance;

    protected ObjectContextInitializer() { }

    private bool isInitialized = false;

    public static ObjectContextInitializer Instance()
    {
        if (instance == null)
        {
            lock (syncLock)
            {
                if (instance == null)
                {
                    instance = new ObjectContextInitializer();
                }
            }
        }

        return instance;
    }

    /// <summary>
    /// This is the method which should be given the call to intialize the ObjectContext; e.g.,
    /// ObjectContextInitializer.Instance().InitializeObjectContextOnce(() => InitializeObjectContext());
    /// where InitializeObjectContext() is a method which calls ObjectContextManager.Init()
    /// </summary>
    /// <param name="initMethod"></param>
    public void InitializeObjectContextOnce(Action initMethod)
    {
        lock (syncLock)
        {
            if (!isInitialized)
            {
                initMethod();
                isInitialized = true;
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

ObjectContextFactory

public static class ObjectContextFactory
{
    /// <summary>
    /// Gets the TraceContext
    /// </summary>
    /// <param name="connectionString">Connection string to use for database queries</param>
    /// <returns>The TraceContext</returns>
    public static TraceContext GetTraceContext(string configName)
    {
        string connectionString = ConfigurationManager.ConnectionStrings[configName].ConnectionString;
        return new TraceContext(connectionString);
    }
}
Run Code Online (Sandbox Code Playgroud)

WebObjectContextStorage

public class WebObjectContextStorage : IObjectContextStorage
{   
    public WebObjectContextStorage(HttpApplication app)
    { 
        app.EndRequest += (sender, args) =>
                              {
                                  ObjectContextManager.CloseAllObjectContexts();
                                  HttpContext.Current.Items.Remove(HttpContextObjectContextStorageKey);
                              };
    }        

    public ObjectContext GetObjectContextForKey(string key)
    {
        ObjectContextStorage storage = GetObjectContextStorage();
        return storage.GetObjectContextForKey(key);
    }

    public void SetObjectContextForKey(string factoryKey, ObjectContext session)
    {
        ObjectContextStorage storage = GetObjectContextStorage();
        storage.SetObjectContextForKey(factoryKey, session);
    }

    public IEnumerable<ObjectContext> GetAllObjectContexts()
    {
        ObjectContextStorage storage = GetObjectContextStorage();
        return storage.GetAllObjectContexts();
    }

    private ObjectContextStorage GetObjectContextStorage()
    {
        HttpContext context = HttpContext.Current;
        ObjectContextStorage storage = context.Items[HttpContextObjectContextStorageKey] as ObjectContextStorage;
        if (storage == null)
        {
            storage = new ObjectContextStorage();
            context.Items[HttpContextObjectContextStorageKey] = storage;
        }
        return storage;
    }       

    private static readonly string HttpContextObjectContextStorageKey = "HttpContextObjectContextStorageKey";       
}
Run Code Online (Sandbox Code Playgroud)

ObjectContextStorage

public class ObjectContextStorage : IObjectContextStorage
{
    private Dictionary<string, ObjectContext> storage = new Dictionary<string, ObjectContext>();

    /// <summary>
    /// Initializes a new instance of the <see cref="SimpleObjectContextStorage"/> class.
    /// </summary>
    public ObjectContextStorage() { }

    /// <summary>
    /// Returns the object context associated with the specified key or
    /// null if the specified key is not found.
    /// </summary>
    /// <param name="key">The key.</param>
    /// <returns></returns>
    public ObjectContext GetObjectContextForKey(string key)
    {
        ObjectContext context;
        if (!this.storage.TryGetValue(key, out context))
            return null;
        return context;
    }


    /// <summary>
    /// Stores the object context into a dictionary using the specified key.
    /// If an object context already exists by the specified key, 
    /// it gets overwritten by the new object context passed in.
    /// </summary>
    /// <param name="key">The key.</param>
    /// <param name="objectContext">The object context.</param>
    public void SetObjectContextForKey(string key, ObjectContext objectContext)
    {           
        this.storage.Add(key, objectContext);           
    }

    /// <summary>
    /// Returns all the values of the internal dictionary of object contexts.
    /// </summary>
    /// <returns></returns>
    public IEnumerable<ObjectContext> GetAllObjectContexts()
    {
        return this.storage.Values;
    }
}
Run Code Online (Sandbox Code Playgroud)

GenericRepository

public class GenericRepository : IRepository
{
    private readonly string _connectionStringName;
    private ObjectContext _objectContext;
    private readonly PluralizationService _pluralizer = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en"));
    private bool _usePlurazation;

    /// <summary>
    /// Initializes a new instance of the <see cref="GenericRepository&lt;TEntity&gt;"/> class.
    /// </summary>
    public GenericRepository()
        : this(string.Empty, false)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="GenericRepository&lt;TEntity&gt;"/> class.
    /// </summary>
    /// <param name="connectionStringName">Name of the connection string.</param>
    public GenericRepository(string connectionStringName, bool usePlurazation)
    {
        this._connectionStringName = connectionStringName;
        this._usePlurazation = usePlurazation;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="GenericRepository"/> class.
    /// </summary>
    /// <param name="objectContext">The object context.</param>
    public GenericRepository(ObjectContext objectContext, bool usePlurazation)
    {
        if (objectContext == null)
            throw new ArgumentNullException("objectContext");
        this._objectContext = objectContext;
        this._usePlurazation = usePlurazation;
    }

    public TEntity GetByKey<TEntity>(object keyValue) where TEntity : class
    {
        EntityKey key = GetEntityKey<TEntity>(keyValue);

        object originalItem;
        if (ObjectContext.TryGetObjectByKey(key, out originalItem))
        {
            return (TEntity)originalItem;
        }
        return default(TEntity);
    }

    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
    {
        var entityName = GetEntityName<TEntity>();
        return ObjectContext.CreateQuery<TEntity>(entityName).OfType<TEntity>();
    }

    public IQueryable<TEntity> GetQuery<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return GetQuery<TEntity>().Where(predicate);
    }

    public IQueryable<TEntity> GetQuery<TEntity>(ISpecification<TEntity> specification) where TEntity : class
    {
        return specification.SatisfyingEntitiesFrom(GetQuery<TEntity>());
    }

    public IEnumerable<TEntity> Get<TEntity>(Expression<Func<TEntity, string>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
    {
        if (sortOrder == SortOrder.Ascending)
        {
            return GetQuery<TEntity>().OrderBy(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
        }
        return GetQuery<TEntity>().OrderByDescending(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
    }

    public IEnumerable<TEntity> Get<TEntity>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, string>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
    {
        if (sortOrder == SortOrder.Ascending)
        {
            return GetQuery<TEntity>().Where(predicate).OrderBy(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
        }
        return GetQuery<TEntity>().Where(predicate).OrderByDescending(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
    }

    public IEnumerable<TEntity> Get<TEntity>(ISpecification<TEntity> specification, Expression<Func<TEntity, string>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
    {
        if (sortOrder == SortOrder.Ascending)
        {
            return specification.SatisfyingEntitiesFrom(GetQuery<TEntity>()).OrderBy(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
        }
        return specification.SatisfyingEntitiesFrom(GetQuery<TEntity>()).OrderByDescending(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
    }

    public TEntity Single<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
    {
        return GetQuery<TEntity>().SingleOrDefault<TEntity>(criteria);
    }

    public TEntity Single<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
    {
        return criteria.SatisfyingEntityFrom(GetQuery<TEntity>());
    }

    public TEntity First<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return GetQuery<TEntity>().FirstOrDefault(predicate);
    }

    public TEntity First<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
    {
        return criteria.SatisfyingEntitiesFrom(GetQuery<TEntity>()).FirstOrDefault();
    }

    public void Add<TEntity>(TEntity entity) where TEntity : class
    {
        if (entity == null)
        {
            throw new ArgumentNullException("entity");
        }
        ObjectContext.AddObject(GetEntityName<TEntity>(), entity);
    }

    public void Attach<TEntity>(TEntity entity) where TEntity : class
    {
        if (entity == null)
        {
            throw new ArgumentNullException("entity");
        }

        ObjectContext.AttachTo(GetEntityName<TEntity>(), entity);
    }

    public void Delete<TEntity>(TEntity entity) where TEntity : class
    {
        if (entity == null)
        {
            throw new ArgumentNullException("entity");
        }
        ObjectContext.DeleteObject(entity);
    }

    public void Delete<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
    {
        IEnumerable<TEntity> records = Find<TEntity>(criteria);

        foreach (TEntity record in records)
        {
            Delete<TEntity>(record);
        }
    }

    public void Delete<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
    {
        IEnumerable<TEntity> records = Find<TEntity>(criteria);
        foreach (TEntity record in records)
        {
            Delete<TEntity>(record);
        }
    }

    public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class
    {
        return GetQuery<TEntity>().AsEnumerable();
    }

    public void Update<TEntity>(TEntity entity) where TEntity : class
    {
        var fqen = GetEntityName<TEntity>();

        object originalItem;
        EntityKey key = ObjectContext.CreateEntityKey(fqen, entity);
        if (ObjectContext.TryGetObjectByKey(key, out originalItem))
        {
            ObjectContext.ApplyCurrentValues(key.EntitySetName, entity);
        }
    }

    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
    {
        return GetQuery<TEntity>().Where(criteria);
    }

    public TEntity FindOne<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
    {
        return GetQuery<TEntity>().Where(criteria).FirstOrDefault();
    }

    public TEntity FindOne<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
    {
        return criteria.SatisfyingEntityFrom(GetQuery<TEntity>());
    }

    public IEnumerable<TEntity> Find<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
    {
        return criteria.SatisfyingEntitiesFrom(GetQuery<TEntity>());
    }

    public int Count<TEntity>() where TEntity : class
    {
        return GetQuery<TEntity>().Count();
    }

    public int Count<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
    {
        return GetQuery<TEntity>().Count(criteria);
    }

    public int Count<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
    {
        return criteria.SatisfyingEntitiesFrom(GetQuery<TEntity>()).Count();
    }

    public IUnitOfWork UnitOfWork
    {
        get
        {
            if (unitOfWork == null)
            {
                unitOfWork = new UnitOfWork(this.ObjectContext);
            }
            return unitOfWork;
        }
    }

    private ObjectContext ObjectContext
    {
        get
        {
            if (this._objectContext == null)
            {
                if (string.IsNullOrEmpty(this._connectionStringName))
                {
                    this._objectContext = ObjectContextManager.Current;
                }
                else
                {
                    this._objectContext = ObjectContextManager.CurrentFor(this._connectionStringName);
                }
            }
            return this._objectContext;
        }
    }

    private EntityKey GetEntityKey<TEntity>(object keyValue) where TEntity : class
    {
        var entitySetName = GetEntityName<TEntity>();
        var objectSet = ObjectContext.CreateObjectSet<TEntity>();
        var keyPropertyName = objectSet.EntitySet.ElementType.KeyMembers[0].ToString();
        var entityKey = new EntityKey(entitySetName, new[] { new EntityKeyMember(keyPropertyName, keyValue) });
        return entityKey;
    }

    private string GetEntityName<TEntity>() where TEntity : class
    {
        // WARNING! : Exceptions for inheritance


        if (_usePlurazation)
        {
             return string.Format("{0}.{1}", ObjectContext.DefaultContainerName, _pluralizer.Pluralize(typeof(TEntity).Name));

        }
        else
        {
             return string.Format("{0}.{1}", ObjectContext.DefaultContainerName, typeof(TEntity).Name);

        }
    }

    private IUnitOfWork unitOfWork;
}
Run Code Online (Sandbox Code Playgroud)

我知道这需要一些时间来阅读代码,但它会帮助我实现里程,并提供有关如何做得更好或者我不处理对象的提示.

我还有一个小问题:"我想在这个存储库上面放一个业务层,这样可以保持像global.asax一样的东西我想但是需要静态类(对吧?)就像BookProvider那样给了我所有的数据关于我的书籍实体?

提前致谢!

Sla*_*uma 5

我能给出的唯一具体的评论是关于处理上下文:

foreach (ObjectContext ctx in Storage.GetAllObjectContexts())
{
    if (ctx.Connection.State == System.Data.ConnectionState.Open)
        ctx.Connection.Close();
}
Run Code Online (Sandbox Code Playgroud)

ObjectContext实现IDisposable,所以标准方式将在我看来:

foreach (ObjectContext ctx in Storage.GetAllObjectContexts())
    ctx.Dispose();
Run Code Online (Sandbox Code Playgroud)

据我所知,ObjectContext.Dispose()只是关闭连接,所以它与你正在做的一样.但我认为这是一个内部实现细节,可能会在EF版本之间发生变化.

您的通用存储库是一个,因为有很多此类存储库.在查看方法时,我想到了几点:

  • 既然你是暴露IQueryablepublic IQueryable<TEntity> GetQuery<TEntity>(...)为什么你最需要的其他方法,如Single,First,Count,等?(为什么不Any呢?等等?)你可以从你那里得到这一切IQueryable.

  • 您的Update方法仅适用于标量属性.但这是通用存储库的常见问题.对于以通用方式更新实体,没有简单的解决方案或根本没有解决方案.

  • 根据存储库模式,您希望达到的目标是什么?如果您在内存数据存储中具有单元可测试性,则无法公开,IQueryable因为LINQ to Entities和LINQ to Objects不同.要测试您的IQueryable是否有效,您需要集成测试和应用程序应该在生产中使用的真实数据库.但是,如果您不公开IQueryable您的存储库,则需要许多特定于业务的方法,这些方法将结果作为POCO,POCO集合或投影/选定属性的DTO返回,并隐藏内部查询规范,以便您可以使用内存数据模拟这些方法测试您的业务逻辑.但这就是通用存储库不再足够的地步.(例如,你打算如何编写一个LINQ Join,其中存储库中只包含一个实体/ ObjectSet作为通用参数?)

如果你问十个人他们的存储库是如何构建的,你会得到十个不同的答案.没有一个是真的错误或最好的,因为它取决于您将使用此存储库构建的应用程序.我相信没有人能告诉你你的存储库真正值得的东西.当您开始编写应用程序时,您将在实践中看到它.对于某些应用程序,它可能过度架构(我认为这是最危险的,因为管理和保持无意义的架构受到控制是昂贵的,并且浪费了编写实际应用程序内容所浪费的时间).而对于其他需求,您可能需要扩展存储库.例如:

  • 如何处理实体导航属性的显式加载或查询(使用CreateSourceQueryDbEntityEntry.Collection/Reference在EF 4.1中)?如果您的应用程序永远不需要明确加载:很好.如果有必要,您需要扩展您的回购.

  • 你如何控制急切加载?有时您可能只想要一个父实体.有时你想要Includchildren1集合,有时你想要child2引用.

  • 如何手动设置实体的实体状态?也许你永远不必.但在下一个应用程序中它可能会非常有用.

  • 如何手动将实体与上下文分离?

  • 如何控制加载行为(有或没有上下文跟踪实体)?

  • 如何控制手动延迟加载行为和创建更改跟踪代理?

  • 如何手动创建实体代理?在使用延迟加载或更改跟踪代理时,在某些情况下可能需要它.

  • 如何在不构建结果集合的情况下将实体加载到上下文中?另一种存储库方法,也许......或许不是.谁事先知道您的应用程序逻辑需要什么.

等等等等...