# Wednesday, February 22, 2012
In EF Code First, context.SaveChanges() automatically does validations.
But lazy loading can collide with validations.

In our model, a Product must have a Category.

public class Product
{
    [Key]
    public int ProductId { getset; }
 
    [Required]
    [StringLength(40)]
    public string ProductName { getset; }
 
    [Required]
    public virtual Category Category { getset; }
}

Let's try this...
using (var context = new NorthwindContext())
{
    //find a specific product
    var product = context.Products.Find(147);
    //set a new name
    product.ProductName = Guid.NewGuid().ToString();
 
    //ERROR!
    context.SaveChanges();
}


Oh no, System.Data.Entity.Validation.DbEntityValidationException. "Validation failed for one or more entities." How can that be? We just loaded it from the database, it must be correct?
The exception says that Category is null. But it exists in the database.

If you inspect it in the debugger, you can see the category ... and if you continue the SaveChanges succeeds. The debugger triggered a lazy load, so it works.

context.SaveChanges() internally turns off lazy loading before validating. Which is good, because you don't want unnecessary database access. But when the reference is required, the validation doesn't recognise this is a proxy which has a categoryId but hasn't loaded it.

Solution 1- no auto validation


One solution is to turn off validation. When you are setting individual properties with values you've already validated, you do not need it here.

context.Configuration.ValidateOnSaveEnabled = false;
context.SaveChanges();

The SQL, by the way, is update [dbo].[Products] set [ProductName] = @0 where ([ProductID] = @1).
(Check it by hooking up a SQL logger, as described here)

An alternative is to cheat the model- ensure references are not Required.

But if you have a product coming back from a UI for insert or update, you need to manually validate that it always has a category.

Solution 2- ensure required references are loaded


This triggers extra database access, but you can keep the automatic validation.

//find a specific product
var product = context.Products.Find(147);
//make sure it's loaded
context.Entry(product).Reference(p => p.Category).Load();

If you load from a query, you can use an .Include(p => p.Category) which will load the product with a join to the category table, so there is only one sql statement. This will bypass the internal cache so if it was loaded the same product earlier, you can't save any data access.
var product = context.Products
    .Include(p => p.Category)
    .First(p => p.ProductId == 147);

Solution 3- Add a foreign key Id property


You can specify foreign key Id properties in addition to the instance property.
//it's not nullable so it's implicitly required
public int CategoryId { getset; }
 
public virtual Category Category { getset; }


Note the CategoryId is now required. The Category instance isn't. Validation can check the CategoryId, and we can set it directly without having to load the instance.

