【问题标题】:C# LINQ to SQL: Refactoring this Generic GetByID methodC# LINQ to SQL:重构此通用 GetByID 方法
【发布时间】:2009-04-09 17:24:04
【问题描述】:

我写了下面的方法。

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

基本上它是泛型类中的一个方法,其中T 是DataContext 中的一个类。

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

问题是我必须先将元素表转换为列表才能在属性上执行GetType,但这不是很方便,因为表中的所有元素都必须枚举并转换为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; }
}

DataContext [查看代码]:

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

通用方法:

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

在这一行抛出异常:return table.SingleOrDefault(e =&gt; e.ID.Equals(id));,异常是:

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

[更新]解决方案:

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

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

【问题讨论】:

  • 您无需担心设计器生成的文件或 edmx 设计器过度编写它们。您无需在设计器文件中实现接口。您将为实现接口的实体。
  • 但这意味着我应该让每个“db类”都实现这个接口,不是吗?
  • 是的,它会,但这是一次性的工作,然后你的代码会更加健壮。
  • GetPrimaryKey 方法有点狡猾,linq to sql 并不总是使用属性来解释映射,您可以完全使用 dbml.. 但是无论您使用什么,在 Mappings 定义示例中都是相同的Denis Troller 给的。
  • 嗯谢谢你的建议;会调查的

标签: c# linq-to-sql generics expression-trees


【解决方案1】:

你需要构建一个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();
}

这应该可以解决问题。无耻地从this blog借来的。 这基本上是 LINQ to SQL 在您编写类似查询时所做的事情

var Q = from t in Context.GetTable<T)()
        where t.id == id
        select t;

您只需为 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;
}

【讨论】:

  • 克服不同字段名称的解决方案是什么?
  • 就像自动一样,不在子类的抽象参数中指定它们或任何需要进一步维护的东西
  • 您可以尝试从我认为的 DataContext 的 MappingSource 中提取它。让我看看……
  • 找到了解决方案!我正在使用他在 MVc 开源网站中的 GetPrimaryKey() 扩展方法。稍后会发布解决方案
  • 我没有在他的帖子中看到它,所以我把它去掉了。这是一个可能的实现。
【解决方案2】:

如果你修改它以使用 GetTable().Where(...),然后把你的过滤放在那里呢?

这样会更有效,因为 Where 扩展方法应该比将整个表提取到列表中更好地处理您的过滤。

