简体   繁体   English

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

[英]C# LINQ to SQL: Refactoring this Generic GetByID method

I wrote the following method. 我写了以下方法。

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

Basically it's a method in a Generic class where T is a class in a DataContext. 基本上它是Generic类中的一个方法,其中T是DataContext中的一个类。

The method gets the table from the type of T ( GetTable ) and checks for the first property (always being the ID) to the inputted parameter. 该方法从T( GetTable )的类型获取表,并检查输入参数的第一个属性(始终是ID)。

The problem with this is I had to convert the table of elements to a list first to execute a GetType on the property, but this is not very convenient because all the elements of the table have to be enumerated and converted to a List . 这个问题是我必须首先将元素表转换为列表以在属性上执行GetType ,但这不是很方便,因为必须枚举表的所有元素并将其转换为List

How can I refactor this method to avoid a ToList on the whole table? 如何重构此方法以避免整个表上的ToList

[Update] [更新]

The reason I can't execute the Where directly on the table is because I receive this exception: 我无法直接在表上执行Where的原因是因为我收到此异常:

Method 'System.Reflection.PropertyInfo[] GetProperties()' has no supported translation to SQL. 方法'System.Reflection.PropertyInfo [] GetProperties()'没有支持的SQL转换。

Because GetProperties can't be translated to SQL. 因为GetProperties无法转换为SQL。

[Update] [更新]

Some people have suggested using an interface for T , but the problem is that the T parameter will be a class that is auto generated in [DataContextName].designer.cs , and thus I cannot make it implement an interface (and it's not feasible implementing the interfaces for all these "database classes" of LINQ; and also, the file will be regenerated once I add new tables to the DataContext, thus loosing all the written data). 有些人建议使用T接口,但问题是T参数将是[DataContextName] .designer.cs中自动生成的类,因此我无法使其实现接口(并且它不可行实现LINQ的所有这些“数据库类”的接口;并且,一旦我向DataContext添加新表,该文件将被重新生成,从而丢失所有写入的数据)。

So, there has to be a better way to do this... 所以,必须有一个更好的方法来做到这一点......

[Update] [更新]

I have now implemented my code like Neil Williams ' suggestion, but I'm still having problems. 我现在已经按照Neil Williams的建议实现了我的代码,但我仍然遇到问题。 Here are excerpts of the code: 以下是代码的摘录:

Interface: 接口:

public interface IHasID
{
    int ID { get; set; }
}

DataContext [View Code]: DataContext [查看代码]:

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

Generic Method: 通用方法:

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

The exception is being thrown on this line: return table.SingleOrDefault(e => e.ID.Equals(id)); 在这一行抛出异常: return table.SingleOrDefault(e => e.ID.Equals(id)); and the exception is: 例外是:

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

[Update] Solution: [更新]解决方案:

With the help of Denis Troller 's posted answer and the link to the post at the Code Rant blog , I finally managed to find a solution: 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();
}

What you need is to build an expression tree that LINQ to SQL can understand. 您需要的是构建LINQ to SQL可以理解的表达式树。 Assuming your "id" property is always named "id": 假设您的“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 should do the trick. 这应该可以解决问题。 It was shamelessly borrowed from this blog . 它是从这个博客无耻地借来的。 This is basically what LINQ to SQL does when you write a query like 这基本上是LINQ to SQL在编写查询时所执行的操作

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

You just do the work for LTS because the compiler cannot create that for you, since nothing can enforce that T has an "id" property, and you cannot map an arbitrary "id" property from an interface to the database. 你只是为LTS做的工作,因为编译器不能为你创建,因为没有什么可以强制T具有“id”属性,并且你不能将任何“id”属性从接口映射到数据库。

==== UPDATE ==== ====更新====

OK, here's a simple implementation for finding the primary key name, assuming there is only one (not a composite primary key), and assuming all is well type-wise (that is, your primary key is compatible with the "short" type you use in the GetById function): 好的,这是一个简单的实现,用于查找主键名称,假设只有一个(不是复合主键),并假设所有类型都很好(也就是说,您的主键与“短”类型兼容)在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;
}

What if you rework this to use GetTable().Where(...), and put your filtering there? 如果你重做这个以使用GetTable()。在哪里(...),并将你的过滤放在那里怎么办?

That would be more efficient, since the Where extension method should take care of your filtering better than fetching the entire table into a list. 这样会更有效,因为Where扩展方法应该比将整个表提取到列表中更好地处理过滤。

Some thoughts... 一些想法......

Just remove the ToList() call, SingleOrDefault works with an IEnumerably which I presume table is. 只需删除ToList()调用,SingleOrDefault就可以使用我认为表中的IEnumerably。

Cache the call to e.GetType().GetProperties().First() to get the PropertyInfo returned. 将调用缓存到e.GetType()。GetProperties()。First()以获取返回的PropertyInfo。

Cant you just add a constraint to T that would force them to implement an interface that exposes the Id property? 你不能只为T添加一个约束来强制它们实现暴露Id属性的接口吗?

Ok, check this demo implementation. 好的,请查看此演示实现。 Is attempt to get generic GetById with datacontext(Linq To Sql). 尝试使用datacontext(Linq To Sql)获取通用GetById。 Also compatible with multi key property. 兼容多键属性。

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

Result: 结果:

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

Maybe executing a query might be a good idea. 也许执行查询可能是个好主意。

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

Regarding: 关于:

System.NotSupportedException: The member 'MusicRepo_DataContext.IHasID.ID' has no supported translation to SQL. System.NotSupportedException:成员'MusicRepo_DataContext.IHasID.ID'没有支持的SQL转换。

The simple workaround to your initial problem is to specify an Expression. 初始问题的简单解决方法是指定表达式。 See below, it works like a charm for me. 见下文,它对我来说就像一个魅力。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM