繁体   English   中英

覆盖LINQ .Include(),可能吗?

[英]Override LINQ .Include(), possible?

我已经在这个问题上浏览互联网很长一段时间了,而且我在覆盖LINQ方法方面得到的结果要少得多。 我不确定是否可以做到,但我想知道是否有人可以确认这是否有效,或者建议替代方案。

情况如下(当然,这个问题简化了)

我们使用EF6 Code First来构建我们的数据库。 我们添加了一个自定义(抽象)基类,所有实体都派生自该基类。 这个基类实现了我们用于审计的一些字段(创建日期,创建日期,修改日期......),但我们还通过在基类中添加IsDeleted (bool)属性来实现软删除系统。

就我们的应用程序所知,绝不能返回具有IsDeleted == true的项目。

DataModel如下(再次,简化)

Company
    ---> 1 Company has many Departments
    Department
        ---> 1 Department has many Adresses
        Address

在过去,我试图创建一个通用的检索方法,通过在DataContext中创建表的“覆盖”来消除IsDeleted对象(也是一个自定义类,因为它会自动处理审计字段)。

对于在DataContext中找到的每个表:

public DbSet<Company> Companies { get; set; }

我们添加了第二个表,只返回未删除的项目。

public IQueryable<Company> _Companies 
{
    get { return this.Companies .Where(co => !co.IsDeleted); }
}

所以我们调用MyDataContext._Companies而不是MyDataContext.Companies 这按预期工作。 它很好地过滤掉已删除的项目。

但是,我们注意到后续的.Include()语句也是如此。 如果我打电话:

var companies = MyDataContext._Companies.Include(x => x.Departments);

//...

还会返回公司中已删除的部门。

在我们目前的情况下,大多数核心业务逻辑已经实现,包括语句在内。 它们主要与安全有关。 我可以更改所有语句,但我宁愿首先寻找一种方法来做到这一点,而不会过多地影响现有代码。
这是第一个应用程序,其中查询的大小不允许我们分别调用每组实体(仅使用直接表而不是include语句)。

所以我的问题是双重的:

  • 我可以覆盖.Include(Func<x,y>)方法以自动包含对所选实体的IsDeleted标志的检查吗?
  • 如果可能的话,如何覆盖,如何将传递的lambda表达式与我想要执行的附加检查相结合?

所以通过电话

someTable.Include(x => x.MyRelatedEntity);

它实际上会执行:

/* Not sure if this is proper syntax. I hope it explains what I'm trying to accomplish. */
someTable.Include(x => x.MyRelatedEntity.Where(y => !y.IsDeleted));

有人能指出我正确的方向吗? 非常感激!

注意:我知道我的问题代码不多。 但我甚至不确定我能在多大程度上实现这一点。 如果无法覆盖Include,还有其他方法吗?

更新

我实现了建议的解决方案,但是我遇到了所有数据库调用的问题。 错误如下:

Problem in mapping fragments starting at line 245:Condition member 'Company.IsDeleted' with a condition other than 'IsNull=False' is mapped. Either remove the condition on Company.IsDeleted or remove it from the mapping.

阅读这个问题,似乎如果我使用IsDeleted作为条件(即建议的解决方案),我仍然不能将它用作属性。

然后问题变成:如何删除某些内容? 删除后,永远不应该返回。 但是应该能够删除未删除的项目。

有没有办法可以通过IsDeleted过滤返回的项目, 但仍然可以将其设置为true并保存它

您正在寻找的解决方案是要求实体的IsDeleted值为false

modelBuilder.Entity<Company>()
    .Map( emc => emc.Requires( "IsDeleted" ).HasValue( false ) );

现在只能从数据库中检索出IsDeleted == false公司

评论更新:

modelBuilder.Entity<Company>()
    .Map( emc => 
    {
        emc.MapInheritedProperties();
        emc.Requires( "IsDeleted" ).HasValue( false );
    } )
    .Ignore( c => c.IsDeleted );

更新:测试代码成功( 此处找到帮助方法):

[Table("EntityA")]
public partial class EntityA
{
    public int EntityAId { get; set; }
    public string Description { get; set; }


    public virtual EntityB PrimaryEntityB { get; set; }

    public virtual EntityB AlternativeEntityB { get; set; }

    public bool IsDeleted { get; set; }
}

[Table("EntityB")]
public partial class EntityB
{
    public int EntityBId { get; set; }
    public string Description { get; set; }

    [InverseProperty("PrimaryEntityB")]
    public virtual ICollection<EntityA> EntityAsViaPrimary { get; set; }
    [InverseProperty( "AlternativeEntityB" )]
    public virtual ICollection<EntityA> EntityAsViaAlternative { get; set; }
}

