【问题标题】:generic class for linq / lambda expressionlinq / lambda 表达式的泛型类
【发布时间】:2015-01-07 20:50:20
【问题描述】:

C# 实体框架 4.0

我有一个包含 10 个表的数据库,其中包含 2 个公共列“id”和“modstamp”

要访问表中的 modstamp,我有一个函数

protected internal override string GetModStampinChild(int sid)
{
    DBContext sq = new DBContext();
    return sq.xxxx.Where(s => s.id == sid)
        .Select(s => s.modstamp).SingleOrDefault().ToModStampString();
}

其中 xxxx 为每个表更改。 我目前正在为每个表覆盖这个函数。

有没有办法使用某种通用的“类”,我可以在“xxxx”是任何表的地方使用它?

【问题讨论】:

  • 我相信您可以通过让所有表实体实现一个公开idmodstamp 属性的接口来做到这一点。
  • 如果在没有try{}finally{}using(){} 块的情况下使用DBContext sq = new DBContext();,则存在非常真实的连接泄漏风险。
  • 您能否阐明为什么要独立于实体中的其他列检索 ModStamp?并且请将您的新 DBContext() 调用包装在 using 语句中,否则您将泄漏该连接。
  • 在任何地方添加使用太糟糕了,它不在 Microsoft 模板中。

标签: c# linq generics lambda


【解决方案1】:

首先,您需要让所有实体实现一个接口或一个包含IDModStamp 属性的抽象类,我们称之为Stampable

public abstract class Stampable 
{
  [Key]
  public int ID { get; set; } 

  [Required]
  public string ModStamp { get; set; }
}

此时,您需要为您的方法做的就是让它实现泛型类型:

protected internal override string GetModStampInChild<T>(int sid) where T : Stampable
{
    using (var sq = new DbContext())
    {
      return sq.Set<T>.Where(s => s.id == sid)
                      .Select(s => s.modstamp)
                      .SingleOrDefault()
                      .ToModStampString();
    }
}

【讨论】:

  • 从性能的角度来看,当您可能已经在不同的 DbContext() 中检索到值时,您是否真的要进行另一次数据库往返以获取 ModStamp。
  • 这是一个很好的观点,但我将这个示例视为一个一般概念,而不是解决问题。 modstamp 是一个时间戳,用于检测乐观并发问题。但是是的,在大多数情况下,我需要放置整行。
  • 这是 Sql Server 时间戳数据类型吗?如果是这样,您不应该做任何事情,因为 EF 会将其映射到 byte[] 数据类型。
【解决方案2】:

如果我理解正确,您需要 Set&lt;T&gt; 的属性 DbContext 类:

首先,使用idmodstamp 属性创建所有实体类的基类。那么:

protected internal override string GetModStampInChild<T>(int sid) where T : BaseEntity
{
    using (var sq = new DbContext())
    {
        return sq.Set<T>.Where(s => s.id == sid)
                        .Select(s => s.modstamp)
                        .SingleOrDefault()
                        .ToModStampString();
    }
}

但是对于这种方法,您必须使用代码优先范式。

【讨论】:

  • 我首先使用 DB 这就是为什么我会收到编译错误:'System.Data.Entity.DbContext.Set()' is a 'method',它在给定上下文。
  • 如果我在收到其他编译错误后添加 ():类型“T”必须是引用类型才能将其用作泛型类型或方法中的参数“TEntity”
  • 是的,我忘了Set是方法,不是属性,但没关系。不幸的是,这种方法不能首先与 DB 一起使用,因为在这种情况下你不能自动生成实体。
【解决方案3】:

另一种选择是通过 c# 的部分类功能向您的实体类添加一个新属性。

所以生成的实体定义可能看起来像这样,注意我不知道你的 ModStamp 列的实际 DataType 是什么:

public partial class Company
{
    public int Id { get; set; }
    public byte[] ModStamp { get; set; }
    public string Name { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

记下您要转换的 ModStamp 列。

然后将 EF 创建这样的代码添加到 Partial.cs 文件中,注意我不知道你真正想用 ModStamp 值做什么:

public static class ModConverter
{
    public static string ToModStampString(byte[] modStamp)
    {
        return BitConverter.ToString(modStamp);
    }
}

public partial class Company
{
    public string ModStampString 
    {
        get
        {
            return ModConverter.ToModStampString(this.ModStamp);
        }
    }
}

然后,您必须手动为每个具有 ModStamp 列的实体添加一个新的 ModStampString 获取属性,就像我为公司实体所做的那样。

【讨论】:

    【解决方案4】:

    这是一个解决方案,它使用 DbContext 和表达式树上的 Set 方法来动态查询该对象。

    private Expression<Func<TArg, bool>> CreatePredicate<TArg, TPredicateField>(string fieldName, TPredicateField value)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(TArg), "o");
    
        MemberExpression memberExpression = Expression.Property(parameter, fieldName);
    
        var condition = Expression.Equal(memberExpression, Expression.Constant(value));
        var lambda = Expression.Lambda<Func<TArg, bool>>(condition, parameter);
    
        return lambda;
    }
    
    private Expression<Func<TArg, TPredicateField>> CreateSelector<TArg, TPredicateField>(string fieldName)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(TArg), "o");
        Expression propertyExpr = Expression.Property(parameter, fieldName);
    
        var lambda = Expression.Lambda<Func<TArg, TPredicateField>>(propertyExpr, parameter);
    
        return lambda;
    }
    
    public TSelectorField GetModStamp<TEntity, TPredicateField, TSelectorField>(TPredicateField id) where TEntity : class
    {
        using (var ctx = new OnTheFlyEntities("Data Source=(local);Initial Catalog=AscensionBO;Integrated Security=True;MultipleActiveResultSets=True"))
        {
            var predicate = CreatePredicate<TEntity, TPredicateField>("Id", id);
            var selector = CreateSelector<TEntity, TSelectorField>("ModStamp");
    
            TSelectorField item = ctx.Set<TEntity>().Where(predicate).Select(selector).SingleOrDefault();
    
            return item;
        }
    }
    

    你可以这样称呼它:

    GetModStamp<Entity2, int, string>(1)
    

    如果您愿意只返回找到的实体,您可以消除 TSelectorField,然后在检索到项目后从项目中获取 ModStamp。这将删除表达式树方法之一和主方法上的通用输入。

    按照其他人的建议,您可以使用接口路线并使用该示例,它会简单得多。

    【讨论】:

    • 如上 Set() 给出编译错误。 SET() 没有“where”扩展。 EF 6.0(如果组件版本为 4.0)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-27
    • 1970-01-01
    • 2022-12-13
    相关资源
    最近更新 更多