در این پست قصد دارم یک UnitOfWork به روش MEF پیاده سازی کنم. ORM مورد نظر EntityFramework CodeFirst است. در صورتی که با UnitOfWork , MEF آشنایی ندارید از لینکهای زیر استفاده کنید:
برای شروع ابتدا مدل برنامه رو به صورت زیر تعریف کنید.
public class Category
{
public int Id { get; set; }
public string Title { get; set; }
}
سپس فایل Map رو برای مدل بالا به صورت زیر تعریف کنید.
public class CategoryMap : EntityTypeConfiguration<Entity.Category>
{
public CategoryMap()
{
ToTable( "Category" );
HasKey( _field => _field.Id );
Property( _field => _field.Title )
.IsRequired();
}
}
برای پیاده سازی الگوی واحد کار ابتدا باید یک اینترفیس به صورت زیر تعریف کنید.
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
namespace DataAccess
{
public interface IUnitOfWork
{
DbSet<TEntity> Set<TEntity>() where TEntity : class;
DbEntityEntry<TEntity> Entry<TEntity>() where TEntity : class;
void SaveChanges();
void Dispose();
}
}
DbContext مورد نظر باید اینترفیس مورد نظر را پیاده سازی کند و برای اینکه بتونیم اونو در CompositionContainer اضافه کنیم باید از Export Attribute استفاده کنیم.
چون کلاس DatabaseContext از اینترفیس IUnitOfWork ارث برده است برای همین از InheritedExport استفاده میکنیم.
[InheritedExport( typeof( IUnitOfWork ) )]
public class DatabaseContext : DbContext, IUnitOfWork
{
private DbTransaction transaction = null;
public DatabaseContext()
{
this.Configuration.AutoDetectChangesEnabled = false;
this.Configuration.LazyLoadingEnabled = true;
}
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.AddFormAssembly( Assembly.GetAssembly( typeof( Entity.Map.CategoryMap ) ) );
}
public DbEntityEntry<TEntity> Entry<TEntity>() where TEntity : class
{
return this.Entry<TEntity>();
}
}
نکته قابل ذکر در قسمت OnModelCreating این است که یک Extension Methodبه نام AddFromAssembly (همانند NHibernate) اضافه شده است که از Assembly مورد نظر تمام کلاسهای Map رو پیدا میکنه و اونو به ModelBuilder اضافه میکنه. کد متد به صورت زیر است:
public static class ModelBuilderExtension
{
public static void AddFormAssembly( this DbModelBuilder modelBuilder, Assembly assembly )
{
Array.ForEach<Type>( assembly.GetTypes().Where( type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof( EntityTypeConfiguration<> ) ).ToArray(), delegate( Type type )
{
dynamic instance = Activator.CreateInstance( type );
modelBuilder.Configurations.Add( instance );
} );
}
}
برای پیاده سازی قسمت BusinessLogic ابتدا کلاس BusiessBase را در آن قرار دهید:
public class BusinessBase<TEntity> where TEntity : class
{
public BusinessBase( IUnitOfWork unitOfWork )
{
this.UnitOfWork = unitOfWork;
}
[Import]
public IUnitOfWork UnitOfWork
{
get;
private set;
}
public virtual IEnumerable<TEntity> GetAll()
{
return UnitOfWork.Set<TEntity>().AsNoTracking();
}
public virtual void Add( TEntity entity )
{
try
{
UnitOfWork.Set<TEntity>().Add( entity );
UnitOfWork.SaveChanges();
}
catch
{
throw;
}
finally
{
UnitOfWork.Dispose();
}
}
}
تمام متدهای پایه مورد نظر را باید در این کلاس قرار داد که برای مثال
من متد Add , GetAll را براتون پیاده سازی کردم. UnitOfWork توسط
ImportAttribute مقدار دهی میشود و نیاز به وهله سازی از آن نیست
کلاس Category رو هم باید به صورت زیر اضافه کنید.
public class Category : BusinessBase<Entity.Category>
{
[ImportingConstructor]
public Category( [Import( typeof( IUnitOfWork ) )] IUnitOfWork unitOfWork )
: base( unitOfWork )
{
}
}
.در انتها باید UI مورد نظر طراحی شود که من در اینجا از Console Application استفاده کردم. یک کلاس به نام Plugin ایجاد کنید و کدهای زیر را در آن قرار دهید.
public class Plugin
{
public void Run()
{
AggregateCatalog catalog = new AggregateCatalog();
Container = new CompositionContainer( catalog );
CompositionBatch batch = new CompositionBatch();
catalog.Catalogs.Add( new AssemblyCatalog( Assembly.GetExecutingAssembly() ) );
batch.AddPart( this );
Container.Compose( batch );
}
public CompositionContainer Container
{
get;
private set;
}
}
در کلاس Plugin توسط AssemblyCatalog تمام Export Attributeهای موجود جستجو میشود و بعد به عنوان کاتالوگ مورد نظر به Container اضافه میشود. انواع Catalog در MEF به شرح زیر است:
- AssemblyCatalog : در اسمبلی مورد نظر به دنبال تمام Export Attributeها میگردد و آنها را به عنوان ExportedValue در Container اضافه میکند.
- TypeCatalog: فقط یک نوع مشخص را به عنوان ExportAttribute در نظر میگیرد.
- DirectoryCatalog : در یک مسیر مشخص تمام Assembly مورد نظر را از نظر Export Attribute جستجو میکند و آنها را به عنوان ExportedValue در Container اضافه میکند.
- ApplicationCatalog : در اسمبلی و فایلهای (EXE) مورد نظر به دنبال تمام Export Attributeها میگردد و آنها را به عنوان ExportedValue در Container اضافه میکند.
- AggregateCatalog : تمام موارد فوق را Support میکند.
کلاس Program رو به صورت زیر بازنویسی کنید.
class Program
{
static void Main( string[] args )
{
Plugin plugin = new Plugin();
plugin.Run();
Category category = new Category(plugin.Container.GetExportedValue<IUnitOfWork>());
category.GetAll().ToList().ForEach( _record => Console.Write( _record.Title ) );
}
}
پروژه اجرا کرده و نتیجه رو مشاهده کنید.