Soft Delete در Entity Framework 6
نویسنده: م کریمی
تاریخ: ۱۳۹۳/۰۲/۳۱ ۱۰:۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
using System.Data.Entity.Core.Metadata.Edm;
public class SoftDeleteAttribute : Attribute
{
public string ColumnName { get; set; }
public SoftDeleteAttribute(string column)
{
ColumnName = column;
}
public static string GetSoftDeleteColumnName(EdmType type)
{
MetadataProperty column = type.MetadataProperties.Where(x => x.Name.EndsWith("customannotation:SoftDeleteColumnName")).SingleOrDefault();
return column == null ? null : (string)column.Value;
}
} [SoftDelete("IsDeleted")]
public class TblUser
{
[Key]
public int TblUserID { get; set; }
[MaxLength(30)]
public string Name { get; set; }
public bool IsDeleted { get; set; }
} protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var Conv = new AttributeToTableAnnotationConvention<SoftDeleteAttribute, string>(
"SoftDeleteColumnName",
(type, attribute) => attribute.Single().ColumnName);
modelBuilder.Conventions.Add(Conv);
}
در پنجره باز شده به قسمت سوم یعنی <StorageModels> مراجعه نمایید و بایستی گزینه زیر را مشاهده نمایید .
<EntityType Name="TblUser" customannotation:SoftDeleteColumnName="IsDeleted">
تا اینجای کار ما توانستیم یک Annotation جدید را به Ef اضافه نماییم .
در مرحله بعد بایستی به Ef دستور دهیم که در تولید Query بر روی این Entity، این مورد را نیز لحاظ کند.
برای این کار کلاسی را ایجاد مینماییم که از اینترفیس IDbCommandTreeInterceptor ارث بری مینماید. مانند کد زیر :
public class SoftDeleteInterceptor : IDbCommandTreeInterceptor
{
public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
{
if (interceptionContext.OriginalResult.DataSpace == System.Data.Entity.Core.Metadata.Edm.DataSpace.SSpace)
{
var QueryCommand = interceptionContext.Result as DbQueryCommandTree;
if (QueryCommand != null)
{
var newQuery = QueryCommand.Query.Accept(new SoftDeleteQueryVisitor());
interceptionContext.Result = new DbQueryCommandTree(QueryCommand.MetadataWorkspace, QueryCommand.DataSpace, newQuery);
}
}
}
} در ابتدا تشخیص داده میشود که نوع خروجی Query آیا از نوع Storage Model است . ( برای توضیحات بیشتر ) سپس پرس و جوی تولید شده را با استفاده از الگوی visitor تغییر داده و Query جدید را تولید نموده و در انتها Query جدیدی را به جای Query قبلی جایگزین مینماییم.
در اینجا ما نیاز به داشتن کلاس SoftDeleteQueryVisitor برای تغییر دادن Query و اضافه نمودن IsDeleted <>1 به Query میباشیم.
یک کلاس دیگری با نام SoftDeleteQueryVisitor به شکل زیر به برنامه اضافه مینماییم.
public class SoftDeleteQueryVisitor : DefaultExpressionVisitor
{
public override DbExpression Visit(DbScanExpression expression)
{
var column = SoftDeleteAttribute.GetSoftDeleteColumnName(expression.Target.ElementType);
if (column!=null)
{
var Binding = DbExpressionBuilder.Bind(expression);
return DbExpressionBuilder.Filter(Binding, DbExpressionBuilder.NotEqual(DbExpressionBuilder.Property(DbExpressionBuilder.Variable(Binding.VariableType, Binding.VariableName), column), DbExpression.FromBoolean(true)));
}
else
{
return base.Visit(expression);
}
}
} در نهایت برای اینکه EF تشخیص دهد که یکچنین Interceptor ایی وجود دارد، بایستی در کلاس DbContextConfig، کلاس SoftDeleteInterceptor را اضافه نماییم؛ همانند کد زیر:
public class DbContextConfig : DbConfiguration
{
public DbContextConfig()
{
AddInterceptor(new SoftDeleteInterceptor());
}
} تا اینجا در تمام Queryهای تولید شده بر روی Entity که با خاصیت SoftDelete مزین شده است، مقدار IsDeleted <> 1 را به صورت اتوماتیک اعمال مینماید. حتی به صورت هوشمند چنانچه این موجودیت در یک Join استفاده شده باشد این شرط را قبل از Join به Query تولید شده اضافه مینماید.
در مقاله بعدی در مورد تغییر کد Remove به کد Update توضیح داده خواهد شد.
برای مطالعه بیشتر
Entity Framework: Building Applications with Entity Framework 6
با سلام و تشکر
کسانی که از روش DB First استفاده میکنند میتوانند در پنجره Mapping Details از گزینه "Add a Condition" برای پیاده سازی این گونه شرطها استفاده کنند.
modelBuilder.Entity<BaseEntity>().Filter("IsDeleted", condition => condition.Condition(row => row.IsDeleted == false));
DbInterception.Add(new FilterInterceptor()); private readonly IDbSet<Category> _categories _categories = _uow.Set<Category>(); _categories.ToList();
var context = new DataLayer.MyContext();
context.EnableFilter("IsDeleted");
var categoiresModel = context.Set<Category>().ToList(); public void SetEnableActiveFilter(bool isActive)
{
this.EnableFilter("IsActive").SetParameter("isActive", isActive);
}
public void SetDisableActiveFilter()
{
this.DisableFilter("IsActive");
} modelBuilder.Conventions.Add(FilterConvention.Create<BaseEntity, bool>("IsActive", (entity, isActive) => entity.IsActive == isActive)); protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>().HasQueryFilter(e => !e.IsDeleted);
base.OnModelCreating(modelBuilder);
} private void ConfigureQueryFilters<TEntity>(ModelBuilder modelBuilder, IMutableEntityType entityType) where TEntity : class
{
if (entityType.BaseType != null || !ShouldFilterEntity<TEntity>()) return;
var filterExpression = BuildFilterExpression<TEntity>();
if (filterExpression == null) return;
if (entityType.IsQueryType)
modelBuilder.Query<TEntity>().HasQueryFilter(filterExpression);
else
modelBuilder.Entity<TEntity>().HasQueryFilter(filterExpression);
}