You have to map this arrangement (unless you like to see an EntityCommandCompilationException with the inner exception message being "More than one item in the metadata collection match the identity 'CategoryId'." - I didn't).
In the EntityTypeConfiguration<Product> the mapping must point to the foreign key id property.
HasRequired(x => x.Category)
    .WithMany()
    .HasForeignKey(p => p.CategoryId);


We've "denormalized" our entity model here, but it makes dealing with detached objects and viewmodels a little easier.

But won't the CategoryId and Category properties get out of step? If you set one, which gets persisted?

Let's set the foreign key id property.
//find a specific product
var product = context.Products.Find(146);
 
//it's category 4 in both
Console.WriteLine(product.Category.CategoryId);
Console.WriteLine(product.CategoryId);
 
//set the categoryId property
product.CategoryId = 1;
 
//we've just set it, so it's 1
Console.WriteLine(product.CategoryId);
//oh no, still says 4!
Console.WriteLine(product.Category.CategoryId);


context.SaveChanges() does the right thing- it saves the new value, 1, assigned to the id property.

Let's set the instance.
//find a specific product
var product = context.Products.Find(146);
 
//it's category 1 in both
Console.WriteLine(product.Category.CategoryId);
Console.WriteLine(product.CategoryId);
 
//set the category instance
product.Category = context.Categories.Find(2);
 
//we've just set it, so it's 2
Console.WriteLine(product.Category.CategoryId);
//oh no, still says 1!
Console.WriteLine(product.CategoryId);


But again, context.SaveChanges() does the right thing- it saves the new value, 2, assigned to the instance.

So it looks like persistence always works as you'd expect, but the properties get out of step. You must be careful in your workflow. This won't work...
product.CategoryId = 1;
var returnMessage = "Category updated to " + product.Category.CategoryName;


Conclusion

In most cases I'd prefer to remove validation on save with context.Configuration.ValidateOnSaveEnabled = false - as long as the values were validated downstream (perhaps in the UI validation framework).

But exposing the foreign key property is undoubtably convenient when the UI just sends you categoryId= 1 and you don't want to load that category from the database just so you can persist product.
posted on Wednesday, February 22, 2012 11:45:48 AM (Romance Standard Time, UTC+01:00)  #    Comments [0]
# Tuesday, February 14, 2012
I wanted to log the SQL so I can profile a Entity Framework Code First application.

MVC Mini-Profiler only works in an ASP MVC application- not in console or unit tests.

The tracing and caching providers for Entity Framework expect ObjectContexts (EF 4.0), not DbContexts. But we can make them work.

Scenario:
I have a code first project with my DbContext, called NorthwindContext.
I have a unit test project, with a test that uses NorthwindContext

Here's the steps.

1. Download the providers.
2 (Optional): review the Q&A and apply some of the suggested patches.
3. Build the solution.
4. The unit test project will reference the dlls from the tracing provider
EFProviderWrapperToolkit.dll
EFTracingProvider.dll
5. Add an App.config to the unit test project something like this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings>
    <add name="NorthwindContext" 
         providerName="System.Data.SqlClient" 
         connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True;Pooling=False;MultipleActiveResultSets=True" />
  </connectionStrings>
  <system.data>
    <DbProviderFactories>
      <add name="EF Tracing Data Provider" 
           invariant="EFTracingProvider" 
           description="Tracing Provider Wrapper" 
           type="EFTracingProvider.EFTracingProviderFactory, EFTracingProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=def642f226e0e59b" />
      <add name="EF Generic Provider Wrapper" 
           invariant="EFProviderWrapper" 
           description="Generic Provider Wrapper" 
           type="EFProviderWrapperToolkit.EFProviderWrapperFactory, EFProviderWrapperToolkit, Version=1.0.0.0, Culture=neutral, PublicKeyToken=def642f226e0e59b" />
    </DbProviderFactories>
  </system.data>
</configuration>
6. In the DbContext, we need to use two of the base constructors
        public NorthwindContext()
        {
            //default ctor, uses app.config connection string named "NorthwindContext"
        }
 
        public NorthwindContext(DbConnection connection)
            :base(connection,true)
        {
           //ctor uses for tracing 
        }
7. In my test, you need to use the overload that takes the DbConnection.
            using (var context = new NorthwindContext(
                CreateConnectionWrapper(@"name=NorthwindContext")))
            {
 
                //profile this!
                var product = context.ProductCollection.Find(1);
            }
8. And add the CreateConnectionWrapper method:
private static DbConnection CreateConnectionWrapper(string nameOrConnectionString)
{
    var providerInvariantName = "System.Data.SqlClient";
    var connectionString = nameOrConnectionString;
    //name=connectionName format
    var index = nameOrConnectionString.IndexOf('=');
    if (index > 0 && nameOrConnectionString.Substring(0, index).Trim()
        .Equals("name"StringComparison.OrdinalIgnoreCase))
    {
        nameOrConnectionString = nameOrConnectionString
            .Substring(index + 1).Trim();
    }
    //look up connection string name
    var connectionStringSetting =
        ConfigurationManager.ConnectionStrings[nameOrConnectionString];
    if (connectionStringSetting != null)
    {
        providerInvariantName = connectionStringSetting.ProviderName;
        connectionString = connectionStringSetting.ConnectionString;
    }
    //create the special connection string with the provider name in it
    var wrappedConnectionString = "wrappedProvider=" + 
        providerInvariantName + ";" + 
        connectionString;
    //create the tracing wrapper
    var connection = new EFTracingConnection
                            {
                                ConnectionString = wrappedConnectionString
                            };
    //hook up logging here
    connection.CommandFinished +=
        (sender, args) => Console.WriteLine(args.ToTraceString());
    return connection;
}
This should cope with connection strings in the 3 common forms ("Northwind", "name=Northwind" and "Data Source=.\SQLEXPRESS;Initial Catalog=Northwind ...")

Note the line to hook up logging (subscribing to the connection.CommandFinished event). We could simply have used
EFTracingProviderConfiguration.LogToConsole = true;

Or you can hook up to log4net or EntLib logging to those tracing events.



posted on Tuesday, February 14, 2012 2:11:00 PM (Romance Standard Time, UTC+01:00)  #    Comments [1]
# Sunday, February 05, 2012

I wanted to use a many-to-many relationship using Entity Framework Code First (v4.1/4.2).

Using pure code first such as this:

using (var context = new MyContext())
{
    var employee = new Employee { FirstName = "Homer", LastName = "Simpson" };
    var territory = new Territory { TerritoryDescription = "Springfield" };
    employee.Territories.Add(territory);
    context.Employees.Add(employee);

    context.SaveChanges();
}

results in a nice association table

image

How do you map existing database tables? Like Northwind's customer to customer demographic table relationship:

image

This is the code I want to write:

using (var context = new MyContext("name=Northwind"))
{

    var demo = new CustomerDemographic();
    demo.CustomerTypeID = "BERLIN";
    demo.CustomerDesc = "Berliner";
    context.CustomerDemographics.Add(demo);
    //link it to a customer by either end
    var alfki = context.Customers.Find("ALFKI");
    alfki.CustomerDemographics.Add(demo);

    context.SaveChanges();
}

We have to override DbContext's OnModelCreating and add some mapping. For CodeFirst, you map both sides of the relationship, so you can either put the mapping on Customer or CustomerDemographic - or even both if the mappings agree. A normal foreign key relationship is mapped with ".HasMany|HasOptional|HasRequired" followed by a ".WithMany|WithOptional|WithRequired".

So, from the CustomerDemographic entity, a many to many is just .HasMany(x=>x.Customers).WithMany(z=>z.CustomerDemographics).

In addition, we don't have standard names for our association table so we add a .Map element to specify the table and the left and right key columns.

Note the primary key of CustomerDemographics isn't the 'tableName'+"Id" convention that Code First will expect. So I have to define the key for that. As we have that end of the configuration, we'll define the mapping there.

Here's the code.

modelBuilder.Entity<CustomerDemographic>()
    //the key isn't standard so specify it
    .HasKey(x => x.CustomerTypeID)
    //define both sides of the relationship - HasMany.WithMany
    .HasMany(x => x.Customers)
    .WithMany(z => z.CustomerDemographics)
    //specify mapping information
    .Map(map =>
    {
        //the association table name
        map.ToTable("CustomerCustomerDemo");
        //the left side (fk to CustomerDemographic, the entity we're defining)
        map.MapLeftKey("CustomerTypeID");
        //the right side (fk to Customers, the other side)
        map.MapRightKey("CustomerID");
    }
);

If we mapped from the Customer entity, the HasMany and WithMany properties are different, and the mapped left and right keys swap round.

Here's the full DbContext for my mini-Northwind mapping:

class MyContext : DbContext
{
    public MyContext(string connectionName)
        : base(connectionName)
    {
    }

    public DbSet<Employee> Employees { get; set; }
    public DbSet<Territory> Territories { get; set; }

    public DbSet<Customer> Customers { get; set; }
    public DbSet<CustomerDemographic> CustomerDemographics { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyContext>());
        Database.SetInitializer<MyContext>(null);
        modelBuilder.Conventions.Remove<System.Data.Entity.Infrastructure.IncludeMetadataConvention>();

        modelBuilder.Entity<CustomerDemographic>()
            //the key isn't standard so specify it
            .HasKey(x => x.CustomerTypeID)
            //define both sides of the relationship - HasMany.WithMany
            .HasMany(x => x.Customers)
            .WithMany(z => z.CustomerDemographics)
            //specify mapping information
            .Map(map =>
            {
                //the association table name
                map.ToTable("CustomerCustomerDemo");
                //the left side (fk to CustomerDemographic, the entity we're defining)
                map.MapLeftKey("CustomerTypeID");
                //the right side (fk to Customers, the other side)
                map.MapRightKey("CustomerID");
            }
        );
    }
}
posted on Sunday, February 05, 2012 9:42:13 PM (Romance Standard Time, UTC+01:00)  #    Comments [0]
# Saturday, December 10, 2011

I've just done all 4 of the exams for .Net 4.0 MCPD Web Developer. Overall, I quite enjoyed it. I even learned a bit.

Programmer certifications, and particularly the Microsoft ones, get a lot of criticism. In theory they prove someone is proficient in the technology. In practice, by itself a certificate is unreliable.

Microsoft's Partner program insists that companies have staff with certifications. The body shops put their new recruits through a brief course so they can pass and then be sold on. Often, they memorised the questions and answers from internet dumps. It's disappointing to google the exam codes: apart from the Microsoft syllabus, every link is a site selling the answers.

If you're a consultant, or you're going to change jobs, experienced programmers probably have to do the exams too. If you work for a Microsoft Partner you have to do it, and HR departments filter their CVs using it. Really, the best measure of proficiency is years of experience and types of projects. That's real-world practical knowledge. Okay, I would say that, having been doing this for 25 years. But, as the company I work for needs the certificate points, and they're paying, I'm happy to do the exams.

