|
|
|
|
 Sunday, August 26, 2007
|
A question many people have run into is: How does CRUD fit into LINQ-to-SQL? While LINQ-to-SQL (I'll abbreviate as DLINQ) provides a very fast and easy way for us to start querying our data, it doesn't handle updates as beautifully. It is particularly noticeable when you are doing multi-tier and hence cannot share a DataContext.
Let's consider an example database called "MembersDatabase" which has a table called "Accounts". Accounts has an int primary key, and a varchar Email field. We use the DLINQ designer and create the dbml that generates a class called MembersDataContext. How does our app-tier code look? I'll give you a hint, it starts with "ug" and rhymes with "nasty":
// Select int someId = 123; // Passed from another tier Account someAccount; // Can't use implicit typing -- no anonymous types using (var dc = new MembersDataContext()) { someAccount = dc.Accounts.SingleOrDefault(a => a.AccountId == someId); }
// Insert var myAccount = new Account(); myAccount.Email = "me@contoso.com"; using (var dc = new MembersDataContext()) { dc.Accounts.Add(myAccount); dc.SubmitChanges(); }
// Update int myId = 1; // Id and email passed from another tier string newEmail = "cool@cool.com"; var changedAccount = new Account(); changedAccount.AccountId = myId; changedAccount.Email = newEmail; using (var dc = new MembersDataContext()) { dc.Accounts.Attach(changedAccount, true); dc.SubmitChanges(); }
// Delete int idToKill = 2; // Passed from another tier using (var dc = new MembersDataContext()) { using (var txScope = new System.Transactions.TransactionScope()) { var acc = dc.Accounts.SingleOrDefault(a => a.AccountId == idToKill); if (acc == null) throw new ChangeConflictException("Row not found."); dc.Accounts.Remove(acc); dc.SubmitChanges(); txScope.Complete(); } }
It's not horrible; it's certainly better than anything before it. But, we can do better. I created some helper classes to do so (see attached file).
First, DatabaseBase<TContext>. This holds our tables, and provides DataContext helper functions Use and Query. Second, TableBase<TItem, TKey>. This actually provides our CRUD methods. I don't think anyone is overly interested in the implementation details (comment if I'm wrong), so here's how you declare your CRUD types:
class MembersDatabase : DatabaseBase<MembersDataContext> { public static readonly TableBase<Account, int> Accounts = CreateTable(dc => dc.Accounts, a => a.AccountId); }
That's it.
You create a new class to serve as your "database" class. All that's required here is to inherit from DatabaseBase.
Next, for each table, simply create a new field via CreateTable. The first parameter is a lambda function that selects the right table off the DataContext. The second parameter is a lambda expression that selects the primary key. Not much too it.
So, how does our previous chunk of code look with this small helper library?
// Select int someId = 123; // Passed from another tier var someAccount = MembersDatabase.Accounts.SelectByKey(someId);
// Insert var myAccount = new Account(); myAccount.Email = "me@contoso.com"; MembersDatabase.Accounts.Insert(myAccount);
// Update int myId = 1; // Id and email passed from another tier string newEmail = "me@me.com"; var changedAccount = new Account(); changedAccount.AccountId = myId; changedAccount.Email = newEmail; MembersDatabase.Accounts.Update(changedAccount);
// Delete int idToKill = 2; // Passed from another tier MembersDatabase.Accounts.Delete(idToKill);
That's about 40% less code. It’s far more straightforward, being a single block.
To start using this code, just drop DatabaseBase.cs into your project. It adds DatabaseBase to System.Data.Linq. Then subclass as shown above, and you're on your way to LINQ updating bliss. What do you think?
DatabaseBase.cs (5.65 KB)
P.S. At any rate, I should get points for the Zelda pun, right?
Update: I forgot to mention, you'll want to turn UpdateCheck to Never on your columns in the LINQ-to-SQL designer. Update: The code (including the Tuple class) for the .NET 3.5 RTM release is here: http://www.atrevido.net/blog/2008/06/26/LINQ+To+The+CRUD+RTM.aspx
|
|
Code
|
Sunday, August 26, 2007 8:30:32 PM UTC
|
Trackback
|
 Wednesday, August 22, 2007
