C#LINQ to SQL:重构此通用GetByID方法

And*_*ech 21 c# generics expression-trees linq-to-sql

我写了以下方法.

public T GetByID(int id)
{
    var dbcontext = DB;
    var table = dbcontext.GetTable<T>();
    return table.ToList().SingleOrDefault(e => Convert.ToInt16(e.GetType().GetProperties().First().GetValue(e, null)) == id);
}
Run Code Online (Sandbox Code Playgroud)

基本上它是Generic类中的一个方法,它T是DataContext中的一个类.

该方法从T(GetTable)类型获取表,并检查输入参数的第一个属性(始终是ID).

这个问题是我必须首先将元素表转换为列表以GetType在属性上执行a ,但这不是很方便,因为必须枚举表的所有元素并将其转换为a List.

我怎样才能重构这个方法以避免ToList整个表格?

[更新]

我无法Where直接在表上执行的原因是因为我收到此异常:

方法'System.Reflection.PropertyInfo [] GetProperties()'没有支持的SQL转换.

因为GetProperties无法翻译成SQL.

[更新]

有些人建议使用T接口,但问题是该T参数将是[DataContextName] .designer.cs中自动生成的类,因此我无法使其实现接口(并且它不可行实现LINQ的所有这些"数据库类"的接口;并且,一旦我向DataContext添加新表,就会重新生成文件,从而丢失所有写入的数据).

所以,必须有一个更好的方法来做到这一点......

[更新]

我现在已经按照Neil Williams的建议实现了我的代码,但我仍然遇到问题.以下是代码的摘录:

接口:

public interface IHasID
{
    int ID { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

DataContext [查看代码]:

namespace MusicRepo_DataContext
{
    partial class Artist : IHasID
    {
        public int ID
        {
            get { return ArtistID; }
            set { throw new System.NotImplementedException(); }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

通用方法:

public class DBAccess<T> where T :  class, IHasID,new()
{
    public T GetByID(int id)
    {
        var dbcontext = DB;
        var table = dbcontext.GetTable<T>();

        return table.SingleOrDefault(e => e.ID.Equals(id));
    }
}
Run Code Online (Sandbox Code Playgroud)

在这一行上抛出return table.SingleOrDefault(e => e.ID.Equals(id));了异常:例外是:

System.NotSupportedException: The member 'MusicRepo_DataContext.IHasID.ID' has no supported translation to SQL.

[更新]解决方案:

Denis Troller发布的答案和Code Rant博客帖子链接的帮助下,我终于找到了解决方案:

public static PropertyInfo GetPrimaryKey(this Type entityType)
{
    foreach (PropertyInfo property in entityType.GetProperties())
    {
        ColumnAttribute[] attributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), true);
        if (attributes.Length == 1)
        {
            ColumnAttribute columnAttribute = attributes[0];
            if (columnAttribute.IsPrimaryKey)
            {
                if (property.PropertyType != typeof(int))
                {
                    throw new ApplicationException(string.Format("Primary key, '{0}', of type '{1}' is not int",
                                property.Name, entityType));
                }
                return property;
            }
        }
    }
    throw new ApplicationException(string.Format("No primary key defined for type {0}", entityType.Name));
}

public T GetByID(int id)
{
    var dbcontext = DB;

    var itemParameter = Expression.Parameter(typeof (T), "item");
    var whereExpression = Expression.Lambda<Func<T, bool>>
        (
        Expression.Equal(
            Expression.Property(
                 itemParameter,
                 typeof (T).GetPrimaryKey().Name
                 ),
            Expression.Constant(id)
            ),
        new[] {itemParameter}
        );
    return dbcontext.GetTable<T>().Where(whereExpression).Single();
}
Run Code Online (Sandbox Code Playgroud)

Den*_*ler 18

您需要的是构建LINQ to SQL可以理解的表达式树.假设您的"id"属性始终命名为"id":

public virtual T GetById<T>(short id)
{
    var itemParameter = Expression.Parameter(typeof(T), "item");
    var whereExpression = Expression.Lambda<Func<T, bool>>
        (
        Expression.Equal(
            Expression.Property(
                itemParameter,
                "id"
                ),
            Expression.Constant(id)
            ),
        new[] { itemParameter }
        );
    var table = DB.GetTable<T>();
    return table.Where(whereExpression).Single();
}
Run Code Online (Sandbox Code Playgroud)

这应该可以解决问题.它是从这个博客无耻地借来的.这基本上是LINQ to SQL在编写查询时所执行的操作

var Q = from t in Context.GetTable<T)()
        where t.id == id
        select t;
Run Code Online (Sandbox Code Playgroud)

你只是为LTS做的工作,因为编译器不能为你创建,因为没有什么可以强制T具有"id"属性,并且你不能将任何"id"属性从接口映射到数据库.

====更新====

好的,这是一个简单的实现,用于查找主键名称,假设只有一个(不是复合主键),并假设所有类型都很好(也就是说,您的主键与"短"类型兼容)在GetById函数中使用):

public virtual T GetById<T>(short id)
{
    var itemParameter = Expression.Parameter(typeof(T), "item");
    var whereExpression = Expression.Lambda<Func<T, bool>>
        (
        Expression.Equal(
            Expression.Property(
                itemParameter,
                GetPrimaryKeyName<T>()
                ),
            Expression.Constant(id)
            ),
        new[] { itemParameter }
        );
    var table = DB.GetTable<T>();
    return table.Where(whereExpression).Single();
}


public string GetPrimaryKeyName<T>()
{
    var type = Mapping.GetMetaType(typeof(T));

    var PK = (from m in type.DataMembers
              where m.IsPrimaryKey
              select m).Single();
    return PK.Name;
}
Run Code Online (Sandbox Code Playgroud)