问题:我们广泛使用存储库模式,以便跨多个应用程序和功能的子部分在我们的数据存储区(使用LINQ的MS SQL)上进行读/写操作。我们有一系列方法都可以做彼此相似的事情。
例如,我们有ProcessAndSortXXXXX类方法。
private static IEnumerable<ClassErrorEntry> ProcessAndSortClassErrorLog(IQueryable<ClassErrorDb> queryable, string sortOrder)
{
var dynamic = queryable;
if (!String.IsNullOrEmpty(sortOrder.Trim()))
{
dynamic = dynamic.OrderBy(sortOrder);
}
return dynamic
.Select(l =>
new ClassErrorEntry(l.Id)
{
ClassId = l.ClassId,
Code = l.Code,
Message = l.Message,
Severity = l.Severity,
Target = l.Target
}
);
}
...和...
private static IEnumerable<ClassTimerLogEntry> ProcessAndSortClassTimerLog(IQueryable<ClassTimerDb> queryable, string sortOrder)
{
var dynamic = queryable;
if (!String.IsNullOrEmpty(sortOrder.Trim()))
{
dynamic = dynamic.OrderBy(sortOrder);
}
return dynamic
.Select(l =>
new ClassTimerLogEntry(l.Id)
{
ClassName = l.ClassName,
MethodName = l.MethodName,
StartTime = l.StartTime,
EndTime = l.EndTime,
ParentId = l.ParentId,
ExecutionOrder = l.ExecutionOrder
}
);
}
正如您可以通过代码看到的那样,它们都非常相似,直到您查看签名然后到达返回语句,我们正在构建ClassErrorEntry和ClassTimerLogEntry的实例。
我想构建一个实用程序方法,我将其添加到所有存储库继承的基类中。
我希望能够传入可用于实例化对象的参数,并将它们打包到返回的IEnumerable中。
我在this post找到了ScottGu,这让我得到了我所需要的大部分内容。它看起来像这样(来自文档中的示例):
var query =
db.Customers.
Where("City = @0 and Orders.Count >= @1", "London", 10).
OrderBy("CompanyName").
Select("new(CompanyName as Name, Phone)");
但这是我被卡住的地方。我需要一个指针或建议如何以通用方式传递LINQ表和DataContext,以便我可以构建动态查询。
如果我以伪代码模拟签名,我认为它看起来像这样:
protected internal IEnumerable ProcessAndSort(IQueryable source, string selectClause, string whereClause, string orderByClause);
我意识到完成的签名可能看起来不同,因为我们想出来了。
谢谢!
更新!
我现在有代码可以生成匿名类型,但在转换为具体类型时失败。
public static IEnumerable<TResult> ProcessAndSort<T, TResult>(IQueryable<T> queryable,
string selector, Expression<Func<T, bool>> predicate, string sortOrder)
{
var dynamic = queryable.Where(predicate).AsQueryable();
if (!String.IsNullOrEmpty(sortOrder.Trim()))
{
dynamic = dynamic.OrderBy(sortOrder);
}
var result= dynamic.Select(selector).Cast<TResult>();
return result;
}
以下是调用此方法的代码:
[TestMethod]
public void TestAnonymousClass()
{
var loggingContext = new LoggingDbDataContext(DatabaseConnectionString);
var repo = new LoggingRepository(loggingContext);
var result = repo.TestGetClassErrorLog(4407, 10, 0,
"new ( ClassId as ClassId, " +
"Code as Code, " +
"Message as Message, " +
"Severity as Severity, " +
"Target as Target )", "Target");
TestContext.WriteLine(result.ToList().Count.ToString());
}
最后一行TestContext.WriteLine(result.ToList().Count.ToString());
抛出异常System.InvalidOperationException: No coercion operator is defined between types 'DynamicClass1' and 'Utilities.Logging.ClassErrorEntry'.
这段代码虽然失败了:
[TestMethod]
public void TestNamedClass()
{
var loggingContext = new LoggingDbDataContext(DatabaseConnectionString);
var repo = new LoggingRepository(loggingContext);
var result = repo.TestGetClassErrorLog(4407, 10, 0,
"new ClassErrorEntry(Id) { ClassId = ClassId, " +
"Code = Code, " +
"Message = Message, " +
"Severity = Severity, " +
"Target = Target }", "Target");
TestContext.WriteLine(result.ToList().Count.ToString());
}
解析错误失败。 Test method eModal.Repositories.Test.RepositoryBaseTest.TestConcreteClass threw exception:
System.Linq.Dynamic.ParseException: '(' expected, found 'ClassErrorEntry' ('Identifier') at char 19 in 'new ClassErrorEntry(Id) { ChassisAuthId = ChassisAuthId, Code = Code, Message = Message, Severity = Severity, Target = Target }'
我不确定第19个字符位置是(
时字符位置是否可疑,并且传入Validate方法的类型表示位置为4,或者第一个'C'
。
答案 0 :(得分:1)
我完全建议你反对为了代码重用而制作弱类型查询。
代码重用是为了提高可维护性,但如果以错误的方式使用,弱类型可能会导致代码重用。
通过以纯文本形式编写查询,您可以有效地使类很难重构和更改,并引入许多模糊的依赖项。
我建议您查看允许合并Expression
的{{3}}。例如,我们编写了一个Paging
方法,该方法按页面拆分查询,并在不同类型的项目中使用它:
var query = CompiledQuery.Compile(
BuildFolderExpr( folder, false )
.Select( msg => selector.Invoke( msg, userId ) ) // re-use selector expression
.OrderBy( mv => mv.DateCreated, SortDirection.Descending )
.Paging() // re-use paging expression
.Expand() // LinqKit method that "injects" referenced expressions
)
public static Expression<Func<T1, T2, PagingParam, IQueryable<TItem>>> Paging<T1, T2, TItem>(
this Expression<Func<T1, T2, IQueryable<TItem>>> expr )
{
return ( T1 v1, T2 v2, PagingParam p ) => expr.Invoke( v1, v2 ).Skip( p.From ).Take( p.Count );
}
在我的示例中,BuildMessageExpr
返回一个相对简单的select表达式(已经依赖于folder
和另一个参数),并且不同的方法通过应用过滤,排序,获取计数,进一步选择来重用此表达式将选择器表达式作为参数传递,等等。创建查询后,当参数相似时,它将被缓存以供将来使用。
答案 1 :(得分:0)
这不是你问题的直接答案。
正如你所说,你有相当多的代码看起来相似,但返回不同的类型。如果你继续寻找这种方法的通用实现,结果可能会有一些黑客,你可能仍然传递一些不舒服的SQL或检查对象的类型或做一些反射kung-fu。你仍然可以选择这个传球,实际上有人可以有一个看起来不像脏黑客的明智想法。
另一种选择是使用具有通用存储库模式和依赖注入(google link)的正确ORM。您的数据访问层看起来会更简单,更易于维护。