First, note that the implementations of Foo.Id and Foo.DoWorkOnBar are irrelevant; the compiler treats foo.Id and foo.DoWorkOnBar() differently even if the implementations don’t access Bar:
// In class Foo:
public new int Id => 0;
public void DoWorkOnBar() { }
The reason that foo.Id compiles successfully but foo.DoWorkOnBar() doesn’t is that the compiler uses different logic¹ to look up properties versus methods.
For foo.Id, the compiler first looks for a member named Id in Foo. When the compiler sees that Foo has a property named Id, the compiler stops the search and doesn’t bother looking at Bar. The compiler can perform this optimization because a property in a derived class shadows all members with the same name in a base class, so foo.Id will always refer to Foo.Id, no matter what members might be named Id in Bar.
For foo.DoWorkOnBar(), the compiler first looks for a member named DoWorkOnBar in Foo. When the compiler sees that Foo has a method named DoWorkOnBar, the compiler continues searching all base classes for methods named DoWorkOnBar. The compiler does this because (unlike properties) methods can be overloaded, and the compiler implements² the overload resolution algorithm in essentially the same way it’s described in the C# specification:
- Start with the “method group” consisting of the set of all overloads of 
DoWorkOnBar declared in Foo and its base classes. 
- Narrow the set down to “candidate” methods (basically, the methods whose parameters are compatible with the supplied arguments).
 
- Remove any candidate method that is shadowed by a candidate method in a more derived class.
 
- Choose the “best” of the remaining candidate methods.
 
Step 1 triggers the requirement for you to add a reference to assembly Bar.
Could a C# compiler implement the algorithm differently? According to the C# specification:
The intuitive effect of the resolution rules described above is as follows: To locate the particular method invoked by a method invocation, start with the type indicated by the method invocation and proceed up the inheritance chain until at least one applicable, accessible, non-override method declaration is found. Then perform type inference and overload resolution on the set of applicable, accessible, non-override methods declared in that type and invoke the method thus selected.
So it seems to me that the answer is “Yes”: a C# compiler could theoretically see that Foo declares an applicable DoWorkOnBar method and not bother looking at Bar. For the Roslyn compiler, however, this would involve a major rewrite of the compiler’s member lookup and overload resolution code—probably not worth the effort given how easily developers can resolve this error themselves.
TL;DR — When you invoke a method, the compiler needs you to reference the base class assembly because that’s the way the compiler was implemented.
¹ See the LookupMembersInClass method of the Microsoft.CodeAnalysis.CSharp.Binder class.
² See the PerformMemberOverloadResolution method of the Microsoft.CodeAnalysis.CSharp.OverloadResolution class.