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

Fla*_*ter 5 c# linq linq-to-entities entity-framework

我已经在互联网上浏览这个问题相当长一段时间了,但我得到的与重写 LINQ 方法有关的结果要少得多。我不确定是否可以做到,但我想知道是否有人可以确认这是否有效,或者提出替代方案。

情况如下(当然针对这个问题进行了简化)

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

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

DataModel 如下(再次简化)

Company
    ---> 1 Company has many Departments
    Department
        ---> 1 Department has many Adresses
        Address
Run Code Online (Sandbox Code Playgroud)

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

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

public DbSet<Company> Companies { get; set; }
Run Code Online (Sandbox Code Playgroud)

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

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

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

然而,我们注意到随后的声明却并非如此.Include()。如果我打电话:

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

//...
Run Code Online (Sandbox Code Playgroud)

从公司中删除的部门也会返回。

就我们现在的情况来说,大部分核心业务逻辑已经实现了,这些include语句也到处都是。它们主要与安全有关。我可以更改所有语句,但我宁愿首先寻找一种方法来执行此操作,而又不会对现有代码产生太大影响。
这是第一个应用程序,其中查询的大小不允许我们单独调用每组实体(通过仅使用直接表而不是包含语句)。

所以我的问题是双重的:

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

所以通过调用

someTable.Include(x => x.MyRelatedEntity);
Run Code Online (Sandbox Code Playgroud)

它实际上会执行:

/* 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));
Run Code Online (Sandbox Code Playgroud)

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

注意:我知道我的问题中没有太多代码。但我什至不确定我可以在什么级别上实现这一点。如果 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.
Run Code Online (Sandbox Code Playgroud)

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

那么问题就变成了:如何删除某些内容?一旦删除,就不应归还。但未删除的项目应该能够被删除。

有什么方法可以通过 IsDeleted 过滤返回的项目,但仍然允许将其设置为 true 并保存它

Moh*_*oho 2

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

modelBuilder.Entity<Company>()
    .Map( emc => emc.Requires( "IsDeleted" ).HasValue( false ) );
Run Code Online (Sandbox Code Playgroud)

现在只有包含的公司IsDeleted == false才会从数据库中检索

评论更新:

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

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

[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; }
}
Run Code Online (Sandbox Code Playgroud)

申请代码:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)