【问题标题】:Override LINQ .Include(), possible?覆盖 LINQ .Include(),可能吗?
【发布时间】:2014-01-24 08:57:01
【问题描述】:

我已经为这个问题浏览互联网很长一段时间了,但我得到的关于覆盖 LINQ 方法的结果要少得多。我不确定它是否可以完成,但我想知道是否有人可以确认这是否有效,或者提出替代方案。

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

我们正在使用 EF6 Code First 来构建我们的数据库。我们添加了一个自定义(抽象)基类,所有实体都从该基类派生。这个基类实现了我们用于审计的一些字段(创建日期、创建者、修改日期……),但我们还通过在基类。

就我们的应用程序所知,不得返回带有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(Func&lt;x,y&gt;) 方法以自动检查所选实体的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 并保存

【问题讨论】:

  • 改变公司类怎么样?您可以尝试添加一个选择未删除部门的属性,或者您可以重命名以使其在公司内部工作: public IQueryable _Departments { get { return this.Departments.Where(co => !co.IsDeleted); } } 然后,使用someTable.Include(X=&gt;X._Departments)
  • 你会如何改变它?此外,这是一个简化的示例,实际上有 13 个表和它们之间的 18 个关系。更改实体是可能的,但前提是它不会影响整个代码库。
  • 看看模型,当映射到模型时,您应该能够过滤(我相信它称为Ignore)行。我的建议是根本不要碰Include 方法,它处于错误的级别。
  • 似乎 Ignore 实际上删除了一个字段(大多数谷歌结果都是关于 NotMapped 属性)。我试图强制评估属性,而不是忽略。如果我误解了您的评论,对不起:)

标签: c# linq entity-framework linq-to-entities


【解决方案1】:

您正在寻找的解决方案是要求实体的 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 );

更新:成功的测试代码(找到了帮助方法here):

[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();
    }
}

【讨论】:

  • 这看起来很有希望!我们已经使用 Map() 方法来映射继承的属性。我可以将第二个 Map() 语句链接到此,还是需要将它与前一个语句结合起来?为清楚起见:modelBuilder.Entity&lt;Company&gt;().Map(m =&gt; m.MapInheritedProperties()); 将变为 modelBuilder.Entity&lt;Company&gt;().Map(m =&gt; m.MapInheritedProperties()).Map(m =&gt; m.Requires("IsDeleted").HasValue(false)); ?
  • 使用花括号在地图表达式中启用多行。我会更新解决方案
  • 实际上,我认为.Ignore 调用不应该在那里,所以我删除了它
  • 这很好,因为我今天了解了 Ignore :) 但是在这种情况下我们不使用它,因为我们实际上使用了大多数审计字段。但它是多余的,因为它总是错误的。
  • 对不起,我从你那里得到了答案,但我遇到了这个实现的问题。请参阅我更新的问题。提前致谢!
【解决方案2】:

您可以像这样进行软删除:

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

您可以在此答案中找到工作代码: How to soft delete using Entity Framework Code First

并且该代码被此博客拾取: http://netpl.blogspot.com/2013/10/soft-delete-pattern-for-entity.html

【讨论】:

    【解决方案3】:

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

    EntityFramework.DynamicFilter.

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

    上面的sn-p保证了软删除的项不被检索。这会影响两个直接查询 (db.Set&lt;Foo&gt;().ToList()) 和间接加载的实体 db.Set&lt;Foo&gt;().Include(e =&gt; e.Bars).ToList()),从而完全隐藏软删除的实体。


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

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

    【讨论】:

      猜你喜欢
      • 2011-02-17
      • 1970-01-01
      • 2011-12-03
      • 2012-03-11
      • 2012-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-18
      相关资源
      最近更新 更多