Search This Blog

Loading...

Friday, January 16, 2009

Is LINQ Becoming a Monster?

I love LINQ . It gives the developers the power to query databases and lists in a manner far more succint, readable and elegant than that could otherwise be obtained via traditional looping methods ( you know, the combination kinds of for, if ) that normally result in lines and lines of code, methods and sub-methods calling. LINQ combines the succintness of SQL queries with the compile-time type checking advantage offered by C#, and produces code that is far more intuitive and readable than SQL or C# custom tailored filtering functions.

But after using it for quite sometime, I found that LINQ could be a bit prohibitive. Prohibitive in the sense that you always have to write LINQ expressions that are big, complicated, ugly and undecipherable at times. 

Take a look at the below queries, see how much you can understand out of it?



public DateTime MyDateTime(IEnumerable<timedb.timerecordrow> relatedForm,
DateTime lastDateTime)
{
DateTime dt1 = relatedForm.OrderBy(record => record.EnterTime).FirstOrDefault(record =>
record.EnterTime > lastDateTime).
EnterTime;
DateTime dt2 = relatedForm
.Where(record => !record.IsLeaveTimeNull())
.OrderBy(record => record.LeaveTime)
.FirstOrDefault(record =>record.LeaveTime > lastDateTime).
LeaveTime;

if (dt1 >= dt2)
return dt2;
else
{
return dt1;
}

}
public List<actioninfo> PullLogRecord()
{

var descriptionDB = ActionDescriptionAdapter.GetData();
var timeDB = TimeRecordAdapter.GetData();

var groupByAction = timeDB.GroupBy(e => e.UserActionReference);
var groupByActionAndCurrElement = groupByAction.Select
(
group=>new {Key = descriptionDB
.Where(record=>record.UserAction==group.Key)
.Select(record=>record.Description).Single(),
NestedGroup= group.ToLookup
(
result=>result.CurrElement,
result=>new TimerInformation()
{
EnterTime = result.EnterTime,
LeaveTime = result.IsLeaveTimeNull() ?
MyDateTime(timeDB.ToList(), result.EnterTime) :
result.LeaveTime
}

)}
);
var dictionary = groupByActionAndCurrElement.ToDictionary
(
result => result.Key,
result => result.NestedGroup
);

List<actioninfo> actionInfos=new List<actioninfo>();
foreach (string key in dictionary.Keys)
{
ActionInfo ai = new ActionInfo(){ActionName = key};


foreach (var pair in dictionary[key])
{
ai.ElementInfo.Add(new ElementInfo()
{
ElementName = pair.Key,TimingInfo = pair.ToList()
});
}
actionInfos.Add(ai);

}
return actionInfos;
}


It's awfully long. But what it does is relatively simple:
  1. Pull records from a data table
  2. Group the data in terms of description and CurrElement tables. 
  3. After the grouping,  get each of the EnterTime and LeaveTime for each record and put them in a list, and put the list into an ElementInfo object,  and so on. If the LeaveTime is a null, then it will find the first record that immediately after the corresponding EnterTime

With such a long query, debugging it becomes very difficult. I have tried to break it into smaller methods, but so far only succeeded in spinning off one method. The whole query is still can't fit into a page. It's still a pain to read.

If I were to write my own C# code, although I might need more lines to do it, but at least I could write sub-functions to make things easier to understand. But with LINQ... that's the best I could produce, given my limited time and relatively low exposure in it.

Is there a way to improve on this?

10 comments:

Anonymous said...

Uncle Ben: Remember, with great power. comes great responsibility.

Anonymous said...

dude..use stored procedures, linq can get ugly.. just cuz M$ marketing tells you to use it?

dont

Anonymous said...

@stored procedure anonymous

stored procedures your ass, just cuz DB vendor$ tells you to use it?

dont

Anonymous said...

Relational DBs are incompatible with the rest of the design decisions of most projects. Complexity is what you get when you have to transform an airplane into a flying-rock and back.

Go CoucgDB, Db4o, Objectivity...

Anonymous said...

"If I were to write my own C# code"

---

Is that the only option you have considered?

I recommend iBATIS!

With iBATIS you use SQL that can be specified in separate XML-files. Through the iBATIS API you can then make sure your C# code will only receive a collection of structures containing only your EnterTime and LeaveTime values. All information about your tables and group-by clause are isolated in the SQL.

The advantage here is that you use actual SQL, which can easily be debugged with your choice of query-analyser.

IMO iBATIS is clearly better than LINQ when you want to query the database for information but does not care much for the table-structure. In this case you want EnterTime and LeaveTime but you have no interest in letting your C# know about "description and CurrElement tables".

iBATIS is free and available at this url:
http://ibatis.com

Dan Howard said...

Rewrite that query using plain SQL and compare the difference. The SQL will be shorter and easier to understand. LINQ is a solution looking for a problem.

Alaa Salman said...

Grouping in any ORM(linq to sql) will probably turn out to be too complex given how an ORM is designed. Did you try to write the query in sql and executing it directly? Maybe it would be simpler. I believe the function to look for is executequery.

Otherwise, i do agree that linq is certainly not a tool for every occasion.

Soon Hui said...

Salman,

I didn't try to create the query in store procedure form, it might be better, maybe

Anonymous said...

Refactor?

Anonymous said...

I'm trying to get into linq -- only because I have to keep up, but the back of my mind is always screaming: why? why? why?

Why add another layer of complexity by adding another language layer on top of SQL?

Analogy: I already communicate well with my wife (SQL). So if I now have to use a (an unproven) translator to talk to her, how is this better communication?

Analogy answer: ... Well you'd need a robotic helper if you switch wives or have more than one ...

Analogy counter-answer: Ain't gonna happen. But if it did, I'd talk to them directly, so that there won't be misunderstandings, and there'd be less time lag between responses.