【讨论】:

    【解决方案3】:

    一些想法...

    只需删除 ToList() 调用,SingleOrDefault 可以与我认为表是的 IEnumerably 一起使用。

    缓存对 e.GetType().GetProperties().First() 的调用以获取返回的 PropertyInfo。

    你不能只给 T 添加一个约束,迫使他们实现一个暴露 Id 属性的接口吗?

    【讨论】:

      【解决方案4】:

      也许执行查询可能是个好主意。

      public static T GetByID(int id)
          {
              Type type = typeof(T);
              //get table name
              var att = type.GetCustomAttributes(typeof(TableAttribute), false).FirstOrDefault();
              string tablename = att == null ? "" : ((TableAttribute)att).Name;
              //make a query
              if (string.IsNullOrEmpty(tablename))
                  return null;
              else
              {
                  string query = string.Format("Select * from {0} where {1} = {2}", new object[] { tablename, "ID", id });
      
                  //and execute
                  return dbcontext.ExecuteQuery<T>(query).FirstOrDefault();
              }
          }
      

      【讨论】:

      • 列名因表而异
      • 好的,我不知道主键列的名称会有所不同。我看到已经有一个获取主键列名的解决方案。问候
      【解决方案5】:

      关于:

      System.NotSupportedException:成员“MusicRepo_DataContext.IHasID.ID”没有支持的 SQL 转换。

      解决初始问题的简单方法是指定一个表达式。见下文,它对我来说就像一个魅力。

      public interface IHasID
      {
          int ID { get; set; }
      }
      DataContext [View Code]:
      
      namespace MusicRepo_DataContext
      {
          partial class Artist : IHasID
          {
              [Column(Name = "ArtistID", Expression = "ArtistID")]
              public int ID
              {
                  get { return ArtistID; }
                  set { throw new System.NotImplementedException(); }
              }
          }
      }
      

      【讨论】:

        【解决方案6】:

        好的,检查这个演示实现。尝试使用 datacontext(Linq To Sql)获取通用 GetById。还兼容多键属性。

        using System;
        using System.Data.Linq;
        using System.Data.Linq.Mapping;
        using System.Linq;
        using System.Reflection;
        using System.Collections.Generic;
        
        public static class Programm
        {
            public const string ConnectionString = @"Data Source=localhost\SQLEXPRESS;Initial Catalog=TestDb2;Persist Security Info=True;integrated Security=True";
        
            static void Main()
            {
                using (var dc = new DataContextDom(ConnectionString))
                {
                    if (dc.DatabaseExists())
                        dc.DeleteDatabase();
                    dc.CreateDatabase();
                    dc.GetTable<DataHelperDb1>().InsertOnSubmit(new DataHelperDb1() { Name = "DataHelperDb1Desc1", Id = 1 });
                    dc.GetTable<DataHelperDb2>().InsertOnSubmit(new DataHelperDb2() { Name = "DataHelperDb2Desc1", Key1 = "A", Key2 = "1" });
                    dc.SubmitChanges();
        
                    Console.WriteLine("Name:" + GetByID(dc.GetTable<DataHelperDb1>(), 1).Name);
                    Console.WriteLine("");
                    Console.WriteLine("");
                    Console.WriteLine("Name:" + GetByID(dc.GetTable<DataHelperDb2>(), new PkClass { Key1 = "A", Key2 = "1" }).Name);
                }
            }
        
            //Datacontext definition
            [Database(Name = "TestDb2")]
            public class DataContextDom : DataContext
            {
                public DataContextDom(string connStr) : base(connStr) { }
                public Table<DataHelperDb1> DataHelperDb1;
                public Table<DataHelperDb2> DataHelperD2;
            }
        
            [Table(Name = "DataHelperDb1")]
            public class DataHelperDb1 : Entity<DataHelperDb1, int>
            {
                [Column(IsPrimaryKey = true)]
                public int Id { get; set; }
                [Column]
                public string Name { get; set; }
            }
        
            public class PkClass
            {
                public string Key1 { get; set; }
                public string Key2 { get; set; }
            }
            [Table(Name = "DataHelperDb2")]
            public class DataHelperDb2 : Entity<DataHelperDb2, PkClass>
            {
                [Column(IsPrimaryKey = true)]
                public string Key1 { get; set; }
                [Column(IsPrimaryKey = true)]
                public string Key2 { get; set; }
                [Column]
                public string Name { get; set; }
            }
        
            public class Entity<TEntity, TKey> where TEntity : new()
            {
                public static TEntity SearchObjInstance(TKey key)
                {
                    var res = new TEntity();
                    var targhetPropertyInfos = GetPrimaryKey<TEntity>().ToList();
                    if (targhetPropertyInfos.Count == 1)
                    {
                        targhetPropertyInfos.First().SetValue(res, key, null);
                    }
                    else if (targhetPropertyInfos.Count > 1) 
                    {
                        var sourcePropertyInfos = key.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
                        foreach (var sourcePi in sourcePropertyInfos)
                        {
                            var destinationPi = targhetPropertyInfos.FirstOrDefault(x => x.Name == sourcePi.Name);
                            if (destinationPi == null || sourcePi.PropertyType != destinationPi.PropertyType)
                                continue;
        
                            object value = sourcePi.GetValue(key, null);
                            destinationPi.SetValue(res, value, null);
                        }
                    }
                    return res;
                }
            }
        
            public static IEnumerable<PropertyInfo> GetPrimaryKey<T>()
            {
                foreach (var info in typeof(T).GetProperties().ToList())
                {
                    if (info.GetCustomAttributes(false)
                    .Where(x => x.GetType() == typeof(ColumnAttribute))
                    .Where(x => ((ColumnAttribute)x).IsPrimaryKey)
                    .Any())
                        yield return info;
                }
            }
            //Move in repository pattern
            public static TEntity GetByID<TEntity, TKey>(Table<TEntity> source, TKey id) where TEntity : Entity<TEntity, TKey>, new()
            {
                var searchObj = Entity<TEntity, TKey>.SearchObjInstance(id);
                Console.WriteLine(source.Where(e => e.Equals(searchObj)).ToString());
                return source.Single(e => e.Equals(searchObj));
            }
        }
        

        结果:

        SELECT [t0].[Id], [t0].[Name]
        FROM [DataHelperDb1] AS [t0]
        WHERE [t0].[Id] = @p0
        
        Name:DataHelperDb1Desc1
        
        
        SELECT [t0].[Key1], [t0].[Key2], [t0].[Name]
        FROM [DataHelperDb2] AS [t0]
        WHERE ([t0].[Key1] = @p0) AND ([t0].[Key2] = @p1)
        
        Name:DataHelperDb2Desc1
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-05-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多