For .net 1 we had to do exams for ASP, windows forms and web services, but most .net programmers then only worked in one or two of those- certainly never windows forms and ASP. I was actually quite impressed by the .Net 2 foundation exam, which covered a lot of basics: collections, threads, tracing, serialization, globalization, encryption. Specific projects may not involve some of it, but a good .net programmer should know almost all of it (even if you have to google the details). Unfortunately they dropped it for .Net 4, going back to technology areas (asp, web services) just like the original .net 1 exams.  Now for .net 4 "web" we have asp, wcf and data access (ado/entity framework). Broadly I agree you'd expect all web developers to know those topics, so that's not too bad.

One big problem with certification questions is that they pick on obscure APIs and ASP controls. Now, questions on the validation controls is probably a good basic requirement, but frankly the gridview events are a nightmare anyway, so memorizing them for an exam is painful. The .net 4 asp exam wasn't too bad although there was some API questions that in real life you'd just google. One of the jQuery questions annoyed me too, using an unusual API detail (I think an older syntax) when they could have used a better, clearer replacement.

The older exams had sections devoted to things nobody ever uses. On the asp .net 2 exam it was mobile controls, and web parts (cloned from Sharepoint, and never used outside Sharepoint). Fortunately there seems less of that in the .net 4 exams, although there's still some asp ajax library stuff in there, plus some dynamic data. The big problem was that jQuery and MVC have moved on since the exam was written. All the questions were about MVC 2, not 3. It's not that the questions were obsolete (there was nothing much about webform views to confuse those who use Razor), it's just that the pace of change is making exams less relevant.

The other exams had little used topics too. The WCF exam covered MSMQ, which I've never seen used in real life. The data access exam was more problematic. Basic ADO is useful, but there was still material about little used dataset features like constraints and dataRelations. There was not much on Linq2Sql, perhaps just as well, but most of the exam was Entity Framework. I know EF is used out there, but I've yet to encounter EF and it seems to be in second place to NHibernate at least for ORM data access. So I did learn something for this exam, and yes, I passed the exam as a novice with no real world experience of Entity Framework. On the other hand, as an experienced programmer I'd feel comfortable doing EF (even if I'd rather be doing NHibernate.)

Two of the exams - the WCF and the ASP Pro exam - had "testlets" with a case study and a small number of questions about the scenario. The WCF was over-elaborate, with source listings and xml, but for all that I thought it was a little more realistic and actually quite enjoyable.

To study, I used the Microsoft Training Kit books, MSDN and for the areas I was less familiar with - mostly EF and some aspects of WCF - a little practice. The WCF exam didn't have a book, so I had to use the .net 3.5 one and look up a couple of subjects (routing and discovery). I skimmed the bits I knew well (the asp/ web side generally). Generally I found you needed a bit more knowledge than was in the books, but only a few things were really obscure. Overall, studying for the exam was interesting and useful. Learning about WCF routing and discovery and EF, things I haven't actually used, did bring me more up-to-date with the .net 4 stack.

A pity Microsoft's "congratulations" email contains a link to a blog that closed 2 years ago.

posted on Saturday, December 10, 2011 10:15:55 PM (Romance Standard Time, UTC+01:00)  #    Comments [0]
# Friday, July 08, 2011
A brief recap of ASP.Net cache:

//in MVC use HttpRuntime.Cache or HttpContext.Cache
//in webforms Application is the original cache without expiration rules
var category = HttpRuntime.Cache["Category"as CategoryModel;
if (category == null)
{
     category = _dataAccess.Find(1);
     HttpRuntime.Cache["Category"] = category;
     // ...or...
     //monitor some files and/or other cache items
     var cd = new CacheDependency(new[] { @"C:\triggerFolder\" }, new[] { "OtherCacheItem" });
     HttpRuntime.Cache.Insert("Category1", category, 
               cd, //dependencies or null
               DateTime.Now.AddMinutes(5), //absolute expiration (or Cache.NoAbsoluteExporation)
             Cache.NoSlidingExpiration //sliding expiration (timespan)
     );
}
Mocking this in tests (especially for MVC) is a bit ugly (.Net 3.5sp1 has System.Web.Abstractions including HttpContextBase, but caching isn't included)

Now in .Net 4 we can reference System.Runtime.Caching.dll. And the really nice thing is this will run outside Asp.Net.
//get the static "default" cache. You can have multiple named caches.
ObjectCache cache = MemoryCache.Default;
//you can't store null in the cache
var category = cache["Category"as CategoryModel;
if (category == null)
{
    category = _dataAccess.Find(1);
    cache["Category"] = category;
    // ...or...
    var policy = new CacheItemPolicy();
    policy.AbsoluteExpiration = DateTime.Now.AddMinutes(5);
    //monitor some files and/or other cache items
    policy.ChangeMonitors.Add(
        new HostFileChangeMonitor(new List<string> { @"C:\triggerFolder\"})
        );
    //synchronize with another cache item
    policy.ChangeMonitors.Add(
        cache.CreateCacheEntryChangeMonitor(new [] { "OtherCacheItem"})
        );
    cache.Add("Category1", category, policy);
}
For simple caching (no change monitors) you don't even need mocking in your tests - in fact, you can test your caching with a real cache. You can move caching down into your library classes that may be called from web pages, tests, WPF apps and consoles.



posted on Friday, July 08, 2011 10:57:57 AM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Friday, June 10, 2011
T4 preprocessed templates are a neat way of generating text at run time, which can be deployed to machines without Visual Studio. Here's MSDN

Here's a simple template, ClassWriter.tt, which must be marked CustomTool = "TextTemplatingFilePreprocessor" in properties (not "TextTemplatingFileGenerator" which is a normal T4).
<#@ template language="C#" #>
<#@ import namespace="System.Linq" #>
<#@ parameter type="Generator.Model.Table" name="table" #>
using System;
 
namespace <#= table.Namespace #>
{
    [Serializable]
    public class <#= table.Name #>
    {
<#  foreach(var column in table.Columns.Where(c=> !c.Hidden)) { #>
        public virtual <#= column.Type #> <#= column.Name #> { get; set; }
<#    } #>
    }
}

At development time, Visual Studio generates the class in the corresponding namespace which you can then call (yeah, it generates code for generating code...).

Notice we're passing a parameter, which has to have the full namespaced name (even "string" has to be "System.String").

The generated class is partial and the parameters are property getters with a backing field. MSDN suggests that to pass in your parameters you should manually code a partial class with conventional properties or a constructor. Actually, there's an easier no-code way. Use the Session property (which is just a Dictionary<string, object>) which you can use with an Initialize() method. Like this:

//create the class generated by TextTemplatingFilePreprocessor 
var generator = new ClassWriter();
//create a session dictionary, fill it and initialize
generator.Session = new Dictionary<stringobject>();
generator.Session.Add("table", table);
generator.Initialize();
//transform!
var text = generator.TransformText();
The key things to watch out for:
  • you must create the Session dictionary (it's not initialized internally)
  • you must call Initialize() after it's populated.

You can also use System.Runtime.Remoting.Messaging.CallContext but weirdly you must still initialize the Session dictionary (Initialize checks Session first, then CallContext).
 
You may be tempted to reuse the T4 template class like this.
//don't do this
var generator = new ClassWriter();
foreach (var item in list)
{
    generator.Session = new Dictionary<stringobject>();
    generator.Session.Add("table", item);
    generator.Initialize();
    var txt = generator.TransformText();
    WriteText(item, txt);
}
The template class actually uses an internal StringBuilder called GenerationEnvironment. So each call returns everything you wrote before. You can't actually reset the StringBuilder (although you can append to it with Write overloads). So, always create the template class within the loop.



posted on Friday, June 10, 2011 1:50:55 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Monday, May 02, 2011
Strong named assemblies cannot reference assemblies which aren't strong named.

Decompile with ildasm and recompile with ilasm using your key.

Default ILDASM and ILASM locations as of .Net 4.0

"C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\ildasm.exe" ClassLibrary.dll /out:ClassLibrary.il
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe" ClassLibrary.il /res:ClassLibrary.res /dll /key:myKey.snk /out:ClassLibrary.dll

posted on Monday, May 02, 2011 2:50:05 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Saturday, April 09, 2011

My Codeplex project, Database Schema Reader, has a new version.

I needed to create a SQLite database was a replica of a parent SQL Server database. The existing project could easily read the schema, and the code generation tools could give me the table DDL and the insert SQL. It was just a matter of executing the SQL and creating the database file.

So I created another simple Windows Forms UI. The SqlWriter class needed some tweaks for SQLite support (and I added integration tests).

For fun, I decided to see if it could also support Microsoft's latest version of SQL Server CE 4.0.

SQL Server CE 4.0 is, like SQLite, an in-process database which can be XCOPY deployed. And unlike previous versions, it is easily able to run ASP.Net websites (it's the default database behind WebMatrix). And 4.0 provides the standard ADO GetSchema which my Database Schema Reader uses (3.5 throws a Not Implemented exception).

It has some of the same limitations as SQLite, such as no stored procedures (good riddance), and no output parameters. But it also has additional limitations. In SQLite you can batch together multiple SQL statements in a single line and execute them in one command. You can't in SQL Server CE.

Unlike SQLite's weakly typed and very simple data types, SQL Server CE 4.0 has most of the standard data types as SQL Server Express and full versions. Unfortunately, this was the big problem. If your table is defined with an IDENTITY primary key, you can't insert the data row with the same primary key, and subsequent foreign key relationships are broken. It's a pretty critical limitation for my scenario.

SQL Server CE 4 only supports a subset of SQL Server data types. The one data type it didn't support, which my database (and later versions of Northwind) use, is NVARCHAR(MAX). You have to use the horrible old NTEXT data type. I could have written a DDL provider that translates varchar max to ntext, but I don't think it's worth the effort.

So my SQL Server CE creation program has major limitations, compared to the SQLite version. It's built into CopyToSQLite.exe, and auto-detects if you've installed SQL Server CE, but you have to have a very simple database for it to work.

SQL Server CE 4 also doesn't support the ADO Sync framework which allows you to synchronize a disconnected database to a parent SQL Server database. Even SQL Server 2008 R2 Management Studio doesn't support it (you have to use Visual Studio 2010 with SP1).

The new FETCH /OFFSET syntax for paging, that will be SQL Server 11, is very nice though. I'm looking forward to that.

posted on Saturday, April 09, 2011 8:05:41 PM (Romance Daylight Time, UTC+02:00)  #    Comments [1]
# Thursday, April 07, 2011
VS 2010 web.config transformations are great.
Change your development web.config which looks like this:

<?xml version="1.0"?>
 
<configuration>
  <connectionStrings>
    <add name="ApplicationServices"
         connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true"
         providerName="System.Data.SqlClient" />
  </connectionStrings>
 
  <system.web>
    <compilation debug="true" targetFramework="4.0">

by adding a Web.DeployTest.config file which looks like this:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
 
  <connectionStrings>
    <add name="ApplicationServices" connectionString="Data Source=StaticVoidSqlServer;Initial Catalog=Northwind;Integrated Security=SSPI;" providerName="System.Data.SqlClient"
         xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
  </connectionStrings>
 
</configuration>

Only by default it's tied up with the build configurations (Debug, Release etc) and the Web deploy story.
But I don't want a web deployment package. I want:
  • a normal "Release" configuration build
  • a directory containing all the website files that I can XCopy deploy (like old school Visual Studio 2008 publish)
  • the web.config transformed with my custom "DeployTest" name.
Doing it this way means the web.DeployTest.config is not automatically nested under the web.config (and I should mark it as BuildAction=None).
But that's my requirements. So I wrote an MSBuild file.

Batch file (build.bat)

First here's a standard batch file, "build.bat", to launch it (I want MSBuild 4.0):
%systemroot%\Microsoft.Net\Framework\v4.0.30319\MSBuild.exe build.proj  /t:Release & pause

MSBuild script (build.proj)


<?xml version="1.0" encoding="utf-8" ?> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Release">   <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />   <PropertyGroup>     <!-- properties that are used in this build file - referenced as $(PropertyName) -->     <ProjectName>StaticVoid</ProjectName>     <DeployConfiguration>DeployTest</DeployConfiguration>     <PublishPath>$(MSBuildProjectDirectory)\..\Publish\</PublishPath>     <OutputPath>$(PublishPath)\StaticVoid\</OutputPath>     <PackagePath>$(PublishPath)\StaticVoidPackage\</PackagePath>     <TransformInputFile>..\StaticVoid\Web.config</TransformInputFile>     <TransformFile>..\StaticVoid\Web.$(DeployConfiguration).config</TransformFile>     <TransformOutputFile>$(OutputPath)\Web.config</TransformOutputFile>     <ImageResourcesPath>..\ImageResources</ImageResourcesPath>   </PropertyGroup>   <ItemGroup>     <ImageResources Include="$(ImageResourcesPath)\*.jpg" />   </ItemGroup>   <!-- targets -->   <Target Name="PublishWebsite">     <Message Text="Publishing Website" />     <RemoveDir Directories="$(PublishPath)"/>     <!-- do a deploy -->     <MSBuild Projects="..\StaticVoid\StaticVoid.csproj" Properties="Configuration=Release;OutputPath=$(PackagePath);DeployOnBuild=true;DeployTarget=PipelinePreDeployCopyAllFilesToOneFolder;AutoParameterizationWebConfigConnectionStrings=false;_PackageTempDir=$(OutputPath)"/>   </Target>   <Target Name="Transform">     <!-- transform the web.config -->     <TransformXml Source="$(TransformInputFile)"                   Transform="$(TransformFile)"                   Destination="$(TransformOutputFile)" />   </Target>   <Target Name="BuildWebsite" DependsOnTargets="PublishWebsite">     <!-- we don't need the deployment package, we wanted the published files to copy manually -->     <RemoveDir Directories="$(PackagePath)"/>     <!-- copy the unmanaged resources -->     <Copy SourceFiles="@(ImageResources)" DestinationFolder="$(PublishPath)images" SkipUnchangedFiles="true" />   </Target>      <Target Name="Release" DependsOnTargets="BuildWebsite; Transform;">   </Target> </Project>
 

A little explanation

The MSBuild task for the website project (StaticVoid.csproj) has a whole set of extra properties set which make it do a deploy.
(Broken here to be easier to read:)
    <MSBuild Projects="..\StaticVoid\StaticVoid.csproj" Properties="Configuration=Release;
OutputPath=$(PackagePath);
DeployOnBuild=true;
DeployTarget=PipelinePreDeployCopyAllFilesToOneFolder;
AutoParameterizationWebConfigConnectionStrings=false;
_PackageTempDir=$(OutputPath)
"/>

The regular deployment package is written to $(PackagePath). I don't care about that, so I delete it.
The actual directory and files that I wanted are written to $(OutputPath) using the _PackageTempDir property.

For the transform, note the msbuild xml must have Project ToolsVersion="4.0" and
<UsingTask TaskName="TransformXml" 
AssemblyFile
="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
And then the transformation:
    <TransformXml Source="$(TransformInputFile)"                   Transform="$(TransformFile)"                   Destination="$(TransformOutputFile)" />
with $(TransformFile) defined as
<TransformFile>..\StaticVoid\Web.$(DeployConfiguration).config</TransformFile>
Simply changing the DeployConfiguration property lets me have test and production builds which transform things nicely.

Update: the old _CopyWebApplication still works too

In Visual Studio 2008 you could use this task:
    <MSBuild Projects="..\StaticVoid\StaticVoid.csproj"
             Targets="ResolveReferences;_CopyWebApplication"
             Properties="Configuration=Release;
                         WebProjectOutputDir=$(OutputPath);
                         OutDir=$(OutputPath)\bin\" />
This still works in Visual Studio 2010's MSBuild (and you don't have a package directory to delete).
The transforms can still be done manually.


posted on Thursday, April 07, 2011 12:34:35 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Monday, November 01, 2010

A long time ago I wrote my database schema reader. It was 2005, .Net 2.0 was just out and I was learning about the new features. .Net 2.0's ADO DbProviderFactory had a nice idea, GetSchema, to get database independent schema information. But the GetSchema collections were slightly different for each database, and they were data tables. So I wrote a simple facade over them to get proper classes.

Initially I used it to generate CRUD stored procedures, and then to generate data access code, even including NHibernate mappings. I even did some (simple) database conversions. All this was quite icky, and each time I did the code-gen a different way, but the core facade worked well.

After getting a couple of queries about the extracts I'd already posted, I thought I might as well put up the entire source code. So here's the codeplex project with the source code, and even some basic SQL code-gen.

I picked Codeplex just because it's more .Net-centric than Google code or github, plus I'm more familiar with TFS and Subversion source control. It was pretty easy. I don't expect many downloads (if any!), but maybe a couple of people can steal the relevant bits of source code.

Check out the database schema reader codeplex project.

posted on Monday, November 01, 2010 12:59:33 PM (Romance Standard Time, UTC+01:00)  #    Comments [0]
# Sunday, June 06, 2010

One of the really useful Visual Studio add-ins is Smart Paster. It adds a "Paste As." context menu that allows you to paste in the clipboard text as a comment, a correctly quoted string or a string builder.

smartpaster

There are versions for VS 2003, 2005 and 2008. But not 2010.

Sometimes you can just copy in the dll and addin file into the VS 2010 Addins folder (.\Documents\Visual Studio 2010\Addins) and edit the addin file (it's just xml) to say "10.0" instead of "9.0". But that doesn't work for SmartPaster - VS 2010 shows an error and insists on disabling the addin.

The VS 2008 download includes the source, so I tried to upgrade it.

It turns out the problem is when it creates the context menus it sets the CommandBarButton.FaceId property (to show an image next to the text). But in VS2010 that throws a DeprecatedException.

Ok, simple fix, but the original source is old code with a fairly high WTF-per-line ratio (well, it was written 2004, .Net 1.1). Before long I had ported it from VB.Net to C# (thanks Telerik) and rewritten large parts (mostly refactoring with Coderush). I simplified by dropping the "regionize" stuff (never use it), the VB support and the configuration form. Here's my code- you can create a new Extensibility Addin project, replace the Connect class and add the SmartPaster class- see below.

It's still a port, so certainly not as clean as something just written from scratch. And perhaps VS2010 has nicer ways of doing all these things now the code window is a WPF control - the EnvDTE objects are ugly and hard to use. Anyway, thanks to Alex Papadimoulis for the original code.

Update: download the binary and unzip into your Addins folder. 

Update 2: source and binary are also on Codeplex. (It's exactly the same as shown here.)

Update 3 (March 2012): The Codeplex version is updated to support VB.Net and is compatible with Visual Studio 11


   1:  using System;
   2:  using System.Collections;
   3:  using EnvDTE;
   4:  using EnvDTE80;
   5:  using Extensibility;
   6:  using Microsoft.VisualStudio.CommandBars;
   7:   
   8:  namespace SmartPaster2010
   9:  {
  10:      ///<summary>The object for implementing an Add-in.</summary>
  11:      ///<seealso class='IDTExtensibility2' />
  12:      public class Connect : IDTExtensibility2
  13:      {
  14:          private readonly ArrayList _pasteAsButtons;
  15:          private readonly SmartPaster _smartPaster;
  16:          private CommandBarPopup _pasteAsPopup;
  17:   
  18:          ///<summary>Implements the constructor for the Add-in object. Place your initialization code within this method.</summary>
  19:          public Connect()
  20:          {
  21:              _pasteAsButtons = new ArrayList();
  22:              _smartPaster = new SmartPaster();
  23:          }
  24:   
  25:          ///<summary>Implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded.</summary>
  26:          ///<param term='application'>Root object of the host application.</param>
  27:          ///<param term='connectMode'>Describes how the Add-in is being loaded.</param>
  28:          ///<param term='addInInst'>Object representing this Add-in.</param>
  29:          ///<seealso class='IDTExtensibility2' />
  30:          public void OnConnection(object application, ext_ConnectMode connectMode, 
object addInInst, ref Array custom)
  31:          {
  32:              _applicationObject = (DTE2)application;
  33:              _addInInstance = (AddIn)addInInst;
  34:   
  35:   
  36:              //check for the commands
  37:              bool cmdExists = false;
  38:              foreach (Command cmd in _applicationObject.Commands)
  39:              {
  40:                  if (cmd.Name.EndsWith("PasteAsComment", StringComparison.OrdinalIgnoreCase))
  41:                  {
  42:                      cmdExists = true;
  43:                      break;
  44:                  }
  45:              }
  46:   
  47:              try
  48:              {
  49:                  if (!cmdExists)
  50:                  {
  51:                      AddPasteAsCommands();
  52:                  }
  53:   
  54:                  if (connectMode == ext_ConnectMode.ext_cm_Startup && _pasteAsPopup == null)
  55:                  {
  56:                      //Add items to the Context (Right-Click) Menu
  57:                      //find the position of the &Paste command
  58:                      int position = 0;
  59:   
  60:                      CommandBar codeWindow = _applicationObject.CommandBars["Code Window"];
  61:   
  62:                      for (int i = 1; i <= codeWindow.Controls.Count; i++)
  63:                      {
  64:                          if (codeWindow.Controls[i].Caption == "&Paste")
  65:                          {
  66:                              position = i;
  67:                              break;
  68:                          }
  69:                      }
  70:   
  71:                      //add the popup menu "Paste As...", which may already be on the menu
  72:                      _pasteAsPopup = (CommandBarPopup)codeWindow.Controls.Add(
(int)MsoControlType.msoControlPopup, 1, Type.Missing, position + 1, Type.Missing);
  73:                      _pasteAsPopup.Caption = "Paste As ...";
  74:                      AddPasteAsButtons();
  75:                  }
  76:   
  77:              }
  78:              catch (Exception ex)
  79:              {
  80:                  System.Diagnostics.Debug.WriteLine(ex.Message);
  81:              }
  82:          }
  83:   
  84:          private void AddPasteAsCommands()
  85:          {
  86:              //no configure or regionize because I never use 'em
  87:              _applicationObject.Commands.AddNamedCommand(_addInInstance, "PasteAsComment", "Paste As Comment", "Pastes clipboard text as a comment.", true, 22, null, Convert.ToInt32(vsCommandStatus.vsCommandStatusSupported) + Convert.ToInt32(vsCommandStatus.vsCommandStatusEnabled));
  88:   
  89:              _applicationObject.Commands.AddNamedCommand(_addInInstance, "PasteAsString", "Paste As String", "Pastes clipboard text as a string literal.", true, 22, null, Convert.ToInt32(vsCommandStatus.vsCommandStatusSupported) + Convert.ToInt32(vsCommandStatus.vsCommandStatusEnabled));
  90:   
  91:              _applicationObject.Commands.AddNamedCommand(_addInInstance, "PasteAsStringBuilder", "Paste As StringBuilder", "Pastes clipboard text as a stringbuilder.", true, 22, null, Convert.ToInt32(vsCommandStatus.vsCommandStatusSupported) + Convert.ToInt32(vsCommandStatus.vsCommandStatusEnabled));
  92:          }
  93:   
  94:   
  95:          private void AddPasteAsButtons()
  96:          {
  97:   
  98:              //now the buttons
  99:              CommandBarButton pasteAsButton;
 100:   
 101:              //add "Comment"
 102:              pasteAsButton = AddCommandButton();
 103:              pasteAsButton.Caption = "Comment";
 104:              pasteAsButton.TooltipText = "Inserts clipboard with each line prefixed with a comment character";
 105:              pasteAsButton.Click += PasteAsComment;
 106:              _pasteAsButtons.Add(pasteAsButton);
 107:   
 108:              //add "String"
 109:              pasteAsButton = AddCommandButton();
 110:              pasteAsButton.Caption = "String";
 111:              pasteAsButton.TooltipText = "Inserts enquoted clipboard text with line breaks and other characters escaped";
 112:              pasteAsButton.Click += PasteAsString;
 113:              _pasteAsButtons.Add(pasteAsButton);
 114:   
 115:              //add "StringBuilder"
 116:              pasteAsButton = AddCommandButton();
 117:              pasteAsButton.Caption = "StringBuilder";
 118:              pasteAsButton.TooltipText = "Inserts enquoted clipboard text built up by a stringbuilder.";
 119:              pasteAsButton.Click += PasteAsStringBuilder;
 120:              _pasteAsButtons.Add(pasteAsButton);
 121:   
 122:          }
 123:   
 124:          private CommandBarButton AddCommandButton()
 125:          {
 126:              var pasteAsButton = (CommandBarButton)_pasteAsPopup.Controls.Add((int)MsoControlType.msoControlButton);
 127:              //in 2010, CommandBarButton.FaceId throws a DeprecatedException.
 128:              //pasteAsButton.FaceId = 22;
 129:              pasteAsButton.Visible = true;
 130:              return pasteAsButton;
 131:          }
 132:   
 133:          #region "PasteAs Handlers"
 134:   
 135:          ///<summary>
 136:          ///Occurs when the user clicks the PasteAsString button.
 137:          ///</summary>
 138:          ///<param name="ctrl">
 139:          ///Denotes the CommandBarButton control that initiated the event. 
 140:          ///</param>
 141:          ///<param name="cancelDefault">
 142:          ///False if the default behavior associated with the CommandBarButton control occurs, unless its canceled by another process or add-in. 
 143:          ///</param>
 144:          private void PasteAsString(CommandBarButton ctrl, ref bool cancelDefault)
 145:          {
 146:              _smartPaster.PasteAsString(_applicationObject);
 147:          }
 148:   
 149:   
 150:          ///<summary>
 151:          ///Occurs when the user clicks the PasteAsComment button.
 152:          ///</summary>
 153:          ///<param name="ctrl">
 154:          ///Denotes the CommandBarButton control that initiated the event. 
 155:          ///</param>
 156:          ///<param name="cancelDefault">
 157:          ///False if the default behavior associated with the CommandBarButton control occurs, unless its canceled by another process or add-in. 
 158:          ///</param>
 159:          private void PasteAsComment(CommandBarButton ctrl, ref bool cancelDefault)
 160:          {
 161:              _smartPaster.PasteAsComment(_applicationObject);
 162:          }
 163:   
 164:          ///<summary>
 165:          ///Occurs when the user clicks the PasteAsStringBuilder button.
 166:          ///</summary>
 167:          ///<param name="ctrl">
 168:          ///Denotes the CommandBarButton control that initiated the event. 
 169:          ///</param>
 170:          ///<param name="cancelDefault">
 171:          ///False if the default behavior associated with the CommandBarButton control occurs, unless its canceled by another process or add-in. 
 172:          ///</param>
 173:          private void PasteAsStringBuilder(CommandBarButton ctrl, ref bool cancelDefault)
 174:          {
 175:              _smartPaster.PasteAsStringBuilder(_applicationObject);
 176:          }
 177:          #endregion
 178:   
 179:          #region "Exec"
 180:   
 181:          ///<summary>
 182:          /// Implements the Exec method of the IDTCommandTarget interface.
 183:          /// This is called when the command is invoked.
 184:          ///</summary>
 185:          ///<param name='commandName'>
 186:          ///        The name of the command to execute.
 187:          ///</param>
 188:          ///<param name='executeOption'>
 189:          ///        Describes how the command should be run.
 190:          ///</param>
 191:          ///<param name='varIn'>
 192:          ///        Parameters passed from the caller to the command handler.
 193:          ///</param>
 194:          ///<param name='varOut'>
 195:          ///        Parameters passed from the command handler to the caller.
 196:          ///</param>
 197:          ///<param name='handled'>
 198:          ///        Informs the caller if the command was handled or not.
 199:          ///</param>
 200:          ///<seealso class='Exec' />
 201:          public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
 202:          {
 203:              handled = false;
 204:              if ((executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault))
 205:              {
 206:                  handled = true;
 207:                  switch (commandName)
 208:                  {
 209:                      //case "SmartPaster.Connect.Configure":
 210:                      //    //show the config form
 211:                      //    SmartPasterForm myForm = new SmartPasterForm();
 212:                      //    myForm.ShowDialog();
 213:                      //    //since config may have changed, show/hide buttons
 214:                      //    EnableContextMenuButtons();
 215:   
 216:                      //    break;
 217:                      case "SmartPaster.Connect.PasteAsComment":
 218:                          _smartPaster.PasteAsComment(_applicationObject);
 219:                          break;
 220:                      case "SmartPaster.Connect.PasteAsString":
 221:                          _smartPaster.PasteAsString(_applicationObject);
 222:                          break;
 223:                      case "SmartPaster.Connect.PasteAsStringBuilder":
 224:                          _smartPaster.PasteAsStringBuilder(_applicationObject);
 225:                          break;
 226:                      //case "SmartPaster.Connect.PasteAsRegion":
 227:                      //    _smartPaster.PasteAsRegion(_applicationObject);
 228:                      //    break;
 229:                      default:
 230:                          handled = false;
 231:                          break;
 232:                  }
 233:              }
 234:          }
 235:          #endregion
 236:   
 237:          #region Standard Template Stuff
 238:          ///<summary>
 239:          ///Implements the OnDisconnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being unloaded.
 240:          ///</summary>
 241:          ///<param name="disconnectMode">The disconnect mode.</param>
 242:          ///<param name="custom">The custom.</param>
 243:          ///<seealso class="IDTExtensibility2"/>
 244:          public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
 245:          {
 246:              if (_pasteAsPopup != null && 
 247:                  (disconnectMode == ext_DisconnectMode.ext_dm_UserClosed || disconnectMode == ext_DisconnectMode.ext_dm_HostShutdown))
 248:              {
 249:                  _pasteAsPopup.Delete(true);
 250:              }
 251:          }
 252:   
 253:          ///<summary>Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. Receives notification when the collection of Add-ins has changed.</summary>
 254:          ///<param term='custom'>Array of parameters that are host application specific.</param>
 255:          ///<seealso class='IDTExtensibility2' />        
 256:          public void OnAddInsUpdate(ref Array custom)
 257:          {
 258:          }
 259:   
 260:          ///<summary>Implements the OnStartupComplete method of the IDTExtensibility2 interface. Receives notification that the host application has completed loading.</summary>
 261:          ///<param term='custom'>Array of parameters that are host application specific.</param>
 262:          ///<seealso class='IDTExtensibility2' />
 263:          public void OnStartupComplete(ref Array custom)
 264:          {
 265:          }
 266:   
 267:          ///<summary>Implements the OnBeginShutdown method of the IDTExtensibility2 interface. Receives notification that the host application is being unloaded.</summary>
 268:          ///<param term='custom'>Array of parameters that are host application specific.</param>
 269:          ///<seealso class='IDTExtensibility2' />
 270:          public void OnBeginShutdown(ref Array custom)
 271:          {
 272:          }
 273:   
 274:          private DTE2 _applicationObject;
 275:          private AddIn _addInInstance;
 276:          #endregion
 277:   
 278:          ///<summary>
 279:          ///Queries the status.
 280:          ///</summary>
 281:          ///<param name="commandName">Name of the command.</param>
 282:          ///<param name="neededText">The needed text.</param>
 283:          ///<param name="statusOption">The status option.</param>
 284:          ///<param name="commandText">The command text.</param>
 285:          public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus statusOption, ref object commandText)
 286:          {
 287:              if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
 288:              {
 289:                  if (commandName.StartsWith("SmartPaster.Connect"))
 290:                  {
 291:                      if (((_applicationObject.ActiveDocument != null)) && ((_applicationObject.ActiveDocument.Object("TextDocument") != null)))
 292:                      {
 293:                          statusOption = vsCommandStatus.vsCommandStatusEnabled | vsCommandStatus.vsCommandStatusSupported;
 294:                      }
 295:                      else
 296:                      {
 297:                          statusOption = vsCommandStatus.vsCommandStatusSupported;
 298:                      }
 299:                  }
 300:                  else
 301:                  {
 302:                      statusOption = vsCommandStatus.vsCommandStatusUnsupported;
 303:                  }
 304:              }
 305:          }
 306:      }
 307:  }

Here's the SmartPaster class.

   1:  using System;
   2:  using System.IO;
   3:  using System.Text;
   4:  using System.Windows.Forms; //clipboard
   5:  using EnvDTE;
   6:  using EnvDTE80;
   7:   
   8:  namespace SmartPaster2010
   9:  {
  10:      /// <summary>
  11:      /// Class responsible for doing the pasting/manipulating of clipdata.
  12:      /// </summary>
  13:      internal sealed class SmartPaster
  14:      {
  15:          /// <summary>
  16:          ///  Convient property to retrieve the clipboard text from the clipboard
  17:          /// </summary>
  18:          private static string ClipboardText
  19:          {
  20:              get
  21:              {
  22:                  IDataObject iData = Clipboard.GetDataObject();
  23:                  if (iData.GetDataPresent(DataFormats.Text))
  24:                      return Convert.ToString(iData.GetData(DataFormats.Text));
  25:                  return string.Empty;
  26:              }
  27:          }
  28:   
  29:          #region "Stringinize"
  30:          /// <summary>
  31:          /// Stringinizes text passed to it for use in C#
  32:          /// </summary>
  33:          /// <param name="txt">Text to be Stringinized</param>
  34:          /// <returns>C# Stringinized text</returns>
  35:          private static string StringinizeInCs(string txt)
  36:          {
  37:              //c# quote character -- really just a "
  38:              const string qChr = "\"";
  39:   
  40:              //sb to work with
  41:              var sb = new StringBuilder(txt);
  42:   
  43:              //escape appropriately
  44:              //escape the quotes with ""
  45:              sb.Replace(qChr, qChr + qChr);
  46:   
  47:              //insert " at beginning and end
  48:              sb.Insert(0, "@" + qChr);
  49:              sb.Append(qChr);
  50:              return sb.ToString();
  51:          }
  52:          #endregion
  53:   
  54:          #region "Commentize"
  55:          /// <summary>
  56:          /// Commentizes text passed to it for use in C#
  57:          /// </summary>
  58:          /// <param name="txt">Text to be Stringinized</param>
  59:          /// <returns>C# Commentized text</returns>
  60:          private static string CommentizeInCs(string txt)
  61:          {
  62:              const string cmtChar = "//";
  63:   
  64:              var sb = new StringBuilder(txt.Length);
  65:   
  66:              //process the passed string (txt), one line at a time
  67:              //the original was horrible WTF code
  68:              using (var reader = new StringReader(txt))
  69:              {
  70:                  string line;
  71:                  while ((line = reader.ReadLine()) != null)
  72:                  {
  73:                      sb.AppendLine(cmtChar + line);
  74:                  }
  75:              }
  76:   
  77:              return sb.ToString();
  78:          }
  79:          #endregion
  80:   
  81:          #region "Stringbuilderize"
  82:          private static string StringbuilderizeInCs(string txt, string sbName)
  83:          {
  84:              //c# quote character -- really just a "
  85:              const string qChr = "\"";
  86:   
  87:              //sb to work with
  88:              var sb = new StringBuilder(txt);
  89:   
  90:              //escape \,", and {}
  91:              sb.Replace(qChr, qChr + qChr);
  92:   
  93:              //process the passed string (txt), one line at a time
  94:   
  95:              //dump the stringbuilder into a temp string
  96:              string fullString = sb.ToString();
  97:              sb.Clear(); //lovely .net 4 - sb.Remove(0, sb.Length);
  98:   
  99:              //the original was horrible WTF code
 100:              using (var reader = new StringReader(fullString))
 101:              {
 102:                  string line;
 103:                  while ((line = reader.ReadLine()) != null)
 104:                  {
 105:                      sb.Append(sbName + ".AppendLine(");
 106:                      sb.Append("@" + qChr);
 107:                      sb.Append(line.Replace("\t", "\\t"));
 108:                      sb.AppendLine(qChr + ");");
 109:                  }
 110:              }
 111:   
 112:              //TODO: Better '@"" + ' replacement to not cover inside strings
 113:              sb.Replace("@" + qChr + qChr + " + ", "");
 114:   
 115:              //add the dec statement
 116:              sb.Insert(0, "StringBuilder " + sbName + " = new StringBuilder(" + txt.Length + ");" + Environment.NewLine);
 117:   
 118:              //and return
 119:              return sb.ToString();
 120:          }
 121:          #endregion
 122:   
 123:          /// <summary>
 124:          /// Inserts text at current cursor location in the application
 125:          /// </summary>
 126:          /// <param name="application">application with activewindow</param>
 127:          /// <param name="text">text to insert</param>
 128:          private static void Paste(DTE2 application, string text)
 129:          {
 130:              //get the text document
 131:              var txt = (TextDocument)application.ActiveDocument.Object("TextDocument");
 132:   
 133:              //get an edit point
 134:              EditPoint ep = txt.Selection.ActivePoint.CreateEditPoint();
 135:   
 136:              //get a start point
 137:              EditPoint sp = txt.Selection.ActivePoint.CreateEditPoint();
 138:   
 139:              //open the undo context
 140:              bool isOpen = application.UndoContext.IsOpen;
 141:              if (!isOpen)
 142:                  application.UndoContext.Open("SmartPaster");
 143:   
 144:              //clear the selection
 145:              if (!txt.Selection.IsEmpty)
 146:                  txt.Selection.Delete();
 147:   
 148:              //insert the text
 149:              //ep.Insert(Indent(text, ep.LineCharOffset))
 150:              ep.Insert(text);
 151:   
 152:              //smart format
 153:              sp.SmartFormat(ep);
 154:   
 155:              //close the context
 156:              if (!isOpen)
 157:                  application.UndoContext.Close();
 158:          }
 159:   
 160:          #region "Paste As ..."
 161:   
 162:          /// <summary>
 163:          /// Public method to paste and format clipboard text as string the cursor 
 164:          /// location for the configured or active window's langage .
 165:          /// </summary>
 166:          /// <param name="application">application to insert</param>
 167:          public void PasteAsString(DTE2 application)
 168:          {
 169:              Paste(application, StringinizeInCs(ClipboardText));
 170:          }
 171:   
 172:          /// <summary>
 173:          /// Public method to paste and format clipboard text as comment the cursor 
 174:          /// location for the configured or active window's langage .
 175:          /// </summary>
 176:          /// <param name="application">application to insert</param>
 177:          public void PasteAsComment(DTE2 application)
 178:          {
 179:              Paste(application, CommentizeInCs(ClipboardText));
 180:          }
 181:   
 182:   
 183:          /// <summary>
 184:          /// Public method to paste format clipboard text into a specified region
 185:          /// </summary>
 186:          /// <param name="application">application to insert</param>
 187:          public void PasteAsRegion(DTE2 application)
 188:          {
 189:              //get the region name
 190:              const string region = "myRegion";
 191:   
 192:              //it's so simple, we really don't need a function
 193:              string csRegionized = "#region " + region + Environment.NewLine + ClipboardText + Environment.NewLine + "#endregion";
 194:   
 195:              //and paste
 196:              Paste(application, csRegionized);
 197:          }
 198:   
 199:          /// <summary>
 200:          /// Public method to paste and format clipboard text as stringbuilder the cursor 
 201:          /// location for the configured or active window's langage .
 202:          /// </summary>
 203:          /// <param name="application">application to insert</param>
 204:          public void PasteAsStringBuilder(DTE2 application)
 205:          {
 206:              const string stringbuilder = "sb";
 207:              Paste(application, StringbuilderizeInCs(ClipboardText, stringbuilder));
 208:          }
 209:   
 210:          #endregion
 211:      }
 212:  }
posted on Sunday, June 06, 2010 5:30:02 PM (Romance Daylight Time, UTC+02:00)  #    Comments [3]
# Friday, April 02, 2010
"Covariance" = specified or MORE derived type

So upcasting to a more basic class in polymorphism is covariant.         
object
s = new string('a', 3);


But you couldn't do it for IEnumerables, even though it seemed safe. Well, now in .net 4 you can.

IList<string> stringList = new List<string>(); //will not compile in .net 1-3, but does in net 4 IEnumerable<object> objectList = stringList;
Trivia: since .net 1, arrays were covariant.
object[] data = new string[3];
data[0] = "s";
data[0] = 1; //no compiler error- just a runtime ArrayTypeMismatchException
Now IEnumerable<T> and IQueryable<T> are covariant.

IList<Cat> cats = new List<Cat>();
//you can cast to IEnumerable (or IQueryable)
IEnumerable<Animal> pets = cats;
//but you cannot cast to the read-write classes
//IList<Animal> animals = cats; //compiler error
Covariant interfaces must be read-only (and not value types).

Contravariance = specified or LESS derived type
 
var cats = new List<Cat>();
//predicate is contravariant
cats.Find(HasFur); //private static bool HasFur(Animal animal)
//so is IComparable
cats.Sort(SortByName); //private static int SortByName(Animal x, Animal y)

posted on Friday, April 02, 2010 11:10:48 AM (Romance Daylight Time, UTC+02:00)  #    Comments [0]