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

我写了以下方法。

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

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

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

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

如何重构此方法以避免整个表上的ToList

[更新]

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

方法’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 where T : class, IHasID,new() { public T GetByID(int id) { var dbcontext = DB; var table = dbcontext.GetTable(); return table.SingleOrDefault(e => e.ID.Equals(id)); } } 

在这一行抛出exception: 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博客上post的链接的帮助下,我终于找到了解决方案:

 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> ( Expression.Equal( Expression.Property( itemParameter, typeof (T).GetPrimaryKey().Name ), Expression.Constant(id) ), new[] {itemParameter} ); return dbcontext.GetTable().Where(whereExpression).Single(); } 

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

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

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

 var Q = from t in Context.GetTable 

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

====更新====

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

 public virtual T GetById(short id) { var itemParameter = Expression.Parameter(typeof(T), "item"); var whereExpression = Expression.Lambda> ( Expression.Equal( Expression.Property( itemParameter, GetPrimaryKeyName() ), Expression.Constant(id) ), new[] { itemParameter } ); var table = DB.GetTable(); return table.Where(whereExpression).Single(); } public string GetPrimaryKeyName() { var type = Mapping.GetMetaType(typeof(T)); var PK = (from m in type.DataMembers where m.IsPrimaryKey select m).Single(); return PK.Name; } 

如果你重做这个以使用GetTable()。在哪里(…),并将你的过滤放在那里怎么办?

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

一些想法……

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

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

你不能只为T添加一个约束来强制它们实现暴露Id属性的接口吗?

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

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

关于:

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

好的,请查看此演示实现。 尝试使用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().InsertOnSubmit(new DataHelperDb1() { Name = "DataHelperDb1Desc1", Id = 1 }); dc.GetTable().InsertOnSubmit(new DataHelperDb2() { Name = "DataHelperDb2Desc1", Key1 = "A", Key2 = "1" }); dc.SubmitChanges(); Console.WriteLine("Name:" + GetByID(dc.GetTable(), 1).Name); Console.WriteLine(""); Console.WriteLine(""); Console.WriteLine("Name:" + GetByID(dc.GetTable(), 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; public Table DataHelperD2; } [Table(Name = "DataHelperDb1")] public class DataHelperDb1 : Entity { [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 { [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 where TEntity : new() { public static TEntity SearchObjInstance(TKey key) { var res = new TEntity(); var targhetPropertyInfos = GetPrimaryKey().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 GetPrimaryKey() { 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(Table source, TKey id) where TEntity : Entity, new() { var searchObj = Entity.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