重构包含LINQ查询的方法

6

我有些困难,无法决定如何最好地重构一个方法,这个方法包含了几乎相似但并不完全相同的 LINQ 查询。

考虑一个类似以下代码的方法:

public SomeObject GetTheObject(IMyObject genericObject) {
    Type t = genericObject.GetType();
    SomeObject so = null;

    switch(t.Name) {
        case "Type1":
            var object1 = (from o in object1s where o.object1id == genericObject.id).FirstOrDefault();
            so = (SomeObject)object1;
        break;
        case "Type2":
            var object2 = (from o in object2s where o.object2id == genericObject.id).FirstOrDefault();
            so = (SomeObject)object2;
        break;
        default:
        break;
    }

    return so;
}

这只是一个例子,但是想象一下我需要执行另一个查询(不同之处在于它使用不同的ObjectSet,使用略有不同的字段(object1id vs object2id)并返回不同的类型。除此之外,这些查询是相同的。
除此之外,这种方法有没有明智的重构方式?感觉好像我错过了什么显而易见的东西。也许我必须使用确切的方法,无法避免重新编写查询,只是似乎我应该可以以某种方式做到!任何指针都将不胜感激。

我尝试使用反射,但无法通过LINQ语句(“from o in object1s where o.object1id”)。您应该考虑动态生成LINQ语句。 - Graham
嗨,Graham,这肯定是一个选项,但事实上我正在尝试将DAL封装在Repository<T>中,这使我受限于可以用来动态构建查询的方法。我尝试构建一个工厂来返回我想要的具体Repository实例。但这让我陷入了与我向Paolo描述的类似的境地,即因为我的Repository需要一个具体的EntityObject类型,所以我无法基于接口创建一个。 - dougajmcdonald
1个回答

4
也许你刚刚过于简化了你的场景,但是函数中臭味十足的部分是对SomeObject的强制转换。你可不可以只使用接口,在调用处(如果需要)进行强制转换?例如,你可以让Type1和Type2实现一个公共接口,其中id1和id2作为id暴露出来(或者如果你不能控制Type1和Type2,就进行装饰)。
即:
public static IMyObject GetTheObject(List<IMyObject> theList,  int id)
{
    var ret = (from o in theList
        where o.id==id
        select o).FirstOrDefault();

    return ret;
}

例如,如果您有以下内容:
    public interface IMyObject {int id {get;}}

    public class Foo : IMyObject {public int id {get; set;}}
    public class Bar : IMyObject {public int id {get; set;}}

你可以做以下事情:

var l1 = new List<IMyObject>(){new Foo(){id=1}, new Foo(){id=2}};
var l2 = new List<IMyObject>(){new Bar(){id=1}, new Bar(){id=2}};   

var obj1 = Test.GetTheObject(l1, 1);
var obj2 = Test.GetTheObject(l2, 2);

在调用函数后,如果需要,可以将对象进行转换。
编辑: 如果你被具体的对象和转换卡住了,我能想到的最好重构方案是:
public static SomeObject GetTheObject(IMyObject genericObject) {
    Type t = genericObject.GetType();

    Func<SomeObject, bool> WhereClause = null;
    IEnumerable<SomeObject> objs = null; // IEnumerable<T> is covariant, 
                      // so we can assign it both an IEnumerable<object1>
                      // and an IEnumerable<object2> (provided object1 and 2 are
                      // subclasses of SomeObject)

    switch(t.Name) {
        case "Type1":
            WhereClause = o => ((Object1)o).object1id == genericObject.id;      
            objs = object1s;
        break;
        case "Type2":
            WhereClause = o =>  ((Object2)o).object2id == genericObject.id;     
            objs = object2s;
        break;
    }

    var ob = objs
    .Where(WhereClause)
    .FirstOrDefault();

    return (SomeObject)ob;
}

是的,我认为我有点简化了事情。我使用接口的问题在于,我的查询实际上是对一个通用存储库的查询,它需要一个具体类型。这意味着我目前无法执行Respository<IMyObject>,这让我很苦恼。 我可以写另一个存储库,其中T:IMyObject,而不是目前的T:EntityObject,但这感觉像是过度设计,但可能确实需要。 - dougajmcdonald
@dougajmcdonald:我能想到的重构并不比你的改进更好,但至少它减少了linq代码中的重复... :) - Paolo Falabella
谢谢你的建议,星期四我有时间会试一下。我希望能够避免使用切换逻辑,因为在我的实际情况中可能会有10-12个选项,我不想用case语句来占据屏幕空间!但也许这是唯一的选择! - dougajmcdonald

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接