public partial class TestEntities : DbContext
{
    public TestEntities()
        : base("TestEntities")
    {
        Database.SetInitializer( new DatabaseInitializer() );
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<EntityA>()
            .Map( emc =>
                {
                    emc.Requires( "IsDeleted" ).HasValue( false );
                } )
                .Ignore( a => a.IsDeleted );
    }

    public override int SaveChanges()
    {
        foreach( var entry in this.ChangeTracker.Entries<EntityA>() )
        {
            if( entry.State == EntityState.Deleted )
            {
                SoftDelete( entry );
            }
        }

        return base.SaveChanges();
    }

    private void SoftDelete( DbEntityEntry entry )
    {
        var entityType = entry.Entity.GetType();

        var tableName = GetTableName( entityType );
        var pkName = GetPrimaryKeyName( entityType );

        var deleteSql = string.Format( "update {0} set IsDeleted = 1 where {1} = @id",
            tableName,
            pkName );

        Database.ExecuteSqlCommand( deleteSql, new SqlParameter( "@id", entry.OriginalValues[ pkName ] ) );

        entry.State = EntityState.Detached;
    }

    private string GetPrimaryKeyName( Type type )
    {
        return GetEntitySet( type ).ElementType.KeyMembers[ 0 ].Name;
    }

    private string GetTableName( Type type )
    {
        EntitySetBase es = GetEntitySet( type );

        return string.Format( "[{0}].[{1}]",
            es.MetadataProperties[ "Schema" ].Value,
            es.MetadataProperties[ "Table" ].Value );
    }
    private EntitySetBase GetEntitySet( Type type )
    {
        ObjectContext octx = ( ( IObjectContextAdapter )this ).ObjectContext;

        string typeName = ObjectContext.GetObjectType( type ).Name;

        var es = octx.MetadataWorkspace
                        .GetItemCollection( DataSpace.SSpace )
                        .GetItems<EntityContainer>()
                        .SelectMany( c => c.BaseEntitySets
                                        .Where( e => e.Name == typeName ) )
                        .FirstOrDefault();

        if( es == null )
            throw new ArgumentException( "Entity type not found in GetTableName", typeName );

        return es;
    }

    public DbSet<EntityA> EntityAs { get; set; }
    public DbSet<EntityB> EntityBs { get; set; }
}

申请代码:

class Program
{
    static void Main(string[] args)
    {
        using( var db = new TestEntities() )
        {
            var a0 = new EntityA()
                {
                    EntityAId = 1,
                    Description = "hi"
                };

            var a1 = new EntityA()
                {
                    EntityAId = 2,
                    Description = "bye"
                };

            db.EntityAs.Add( a0 );
            db.EntityAs.Add( a1 );

            var b = new EntityB()
            {
                EntityBId = 1,
                Description = "Big B"
            };

            a1.PrimaryEntityB = b;

            db.SaveChanges();

            // this prints "1"
            Console.WriteLine( b.EntityAsViaPrimary.Count() );

            db.EntityAs.Remove( a1 );

            db.SaveChanges();

            // this prints "0"
            Console.WriteLine( b.EntityAsViaPrimary.Count() );
        }

        var input = Console.ReadLine();
    }
}

你可以这样做软删除:

  1. 在OnModelCreating中,为每个可以软删除的实体添加一个IsDeleted鉴别器
  2. 覆盖SaveChanges并查找要删除的所有条目
  3. 在这些条目上运行SQL以设置IsDeleted鉴别器,然后将其状态设置为“已分离”
  4. 更改任何唯一索引以忽略任何软删除记录

您可以在此答案中找到工作代码: 如何使用Entity Framework Code First进行软删除

这个代码被这个博客选中: http//netpl.blogspot.com/2013/10/soft-delete-pattern-for-entity.html

四年后,我终于偶然发现了一个完全符合我想要的库。

EntityFramework.DynamicFilter

modelBuilder.Filter("IsDeleted", (ISoftDelete d) => d.IsDeleted, false);

上面的代码段确保不会检索软删除的项目。 影响直接查询( db.Set<Foo>().ToList()和间接loadaed实体db.Set<Foo>().Include(e => e.Bars).ToList()从而完全隐藏视图中的软删除实体。


为了完整起见,我将它与对SaveChanges()的重写结合起来,它在提交到数据库之前将硬删除转换为软删除。

这意味着开发人员能够安全地使用硬删除逻辑,他们甚至都不会意识到上下文正在使用软删除。 他们不需要知道,他们不需要关心,他们不能忘记以正确的方式实现它,他们永远不需要为IsDeleted标志写一个检查。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM