С очень большим поклоном автору оригинального кода по ссылке, приведенной выше, вот что я закончил. Он проходит основные сценарии, но я еще не пробовал более глубокие навигационные и связанные запросы. Даже если есть проблема, я считаю, что это будет исправление ошибки, а не остановка шоу.
Здесь идет. Следующее можно использовать повторно, работает с несколькими контекстами и означает, что вы можете перейти к базе данных и получить что-то обратно в 2 строки реального кода на клиенте.
DataModel (проект библиотеки классов)
Ваш проект EF Code First с DOCContext POCO и т. Д.
DbContextManagement (проект библиотеки классов)
DbContextManager.cs
namespace DbContextManagement
{
using System.Data.Entity;
/// <summary>
/// Abstract base class for all other DbContextManager classes.
/// </summary>
public abstract class DbContextManager
{
/// <summary>
/// Returns a reference to an DbContext instance.
/// </summary>
/// <typeparam name="TDbContext">The type of the db context.</typeparam>
/// <returns>The current DbContext</returns>
public abstract TDbContext GetDbContext<TDbContext>()
where TDbContext : DbContext, new();
}
}
DbContextScope.cs
namespace DbContextManagement
{
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Threading;
/// <summary>
/// Defines a scope wherein only one DbContext instance is created, and shared by all of those who use it.
/// </summary>
/// <remarks>Instances of this class are supposed to be used in a using() statement.</remarks>
public class DbContextScope : IDisposable
{
/// <summary>
/// List of current DbContexts (supports multiple contexts).
/// </summary>
private readonly List<DbContext> contextList;
/// <summary>
/// DbContext scope definitiion.
/// </summary>
[ThreadStatic]
private static DbContextScope currentScope;
/// <summary>
/// Holds a value indicating whether the context is disposed or not.
/// </summary>
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="DbContextScope"/> class.
/// </summary>
/// <param name="saveAllChangesAtEndOfScope">if set to <c>true</c> [save all changes at end of scope].</param>
protected DbContextScope(bool saveAllChangesAtEndOfScope)
{
if (currentScope != null && !currentScope.isDisposed)
{
throw new InvalidOperationException("DbContextScope instances cannot be nested.");
}
this.SaveAllChangesAtEndOfScope = saveAllChangesAtEndOfScope;
this.contextList = new List<DbContext>();
this.isDisposed = false;
Thread.BeginThreadAffinity();
currentScope = this;
}
/// <summary>
/// Gets or sets a value indicating whether to automatically save all object changes at end of the scope.
/// </summary>
/// <value><c>true</c> if [save all changes at end of scope]; otherwise, <c>false</c>.</value>
private bool SaveAllChangesAtEndOfScope { get; set; }
/// <summary>
/// Save all object changes to the underlying datastore.
/// </summary>
public void SaveAllChanges()
{
var transactions = new List<DbTransaction>();
foreach (var context in this.contextList
.Select(dbcontext => ((IObjectContextAdapter)dbcontext)
.ObjectContext))
{
context.Connection.Open();
var databaseTransaction = context.Connection.BeginTransaction();
transactions.Add(databaseTransaction);
try
{
context.SaveChanges();
}
catch
{
/* Rollback & dispose all transactions: */
foreach (var transaction in transactions)
{
try
{
transaction.Rollback();
}
catch
{
// "Empty general catch clause suppresses any errors."
// Haven't quite figured out what to do here yet.
}
finally
{
databaseTransaction.Dispose();
}
}
transactions.Clear();
throw;
}
}
try
{
/* Commit all complete transactions: */
foreach (var completeTransaction in transactions)
{
completeTransaction.Commit();
}
}
finally
{
/* Dispose all transactions: */
foreach (var transaction in transactions)
{
transaction.Dispose();
}
transactions.Clear();
/* Close all open connections: */
foreach (var context in this.contextList
.Select(dbcontext => ((IObjectContextAdapter)dbcontext).ObjectContext)
.Where(context => context.Connection.State != System.Data.ConnectionState.Closed))
{
context.Connection.Close();
}
}
}
/// <summary>
/// Disposes the DbContext.
/// </summary>
public void Dispose()
{
// Monitor for possible future bugfix.
// CA1063 : Microsoft.Design : Provide an overridable implementation of Dispose(bool)
// on 'DbContextScope' or mark the type as sealed. A call to Dispose(false) should
// only clean up native resources. A call to Dispose(true) should clean up both managed
// and native resources.
if (this.isDisposed)
{
return;
}
// Monitor for possible future bugfix.
// CA1063 : Microsoft.Design : Modify 'DbContextScope.Dispose()' so that it calls
// Dispose(true), then calls GC.SuppressFinalize on the current object instance
// ('this' or 'Me' in Visual Basic), and then returns.
currentScope = null;
Thread.EndThreadAffinity();
try
{
if (this.SaveAllChangesAtEndOfScope && this.contextList.Count > 0)
{
this.SaveAllChanges();
}
}
finally
{
foreach (var context in this.contextList)
{
try
{
context.Dispose();
}
catch (ObjectDisposedException)
{
// Monitor for possible future bugfix.
// CA2202 : Microsoft.Usage : Object 'databaseTransaction' can be disposed
// more than once in method 'DbContextScope.SaveAllChanges()'.
// To avoid generating a System.ObjectDisposedException you should not call
// Dispose more than one time on an object.
}
}
this.isDisposed = true;
}
}
/// <summary>
/// Returns a reference to a DbContext of a specific type that is - or will be -
/// created for the current scope. If no scope currently exists, null is returned.
/// </summary>
/// <typeparam name="TDbContext">The type of the db context.</typeparam>
/// <returns>The current DbContext</returns>
protected internal static TDbContext GetCurrentDbContext<TDbContext>()
where TDbContext : DbContext, new()
{
if (currentScope == null)
{
return null;
}
var contextOfType = currentScope.contextList
.OfType<TDbContext>()
.FirstOrDefault();
if (contextOfType == null)
{
contextOfType = new TDbContext();
currentScope.contextList.Add(contextOfType);
}
return contextOfType;
}
}
}
FacadeBase.cs
namespace DbContextManagement
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Entity;
using System.Reflection;
/// <summary>
/// Generic base class for all other Facade classes.
/// </summary>
/// <typeparam name="TDbContext">The type of the db context.</typeparam>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TEntityKey">The type of the entity key.</typeparam>
/// <remarks>Not sure the handling of TEntityKey is something I've worked with properly.</remarks>
public abstract class FacadeBase<TDbContext, TEntity, TEntityKey>
where TDbContext : DbContext, new()
where TEntity : class
{
/// <summary>
/// Gets the db context.
/// </summary>
public TDbContext DbContext
{
get
{
if (DbContextManager == null)
{
this.InstantiateDbContextManager();
}
return DbContextManager != null
? DbContextManager.GetDbContext<TDbContext>()
: null;
}
}
/// <summary>
/// Gets or sets the DbContextManager.
/// </summary>
/// <value>The DbContextManager.</value>
private DbContextManager DbContextManager { get; set; }
/// <summary>
/// Adds a new entity object to the context.
/// </summary>
/// <param name="newObject">A new object.</param>
public virtual void Add(TEntity newObject)
{
this.DbContext.Set<TEntity>().Add(newObject);
}
/// <summary>
/// Deletes an entity object.
/// </summary>
/// <param name="obsoleteObject">An obsolete object.</param>
public virtual void Delete(TEntity obsoleteObject)
{
this.DbContext.Set<TEntity>().Remove(obsoleteObject);
}
/// <summary>
/// Gets all entities for the given type.
/// </summary>
/// <returns>DbContext Set of TEntity.</returns>
public virtual IEnumerable<TEntity> GetAll()
{
return this.DbContext.Set<TEntity>();
}
/// <summary>
/// Gets the entity by the specified entity key.
/// </summary>
/// <param name="entityKey">The entity key.</param>
/// <returns>Entity matching specified entity key or null if not found.</returns>
public virtual TEntity GetByKey(TEntityKey entityKey)
{
return this.DbContext.Set<TEntity>().Find(entityKey);
}
/// <summary>
/// Deletes the entity for the specified entity key.
/// </summary>
/// <param name="entityKey">The entity key.</param>
public virtual void DeleteByKey(TEntityKey entityKey)
{
var entity = this.DbContext.Set<TEntity>().Find(entityKey);
if (entity != null)
{
this.DbContext.Set<TEntity>().Remove(entity);
}
}
/// <summary>
/// Instantiates a new DbContextManager based on application configuration settings.
/// </summary>
private void InstantiateDbContextManager()
{
/* Retrieve DbContextManager configuration settings: */
var contextManagerConfiguration = ConfigurationManager.GetSection("DbContext") as Hashtable;
if (contextManagerConfiguration == null)
{
throw new ConfigurationErrorsException("A Facade.DbContext tag or its managerType attribute is missing in the configuration.");
}
if (!contextManagerConfiguration.ContainsKey("managerType"))
{
throw new ConfigurationErrorsException("dbManagerConfiguration does not contain key 'managerType'.");
}
var managerTypeName = contextManagerConfiguration["managerType"] as string;
if (string.IsNullOrEmpty(managerTypeName))
{
throw new ConfigurationErrorsException("The managerType attribute is empty.");
}
managerTypeName = managerTypeName.Trim().ToUpperInvariant();
try
{
/* Try to create a type based on it's name: */
var frameworkAssembly = Assembly.GetAssembly(typeof(DbContextManager));
var managerType = frameworkAssembly.GetType(managerTypeName, true, true);
/* Try to create a new instance of the specified DbContextManager type: */
this.DbContextManager = Activator.CreateInstance(managerType) as DbContextManager;
}
catch (Exception e)
{
throw new ConfigurationErrorsException("The managerType specified in the configuration is not valid.", e);
}
}
}
}
ScopedDbContextManager.cs
namespace DbContextManagement
{
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
/// <summary>
/// Manages multiple db contexts.
/// </summary>
public sealed class ScopedDbContextManager : DbContextManager
{
/// <summary>
/// List of Object Contexts.
/// </summary>
private List<DbContext> contextList;
/// <summary>
/// Returns the DbContext instance that belongs to the current DbContextScope.
/// If currently no DbContextScope exists, a local instance of an DbContext
/// class is returned.
/// </summary>
/// <typeparam name="TDbContext">The type of the db context.</typeparam>
/// <returns>Current scoped DbContext.</returns>
public override TDbContext GetDbContext<TDbContext>()
{
var currentDbContext = DbContextScope.GetCurrentDbContext<TDbContext>();
if (currentDbContext != null)
{
return currentDbContext;
}
if (this.contextList == null)
{
this.contextList = new List<DbContext>();
}
currentDbContext = this.contextList.OfType<TDbContext>().FirstOrDefault();
if (currentDbContext == null)
{
currentDbContext = new TDbContext();
this.contextList.Add(currentDbContext);
}
return currentDbContext;
}
}
}
UnitOfWorkScope.cs
namespace DbContextManagement
{
/// <summary>
/// Defines a scope for a business transaction. At the end of the scope all object changes can be persisted to the underlying datastore.
/// </summary>
/// <remarks>Instances of this class are supposed to be used in a using() statement.</remarks>
public sealed class UnitOfWorkScope : DbContextScope
{
/// <summary>
/// Initializes a new instance of the <see cref="UnitOfWorkScope"/> class.
/// </summary>
/// <param name="saveAllChangesAtEndOfScope">if set to <c>true</c> [save all changes at end of scope].</param>
public UnitOfWorkScope(bool saveAllChangesAtEndOfScope)
: base(saveAllChangesAtEndOfScope)
{
}
}
}
Фасад (Проект библиотеки классов)
YourEntityFacade.cs
namespace Facade
{
using System.Collections.Generic;
using System.Linq;
using DataModel;
using DataModel.Entities;
using DbContextManagement;
public class YourEntityFacade : FacadeBase<YourDbContext, YourEntity, int>
{
public override IEnumerable<YourEntity> GetAll()
{
return base.GetAll()
.Distinct()
.ToList();
}
}
}
TestConsole (проект консольного приложения)
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="DbContext" type="System.Configuration.SingleTagSectionHandler" />
</configSections>
<DbContext managerType="DbContextManagement.ScopedDbContextManager" />
<connectionStrings>
<add
name="YourDbContext"
providerName="System.Data.SqlClient"
connectionString="Your connection string" />
</connectionStrings>
</configuration>
Program.cs
namespace TestConsole
{
using System;
using System.Collections.Generic;
using DataModel.Entities;
using DbContextManagement;
using Facade;
public static class Program
{
public static void Main()
{
TestGetAll();
Console.ReadLine();
}
private static void TestGetAll()
{
Console.WriteLine();
Console.WriteLine("Test GetAll()");
Console.WriteLine();
IEnumerable<YourEntity> yourEntities;
using (new UnitOfWorkScope(false))
{
yourEntities= new YourEntityFacade().GetAll();
}
if (yourEntities != null)
{
foreach (var yourEntity in yourEntities)
{
Console.WriteLine(
string.Format("{0}, {1}",
yourEntity.Id,
yourEntity.Name));
}
}
else
{
Console.WriteLine("GetAll() NULL");
}
}
}
}
Итак, использование очень простое, и вы можете работать с несколькими контекстами и фасадами в пределах области действия. При таком подходе единственный код, который вы пишете, - это пользовательские запросы. Больше не нужно бесконечно обновлять UnitOfWorks ссылками на репозитории и создавать репозитории copy-cat. Я думаю, это здорово, но имейте в виду, что это бета-код, и я уверен, что где-то есть большая дыра, которую нужно будет подключить:)
Спасибо всем и, в частности, Ладиславу за терпение и помощь по этому и многим другим смежным вопросам, которые я задаю. Я надеюсь, что этот код представляет интерес. Я связался с автором в блоге выше, но ответа пока нет, и в эти дни я думаю, что он в NHibernate.
Richard