|
What people do in their own time in the privacy of their homes is none of my business. However, when they mess with reading documentation, then it crosses the line and becomes annoying. How many times do VB developers need to be told that a null is "Nothing"? Consider this snippet from MSDN:
------- The CreateUser method will return a null reference (Nothing in Visual Basic) if password is an empty string or a null reference (Nothing in Visual Basic), username is an empty string or a null reference (Nothing in Visual Basic) or contains a comma (,), passwordQuestion is not a null reference (Nothing in Visual Basic) and contains an empty string, or passwordAnswer is not a null reference (Nothing in Visual Basic) and contains an empty string. -------
Five times in one paragraph! I know null type systems are annoying and lead to errors, but that seems a bit excessive. Seriously though, it'd make more sense to make VB developers learn a few words once, rather than having to mess up documentation just in case they get confused.
|
|
Code | Humour
|
Wednesday, August 22, 2007 2:49:34 PM UTC
|
Trackback
|
 Thursday, August 16, 2007
|
Be sure to read these first two articles:
Practical Functional C# - Part I
Practical Functional C# - Part II
OK let's start with a quick challenge. Write an accurate description of the following program, in English:
static void Main(string[] args) { string output = ""; if (args.Length > 0) output = args[0]; if (args.Length > 1) { for (int i = 1; i < args.Length; i++) { output += ", " + args[i]; } } Console.WriteLine(output); }
Well? The specification might be something like "a program that writes its arguments separated by a comma and space". But how quickly could you determine that from the code? How much additional time does it take to determine there aren't any bugs? Every time a maintenance developer comes across this code, they have to analyze this code, determine the boundary conditions correctly, verify the indexing, and so on. Every time someone reads this code, she must pay a high tax. The saddest part is that this is an extremely common pattern.
Edit: As Chad Hower pointed out in the comments that you can remove the two if statements by performing a check inside the loop. That shortens it considerably and reduces some of the "tax" that has to be paid when you read it.
"Take this set of values and aggregate them into a single value". How many pieces of code do exactly this, but obscure it behind a for loop? Functional languages realize this and provide functions called "Fold" or "Reduce". Such functions take an accumulator function, apply it to every element in the list, then return the accumulator value when finished. C# 3.0, courtesy of LINQ, provides an equivalent function, called "Aggregate":
static void Main(string[] args) { string output = args.DefaultIfEmpty("").Aggregate( (accum, item) => accum += ", " + item); Console.WriteLine(output); }
Here we are saying that we are going to aggregate args into a single string value. The lambda on the second line takes two arguments. The first is the accumulator and Aggregate passes it to each element ("threads" it through). The second parameter is the current item we are working with. The return value of our lambda is simply the concatenation of the current accumulated value, comma and space, and the current item. This return value becomes the accumulator for the next item. On the first execution, since we did not give it an explicit seed value, it just uses the first item. The final return value becomes the value that Aggregate returns to output. (Edited: Added DefaultIfEmpty -- this overload of Aggregate doesn't work on empty sequences.) Using the lambda provides nicer syntax than this equivalent code:
static void Main(string[] args) { string output = args.DefaultIfEmpty("").Aggregate(joinCommaSpace); Console.WriteLine(output); } static string joinCommaSpace(string a, string b) { return a + ", " + b; }
So why is this a good thing? Well, it goes back to the questions about the first set of code: how much effort is required to determine intent and correctness of a particular piece of code? In the imperative way, we need eight lines of code, with three distinct paths. Using a functional approach, we have three lines of code and only one code path. The only serious objection that I've heard is that this code is "unfamiliar". Well, sure, anything new might be unfamiliar, but that does not make it bad.
You wouldn't write your SQL code to make someone only familiar with C "comfortable" or "familiar", would you? You wouldn't write in C# the same way you'd write in C or BASIC. So why stick with outdated programming practices just because some "new" developer might get confused? Functional code is more concise, less error prone, and much more readable. Learning this style might take a couple of days, but it's an invaluable skill. At any rate, LINQ is built upon these concepts, so it will do people good to learn anyways.
Now, let's examine how to create our own functions to hide loops. This time, we're going to look at data access. A very common pattern in data access is creating a SqlDataReader, going through it, adding elements to a list. Like other patterns, overhead obscures the intent:
public static List<Person> GetAllPeople() { // Setup command var comm = new SqlCommand("GetAllPeople"); comm.CommandType = CommandType.StoredProcedure;
// Setup connection using (var conn = new SqlConnection(Settings.ConnectionString)) { comm.Connection = conn; conn.Open();
// Loop and add people to our list using (var reader = comm.ExecuteReader()) { var people = new List<Person>(); while (reader.Read()) { var p = new Person(); p.Name = reader.GetString(0); p.Age = reader.GetInt32(1); people.Add(p); } // Done return people; } } }
Yes, something as simple as reading a list of two-field type can take 14 lines of code. Let's do something about that. The only unique part is where we create a Person from the SqlDataReader. Outside of that, it's a very straightforward, but large, pattern. Refactoring the pattern, we get this:
public static List<T> ListFromReader<T>(string connectionString, SqlCommand command, Func<SqlDataReader, T> code) { // Setup connection using (var conn = new SqlConnection(connectionString)) { command.Connection = conn; conn.Open();
// Loop into list using (var reader = command.ExecuteReader()) { var list = new List<T>(); while (reader.Read()) { // Here we call the supplied code to add the right item T item = code(reader); list.Add(item); } return list; } } }
Now our code to GetAllPeople is very simple:
public static List<Person> GetAllPeople2() { var comm = new SqlCommand("GetAllPeople"); comm.CommandType = CommandType.StoredProcedure;
return ListFromReader(Settings.ConnectionString, comm, reader => new Person { Name = reader.GetString(0), Age = reader.GetInt32(1) }); }
Just be looking at this code, we know all the data-API stuff is handled correctly. This approach is vastly superior to other common SQL "helpers", for example, an ExecuteReader method that gives us a SqlDataReader. First, we have no locals that need disposing or other cleanup: this function scopes the variables to our lambda. Second, we can focus on our actual logic (creating a Person) rather than dealing with loop conditions.
Edit: This is not specific to C# 3.0! You can definately achieve a lot of the same benefits in 2.0, except you need to replace the simple lambda syntax ( => ) with the much more verbose delegate (ArgType arg) { } (anonymous method) syntax. C# 3.0 just makes it much easier to write.
What other common loops do you encounter? I have a few more we'll cover in the next article. As always, comments, insults and suggestions are welcome.
|
|
Code
|
Thursday, August 16, 2007 4:36:01 PM UTC
|
Trackback
|
 Monday, August 13, 2007
|
Previous article: Practical Functional C# - Part I
Last time I demonstrated how to replicate the using keyword as a function that takes a function. In this article, I'm going to show some real-world cases where you'll see a major improvement by taking a more functional approach. I'm going to use WCF as an example space.
WCF allows us to define services as normal C# interfaces. We then use a factory to create a proxy for an interface, specifying the URI, binding types (HTTP, binary, message queue), and other options. However, our interest is how this actually looks from the client side; how we actually make calls.
Since a WCF call is actually invoking code on another machine, any number of bad things can happen to our client channel, resulting in exceptions. When this happens, we have to Abort the channel. But if things complete successfully, we just need to Close the channel. Of course, that's not easy enough: if the Close fails, we then need to Abort the channel anyways. Confused? Well, check out this code snippet that uses an interface called ICalculator to add two numbers:
void WcfExample() { int a = 1; int b = 2; int sum; var chanFactory = GetCachedFactory<ICalculator>(); ICalculator calc = chanFactory.CreateChannel(); bool error = true; try { sum = calc.Add(a, b); ((IClientChannel)calc).Close(); error = false; } finally { if (error) { ((IClientChannel)calc).Abort(); } } Console.WriteLine(sum); }
Ouch! Out of 17 lines of code, only five relate to our problem. The rest (70%) is pure, ugly overhead. This is a much more complicated pattern than the using pattern, and from experience, I can tell you it is error-prone. Fortunately, this pattern decomposes nicely. The only two unique things are the name of the interface and the statement that acts on the interface. Here is one way you might go about writing this generically:
TReturn UseService<TChannel, TReturn>(Func<TChannel, TReturn> code) { var chanFactory = GetCachedFactory<TChannel>(); TChannel channel = chanFactory.CreateChannel(); bool error = true; try { TReturn result = code(channel); ((IClientChannel)channel).Close(); error = false; return result; } finally { if (error) { ((IClientChannel)channel).Abort(); } } }
This is exactly like the previous code, but we've substituted TChannel and a function parameter named code instead of our actual types. The type of function we want is Func<TChannel, TReturn>. You can think of this as saying: "transforms a TChannel into a TReturn". Now, look at the beauty this allows on the client side:
void WcfExample2() { int a = 1; int b = 2; int sum = UseService((ICalculator calc) => calc.Add(a, b)); Console.WriteLine(sum); }
Presto! Our overhead went from 12 lines to zero. If that didn't sell you on the power of functions, I suggest finding another career. Now, there is a lambda there, but all it is saying is "Here is a function that takes one parameter of type ICalculator and returns the value of Add(a, b)." The C# compiler automatically infers the return type (int).
Some of you mentioned still using C# 2.0. Two things: First, you can use the 3.0 compiler and run on 2.0 because these examples don't reference any new assemblies. Second, you can achieve the same thing using anonymous methods, but they just look a bit uglier:
void WcfExample3() { // C# 2.0 example with anonymous methods int a = 1; int b = 2; int sum = UseService<ICalculator, int>(delegate(ICalculator calc) { return calc.Add(a, b); }); Console.WriteLine(sum); }
By this time, I hope you are thinking about places in your code where you can extract a larger pattern and use functions instead. The key concept to understand here is that your source code should reflect your solution. You should not have extra code sitting around just for the sake of appeasing the platform or meeting some "design pattern". Let the platform and libraries take care of the details, and let your code focus on actually solving problems. As always, I welcome your comments, suggestions, and insults, so please let me know what you think!
Next up, we'll take a look at loops, see why they are harmful, and then go about fixing them.
|
|
Code
|
Monday, August 13, 2007 11:07:26 PM UTC
|
Trackback
|
 Sunday, August 12, 2007
|
Edit: This first article might be a bit dense at times. However, I promise, if you stick through and read at least until Parts II and III (Loops are Evil) you will see major benefits that will totally transform your code (even C# 2.0 code!). In fact, to the first 3 people that can show me that these practices will NOT result in better code in many enterprise apps, I'll give $100 each. Post a blog comment or email me (mgg AT telefinity dot com).
Redundancy in source code is a common degeneration. But for the C# programmer, her weapons to eliminate it have been unwieldy at best. She has been able to eliminate simple, blocky, patterns, but truly writing reusable code at a fine level has not been easy.
This series will demonstrate how you can take advantage of functional programming (FP) in your work, today. The first task is to dispense with the notion that functional programming is difficult and strange. Most FP articles start with a recursive definition of the Fibonacci sequence and then talk about currying functions. Here, we're going to start off with something that every C# programmer has used many times. I can guarantee that after you absorb this series of articles, you'll be writing more concise, less buggy, more powerful software. I've seen many C# modules get cut down drastically in size. I've seen new C# code written that does multithreading and it was written correctly on the first go. I want you to see these things too.
Refactoring out common blocks of code is familiar. Probably every developer has written a helper method to initialize a database command or prepare a commonly used object. Unfortunately, refactoring only tends to happen to contiguous blocks. We do not see refactoring of complex patterns that do not fit into neat blocks. Consider the following visualizations of a program. The red blocks are the unique parts of the program, and the grey ones represent common statements.
 becomes
It is easy to refactor the first sequence: move the common parts into another function and call it. However, the second pattern poses quite a problem. The individual "common" blocks are too simple to move into another method. The call to the refactor method would be the same as the method itself! How can we refactor it correctly?
Well, C# itself contains keywords that refactor some of these patterns. Consider the using keyword. The following two methods are nearly equivalent:
void usingPatternExample() { string result; StreamReader sr = new StreamReader(filename); try { result = sr.ReadToEnd(); } finally { if (sr != null) sr.Dispose(); } }
void usingKeywordExample() { string result; using (StreamReader sr = new StreamReader(filename)) { result = sr.ReadToEnd(); } }
I think it is obvious why everyone uses the using keyword over writing out this long init-try-something-finally-dispose pattern repeatedly. When we read code that uses the using keyword, the intent is instantly clear. We don't need to go validate the pattern to make sure it does what we think it does, we can just see "using" and know that it is correct. As an extra benefit, we can declare our disposable variable inside the using scope, so that we don't accidentally use it after it is disposed.
This is all good and dandy, until we realize that we are stuck with the handful of patterns defined as C# keywords! If I wanted to declare the variable result as an output of the using statement, I'm out of luck. If I want to initialize multiple objects in one using block, then that is just too bad. Part of the reason for this is that it was extremely ugly to do this in C# before version 3.0. Consider this C# 1.0 example to recreate the using keyword as a user-defined function:
delegate void UsingAction(IDisposable obj); void Using(IDisposable obj, UsingAction action) { try { action(obj); } finally { if (obj != null) obj.Dispose(); } }
So far, so good – nothing ugly yet. The usage of this user-defined Using function on the other hand:
string result; void UsingExample1() { Using(new StreamReader(filename), new UsingAction(readIntoResult)); } void readIntoResult(IDisposable obj) { StreamReader sr = (StreamReader)obj; result = sr.ReadToEnd(); }
Atrocious! The pattern of the code is shattered and complicated to follow. The main part of the code (getting the result) is forced to sit in a separate method many lines away. We're required to use fields to pass data in or out. And just to top it off, lack of generics forces a cast – we had to write StreamReader three times. The utter syntax hides our intention. In short, it is completely unusable.
C# 2.0 makes some progress. First, we can change UsingAction to Action<T> and allow the caller to specify the type. All that is needed is a constraint on the type to IDisposable. The new declaration of Using looks like this:
void Using<T>(T obj, Action<T> action) where T : IDisposable
This says that we need something of type "T", and the only restriction is that T must be a type that implements IDisposable. Then, we want an Action that works on that same type T, whatever it is. By using T instead of a specific type, we are essentially saying "we don't care about what type your object is; our function works with all sorts of types". This is the source of the name "generics". Our code is more generic; it's not tied to a specific type. Generics are an extremely important tool for refactoring patterns because patterns occur across different types.
To clean up the calling code, C# 2.0 has a feature called anonymous methods. They are exactly as they sound: methods without names. They can be declared inline, right along with the rest of the code. Additionally, they "capture" local variables, making it unnecessary to declare fields to pass data in and out.
void UsingExample2() { string result; Using(new StreamReader(filename), delegate(StreamReader sr) { result = sr.ReadToEnd(); }); }
What a major improvement! C# 2 can infer the type of T for Using, avoiding having to write Using<StreamReader>. Apart from having to type StreamReader twice, this code is starting to approach the level of clarity that the using keyword provides.
C# 3.0 introduces lambdas. Now, that's usually a scary word from functional programming, but it's actually very simple. For C#, it's just an easier way of writing anonymous methods. Instead of having to write "delegate(ParamType paramName) { }", we can just do "paramName =>". That's all there is to it! Nothing scary at all. For example, if I want to write a lambda to add two numbers, I’d write it like this:
Func<int, int, int> add = (int a, int b) => a + b;
The part Func<int, int, int> says we have a function that takes two ints and returns an int. Next, we declare our two integer parameters: (int a, int b). Then, we define the lambda body by using the => operator. Our body consists only of “a + b”. With lambdas, if we only have one statement, we don’t need to explicitly use braces or the return keyword. Alternatively, we could have written it as:
Func<int, int, int> add = (int a, int b) => { return a + b; };
Behind the scenes, the compiler goes and creates a method and creates the Func delegate on top of it. So, armed with this new syntax and power, let’s take another look at our Using method:
|
void UsingExample3() { string result; Using(new StreamReader(file), sr => result = sr.ReadToEnd() ); } |
void usingKeywordExample() { string result; using(var sr = new StreamReader(file)) { result = sr.ReadToEnd(); } } |
This shows there is nothing that special about the built-in keywords. It is also the first step in demonstrating that some parts of functional programming are not very foreign to the C# programmer. The next articles in this series will provide some real-world application of these ideas and show how you can reduce the amount of code you have to write (and reduce the number of bugs at the same time!).
|
|
Code
|
Sunday, August 12, 2007 11:21:32 PM UTC
|
Trackback
|
 Thursday, August 02, 2007
|
C# with lambda syntax and extension methods (in lieu of compositional operators) gets us so far, but the syntax and compiler could use a bit of polish. I'll show some exact examples later, but meanwhile this picture sums up my feelings at the moment:

Edited to add: Inspired by http://xkcd.com/297/
Edited to add: Just to be clear, this isn't a compiler or IDE issue: it's a spec issue (AFAIK). The C# spec simply doesn't allow certain things (like type inference from a method group).
|
|
Code | Humour
|
Thursday, August 02, 2007 7:39:10 PM UTC
|
Trackback
|
 Wednesday, April 04, 2007
|
We were rolling out a new database that is transactionally replicated to a few other nodes. In test and staging, everything worked fine, but in production, on a cluster, the distribution job failed. The snapshot runs as the SQL Agent account, but the distro runs as a separate account to distro just that database to the subscribers. The error is:
Unable to start execution of step 2 (reason: Error authenticating proxy DOMAIN\SomeUserName, system error: Logon failure: unknown user name or bad password.). The step failed.
We spent about an hour trying to figure out what was going wrong -- all the ACLs were right, the user was in the PAL. Everything was identical in permissions to the other environments.
After a bit of time on the line with PSS, we noticed that if we ran everything as the SQL Agent Account (the cluster service), then it worked. But, this required adding the cluster's account to the subscriber DB, and that was not acceptable.
Finally, our PSS rep suggested we check that the SQL Agent account was trusted for delegation. Bingo. On staging and test, the SQL Agent account is Network Service (or Local System). But in a cluster, it runs as a separate account, and that account is not trusted for delegation. Hence, the impersonation call failed. Simply going into ADUC and trusting it for Kerberos delegation, then restarting the SQL Agent, allowed us to use the proxy accounts without problem.
It seems like this message comes up a lot in context of replication and clusters. Hope this helps someone else!
|
|
Misc. Technology
|
Wednesday, April 04, 2007 1:24:11 AM UTC
|
Trackback
|
 Tuesday, March 13, 2007
|
My friend just dealt with an oursourced project. Yes, outsourced as in sending it to a place that charges a lot less money than, say, developers who actually know what they're doing.
One of their deliverables was a program that compressed an XML string into a gzip file. Should be a minor thing, right? The C#/.NET 2 code is less than 10 lines. Well, their first delivery produced files that contained the text "System.Byte[]". This was not accepted and they vowed to look into it in more as they were sure the code was correct.
Their next files were a bit larger and supposedly were really correct this time. But still, no zip program could read the data. Well, a quick look at the beginning of the file shows the bytes EF BB BF -- the UTF-8 BOM. The rest of the file was ASCII digits. Yes, they wrote the bytestream out as a UTF-8 interpretation.
If we define evolution as "the non-random survival of randomly mutating replicators", we can define their approach as "the non-random acceptance of randomly mutating programs."
|
|
Code | Humour
|
Tuesday, March 13, 2007 5:51:08 PM UTC
|
Trackback
|
 Tuesday, August 08, 2006
|
Having just got a dSLR (Nikon D50, rather low-end) with the cheap kit-lens (some Quantaray lens), I had been looking for a better lens. Most of the photos I take are inside, mainly snapshots. I've never seen flash photos that look that great, so I prefer to use the existing light. Of course, indoors, this makes for some really nasty blur in most cases. There's just not enough light inside my house to be able to get a fast enough shutter speed.
I read some reviews about the Nikon VR (Vibration Reduction) lenses, and that they supposedly compensate for 3 full stops. I tried one VR lens out in a store, and it seemed pretty interesting, so I ordered the Nikkor 18-200 VR. Unfortunately, Nikon apparently doesn't know anything about logistics or manufacturing, and this lens is backordered 2-6 months (depending on who you ask). Since I don't want to be taking crappy shots for that long, I grabbed the Nikkor 24-120 VR as a temporary lens. Well, I'm not sure it'll help my crappy shots, but at least they won't be blurry.
I got home, it's dusk, and decided to try it out. For fun, I set the exposure to 1 second, and well, let the results speak for themselves:
VR Off:

VR On:

Ignore the actual value of the image (I'm not a photographer), but just look at the blur. Granted, I was lying against a sofa, so I had a bit of something to lean on, but the difference with VR on == wow! So, given a bit of support, I can take pictures, handheld, with exposures > 1/15, no problem. Worth every dollar.
Yes, I know this is nothing new, but I'm so impressed I wanted to write it down (also helps my rationalization on buying the lens :)).
|
|
Photography
|
Tuesday, August 08, 2006 2:01:21 AM UTC
|
Trackback
|
 Friday, July 28, 2006
|
Sniping is placing your bids seconds before an auction ends. This allows the buyer to get an item for less money, since there is no bidding war. eBay allows and encourages sniping. Google for eBay sniping to find services that do this.
People say this helps them save money since there is no bidding war, and they don't accidentally get carried away. Well, if you have no self control, then you've got other problems.
Sniping fundamentally goes against eBay's system. Let's take sniping to its logical conclusion: everyone snipes. In this scenario, you essentially have a sealed-envelope auction. Everyone submits their bid, then at the end of the auction the person with the highest bid wins. But eBay is NOT a sealed-envelope auction. If it were, then why would eBay have outbid notifications?
A lot of the justification for sniping is ridiculous: "It helps us control spending." -- So does eBay: type in your "true max" into the eBay proxy bidding, and you're done.
"It allows us to be away from the computer." -- So does eBay's bidding.
"If auctions auto-extended to avoid sniping, then we'd be on for hours." -- Only if you wanted to exceed your "true max" as snipers are so fond of calling it. This contradicts the "controlled spending".
eBay simply contradicts themselves on that simple premise: why offer services (outbid notifications, showing bid history, etc.) that go against sniping, if sniping is something that should be done? Moreso, why does eBay themselves not offer a sniping service, and instead makes you pay a 3rd party? The lack of critical thought in this country is astounding.
Any pro-sniping people out there, feel free to post a rebuttal in the comments. If there's any real reason 3rd party sniping sites should exist, I'd love to know.
Edit: Removed horrible wording at beginning :).
|
|
|
Friday, July 28, 2006 3:39:28 AM UTC
|
Trackback
|
 Wednesday, June 07, 2006
|
Just got an Xbox 360 and tried Burnout Revenge on Xbox Live. For some lame reason, you have to play on EA servers (I believe it's so they have more of an excuse to get your details). Anyways, they ask for your permission to use your Xbox live data. The prompt looks like this (paraphrasing):
Can Microsoft share your Xbox Live account with EA? A = Yes , B = No
This is the standard Xbox 360 convention, where A = Accept/Next/OK and B = Reject/Back/Cancel.
Then they ask two more questions, Can EA spam you with their newsletter, and can EA share your details with other companies. This time the responses are: A = No, B = Yes
Of course, you're trained to press B for no and by the time you realise they've tricked you it's too late.
Way to go EA, thanks for just making sure we have a reason to hate your company. And Microsoft, WTF? What about making sure the user is in control? MS should not allow their partners to behave like this.
|
|
Misc. Technology
|
Wednesday, June 07, 2006 2:38:14 AM UTC
|
Trackback
|
 Saturday, May 27, 2006
|
I got this error while using Team Build today (from the event log): TF53010 ... Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression ... sp_InsertProjectDetails ...
According to http://geekswithblogs.net/cyoung/archive/2006/03/09/71820.aspx, this is a bug in the RC (yes, I still haven't had a chance to goto RTM). I'm not sure if it's fixed in RTM or not. At any rate, my issue was that I had 2 shared projects (projects that are included in more than 1 solution). The second shared project references the first. These seems to cause Team Build to die, with no error in the build log. The event log has more details. So, I added a hack of a file reference instead of a project reference for that solution and things seem to build.
|
|
Misc. Technology
|
Saturday, May 27, 2006 10:38:26 PM UTC
|
Trackback
|
 Wednesday, May 24, 2006
|
Here's one thing I love: Vista Tablet capabilities :). The tablet input area is always visible as a ~5 pixel deep tab, and it throws a drop shadow on windows in the foreground:
Pointing at it makes it slide out so you can click to open:
 I prefer that to the hovering icon that appears on textboxes. Of course, it has nice animations and so on, showing it sliding in and out, and you can drag it around the screen.
Another thing that looks promising: Personalized handwriting recognition. My handwriting is very bad (22 years on a PC and counting!) -- and XP Tablet messes up a lot when trying to read it. Now, via a "Speech Recognition Training"-style UI, you can train it for your own handwriting by writing in 50 different sample sentences. Oh yea, and it's localized too (train for other scripts).
Also, a small, but profoundly useful enhancement: Different pointers and click effects when using a stylus. When you target using a stylus, you get a small diamond cursor. Clicking makes tiny ripples. It looks really superb, and it's great to get that kind of feedback. Although, at first it scared me, cause I thought my screen was damaged, and the rippling was from pressign on the LCD too hard ;).
Along with pen-mode feedback: holding down the pen button ("right click") adds a circle around the diamond pen cursor. Right clicking makes the circle do a beautiful blue "energy" glow. The cursors don't show up in my screenshots, but here's the right-click energy circle thing:

Finally, a "Pen Flicks" feature makes navigating and so on by pen easier. If you, well, flick the pen up/down/left/right, you can scroll or move that way. Perfect for web browsing, reading, etc., even though my Toshiba R10 has a directional button on the display. There's also an edit mode so you can do things like undo, etc.

Now I just need to install Office 2007 (OneNote in particular) on my tablet to see if there's some awesome integration and so on...
|
|
Misc. Technology
|
Wednesday, May 24, 2006 8:32:30 PM UTC
|
Trackback
|
|
One of the big features of Vista was supposed to be that it was resolution-independent so that high resolution displays can be used. This makes extra sense when you think of Media Center on high-res systems. I have a Dell 24" LCD, 1920x1200 resolution and I run Media Center on it. Outside of Media Center, things look horrid. So I gleefully installed Vista Beta 2...
You still have to reboot after changing DPI. Sigh, ok... And then? Things look like crap. Half the windows seem to scale somewhat decently, half don't. And the ones that scale? Ever blown up an image in Photoshop? Yea, it looks worse than that. Seriously, WTF? There is supposed to be all these advanced graphics rendering capabilities, but they still can't render high DPI stuff correctly. Even the "Back" button (the round circle with arrow) looks like a horrible upscaled image. Pathetic. I've got no problem shelling out the $$$ that Vista ULTIMATE (sigh, MS marketing and sales needs to be caned) will cost IFF it's gonna make my home PC/MCE be one extremely slick amazing thing. But they've got a way to go, apparently.
|
|
Misc. Technology
|
Wednesday, May 24, 2006 6:51:04 PM UTC
|
Trackback
|
 Tuesday, May 23, 2006
|
Anyone else having this issue? When I use Outlook, it sometimes just spikes to 100% (well, 50% since I have hyperthreading). This is making the magnificent Beta 2 release of Office 12 almost unusable.
-- OK, seemed to be the indexer, as I left it alone for two hours and now it's behaving... strange...
Other than that... WOW. Sending an email never looked so good :).
|
|
Misc. Technology
|
Tuesday, May 23, 2006 9:56:47 PM UTC
|
Trackback
|
|
|