Development With A Dot

Blog on development in general, and specifically on .NET

Sponsors

News

My Friends

My Links

Permanent Posts

Portuguese Communities

November 2010 - Posts

CachingCallHandler and Unity 2.0

Unity 2.0 no longer includes CachingCallHandler, as you can see in here. Since this is something I use very often, I decided to bring it back, with some changes:

  • It no longer uses ASP.NET cache, but the new MemoryCache
  • The current process is part of the cache key
  • The full method signature is included as part of the key, not just it's name

Otherwise, it is based on the source code from Enterprise Library 4.1.


	[Serializable]
	[ConfigurationElementType(typeof(CustomCallHandlerData))]
	[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
	public sealed class CachingCallHandlerAttribute : HandlerAttribute, ICallHandler
	{
		#region Private fields
		private readonly Guid KeyGuid = new Guid("ECFD1B0F-0CBA-4AA1-89A0-179B636381CA");
		private TimeSpan expirationTime = new TimeSpan(0, 5, 0);
		#endregion

		#region Public Constructors
		public CachingCallHandlerAttribute()
		{
		}

		public CachingCallHandlerAttribute(Int32 hours, Int32 minutes, Int32 seconds)
		{
			this.expirationTime = new TimeSpan(hours, minutes, seconds);
		}
		#endregion

		#region Public override methods
		public override ICallHandler CreateHandler(IUnityContainer ignored)
		{
			return (this);
		}
		#endregion

		#region ICallHandler Members

		public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
		{
			if (this.targetMethodReturnsVoid(input) == true)
			{
				return (getNext()(input, getNext));
			}

			Object [] inputs = new Object [ input.Inputs.Count ];
			
			for (Int32 i = 0; i < inputs.Length; ++i)
			{
				inputs [ i ] = input.Inputs [ i ];
			}

			String cacheKey = this.createCacheKey(input.MethodBase, inputs);
			ObjectCache cache = MemoryCache.Default;
			Object [] cachedResult = (Object []) cache.Get(cacheKey);

			if (cachedResult == null)
			{
				IMethodReturn realReturn = getNext()(input, getNext);
				
				if (realReturn.Exception == null)
				{
					this.addToCache(cacheKey, realReturn.ReturnValue);
				}

				return (realReturn);
			}

			IMethodReturn cachedReturn = input.CreateMethodReturn(cachedResult [ 0 ], input.Arguments);
			
			return (cachedReturn);
		}

		#endregion

		#region Private methods
		private Boolean targetMethodReturnsVoid(IMethodInvocation input)
		{
			MethodInfo targetMethod = input.MethodBase as MethodInfo;
			return ((targetMethod != null) && (targetMethod.ReturnType == typeof(void)));
		}

		private void addToCache(String key, Object value)
		{
			ObjectCache cache = MemoryCache.Default;
			Object [] cacheValue = new Object [] { value };
			cache.Add(key, cacheValue, DateTime.Now + this.expirationTime);
		}

		private String createCacheKey(MethodBase method, params Object [] inputs)
		{
			StringBuilder sb = new StringBuilder();
			sb.AppendFormat("{0}:", Process.GetCurrentProcess().Id);
			sb.AppendFormat("{0}:", KeyGuid);
				
			if (method.DeclaringType != null)
			{
				sb.Append(method.DeclaringType.FullName);
			}
				
			sb.Append(':');
			sb.Append(method);

			if (inputs != null)
			{
				foreach (Object input in inputs)
				{
					sb.Append(':');
						
					if (input != null)
					{
						sb.Append(input.GetHashCode().ToString());
					}
				}
			}

			return (sb.ToString());
		}

		#endregion
	}


Bookmark and Share
Common Service Locator for MAF

Here's an implementation of the Common Service Locator for MAF:


	public class MafServiceLocator : IServiceLocator
	{
		#region Public constructor
		public MafServiceLocator()
		{
			this.Path = Environment.CurrentDirectory;
			this.Level = AddInSecurityLevel.FullTrust;
		}
		#endregion

		#region Public properties
		public String Path
		{
			get;
			set;
		}

		public AddInSecurityLevel Level
		{
			get;
			set;
		}
		#endregion

		#region IServiceLocator Members

		public IEnumerable<TService> GetAllInstances<TService>()
		{			
			AddInStore.Update(this.Path);

			foreach (AddInToken token in AddInStore.FindAddIns(typeof(TService), this.Path))
			{
				yield return (token.Activate<TService>(this.Level));
			}
		}

		public IEnumerable<Object>GetAllInstances(Type serviceType)
		{
			AddInStore.Update(this.Path);

			foreach (AddInToken token in AddInStore.FindAddIns(serviceType, this.Path))
			{
				yield return (token.Activate<Object>(this.Level));
			}
		}

		public TService GetInstance<TService>(String key)
		{
			AddInStore.Update(this.Path);

			foreach (AddInToken token in AddInStore.FindAddIns(typeof(TService), this.Path))
			{
				if (token.Name == key)
				{
					return (token.Activate<TService>(this.Level));
				}
			}

			return (default(TService));
		}

		public TService GetInstance<TService>()
		{
			AddInStore.Update(this.Path);

			foreach (AddInToken token in AddInStore.FindAddIns(typeof(TService), this.Path))
			{
				return (token.Activate<TService>(this.Level));
			}

			return (default(TService));
		}

		public Object GetInstance(Type serviceType, String key)
		{
			AddInStore.Update(this.Path);

			foreach (AddInToken token in AddInStore.FindAddIns(serviceType, this.Path))
			{
				if (token.Name == key)
				{
					return (token.Activate<Object>(this.Level));
				}
			}

			return (null);
		}

		public Object GetInstance(Type serviceType)
		{
			AddInStore.Update(this.Path);

			foreach (AddInToken token in AddInStore.FindAddIns(serviceType, this.Path))
			{
				return (token.Activate<Object>(this.Level));
			}

			return (null);
		}

		#endregion

		#region IServiceProvider Members

		public Object GetService(Type serviceType)
		{
			AddInStore.Update(this.Path);

			return (this.GetInstance(serviceType));
		}

		#endregion
	}

	ServiceLocator.SetLocatorProvider(() => new MafServiceLocator());

	IEnumerable<IOperation> operations = ServiceLocator.Current.GetAllInstances<IOperation>();

	IOperation op = ServiceLocator.Current.GetService(typeof(IOperation)) as IOperation;

Bookmark and Share
Posted: Nov 27 2010, 01:21 PM by Ricardo Peres | with no comments
Filed under: ,
Reusing Cookies in Different WCF Web Services

You probably know that to have cookie-based sessions in your WCF service, you have to use one of the *ContextBinding bindings, such as BasicHttpContextBinding. But what if you want to share cookies (such as authentication) among different services? Enter CookieContext and CookieBehaviorAttribute.

First, I created a simple behavior attribute, CookieBehaviorAttribute. that you should apply to on your contract interface. All operations to services with that attribute that are placed inside a CookieContext will share the its cookies. Here's the code:


	//sample service interface declaration
	[CookieBehavior(true)]
	[ServiceContract(Name = "AuthenticationService", Namespace = @"http://asp.net/ApplicationServices/v20")]
	public interface IAuthenticationService
	{
		[OperationContract]
		Boolean IsLoggedIn();

		[OperationContract]
		Boolean Login(String username, String password, String customCredential, Boolean isPersistent);

		[OperationContract]
		void Logout();

		[OperationContract]
		Boolean ValidateUser(String username, String password, String customCredential);
	}

	//behavior attribute
	[Serializable]
	[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
	public sealed class CookieBehaviorAttribute : Attribute, IContractBehavior, IEndpointBehavior, IClientMessageInspector
	{
		#region Private fields
		private CookieContainer cookieContainer = new CookieContainer();
		#endregion

		#region Public constructors
		public CookieBehaviorAttribute() : this(false)
		{
		}

		public CookieBehaviorAttribute(Boolean shared)
		{
			this.Shared = shared;
		}
		#endregion

		#region Public properties
		public Boolean Shared
		{
			get;
			private set;
		}
		#endregion

		#region Private methods
		private void getCookies(HttpResponseMessageProperty prop, CookieContainer cookieContainer)
		{
			if (prop != null)
			{
				String header = prop.Headers [ HttpResponseHeader.SetCookie ];

				if (header != null)
				{
					cookieContainer.SetCookies(new Uri(@"http://someuri.tld"), header);
				}
			}
		}

		private void setCookies(HttpRequestMessageProperty prop, CookieContainer cookieContainer)
		{
			if (prop != null)
			{
				prop.Headers.Add(HttpRequestHeader.Cookie, cookieContainer.GetCookieHeader(new Uri(@"http://someuri.tld")));
			}
		}
		#endregion

		#region IClientMessageInspector Members

		void IClientMessageInspector.AfterReceiveReply(ref Message reply, Object correlationState)
		{
			HttpResponseMessageProperty prop = reply.Properties [ HttpResponseMessageProperty.Name.ToString() ] as HttpResponseMessageProperty;

			this.getCookies(prop, (this.Shared == true) ? CookieContext.Current.cookieContainer : this.cookieContainer);
		}

		Object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
		{
			HttpRequestMessageProperty prop = request.Properties [ HttpRequestMessageProperty.Name.ToString() ] as HttpRequestMessageProperty;

			this.setCookies(prop, (this.Shared == true) ? CookieContext.Current.cookieContainer : this.cookieContainer);
			
			return (null);
		}

		#endregion

		#region IEndpointBehavior Members

		void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
		{
		}

		void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
		{
			clientRuntime.MessageInspectors.Add(this);
		}

		void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
		{
		}

		void IEndpointBehavior.Validate(ServiceEndpoint endpoint)
		{
		}

		#endregion

		#region IContractBehavior Members

		void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
		{
		}

		void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
		{
			endpoint.Behaviors.Add(this);
		}

		void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
		{
		}

		void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
		{
		}

		#endregion
	}

	//cookie context
	public class CookieContext: IDisposable
	{
		#region Internal fields
		internal CookieContainer cookieContainer = new CookieContainer();
		#endregion
		
		#region Private static fields
		[ThreadStatic]
		private static CookieContext current = null;
		#endregion

		#region Public static constructor
		public CookieContext()
		{
			current = this;
		}
		#endregion

		#region Public static properties
		public static CookieContext Current
		{
			get
			{
				return (current);
			}
		}
		#endregion

		#region IDisposable Members

		void IDisposable.Dispose()
		{
			this.cookieContainer.SetCookies(new Uri(@"http://someuri.tld"), String.Empty);
			this.cookieContainer = null;
			current = null;
		}

		#endregion
	}

And some sample code:


			IAuthenticationService auth1 = ChannelFactory<IAuthenticationService>.CreateChannel(new BasicHttpBinding/*BasicHttpContextBinding*/(), new EndpointAddress(@"http://localhost:34744/AuthenticationService.svc"));
			IAuthenticationService auth2 = ChannelFactory<IAuthenticationService>.CreateChannel(new BasicHttpBinding/*BasicHttpContextBinding*/(), new EndpointAddress(@"http://localhost:34744/AuthenticationService.svc"));

			using (new CookieContext())
			{
				Boolean authenticated = auth1.Login("username", "password", String.Empty, true);

				Boolean loggedIn1 = auth1.IsLoggedIn();	//true if the username and password are valid

				Boolean loggedIn2 = auth2.IsLoggedIn();	//also true
			}

Note that, in order to share cookies, the CookieBehaviorAttribute.Shared property must be true and all calls must be inside a CookieContext scope. Otherwise, this can be used to support cookies in otherwise cookie-ignorant bindings, such as BasicHttpBinding. Bookmark and Share

Server-side Validator

I have talked before about server-side validation using the ASP.NET validation framework. A typical scenario would be, for example, to validate the existance of a given username, in a registration form. This time, I present here a much simpler way, which is self contained and doesn't even require a ScriptManager.

Basically, it is a class that extends CustomValidator and adds a JavaScript AJAX (or, should I say, SJAX, since it is synchronous) call to the server. If an event handler for the ServerValidate event exists, it is given the chance to invalidate the validator. No client-side validation is performed (the ClientValidationFunction is ignored).

First, the code:


	public class ServerValidator : CustomValidator, IPostBackEventHandler
	{
		protected override void OnPreRender(EventArgs e)
		{
			if ((this.Visible == true) && (this.Enabled == true))
			{
				String script = "\nfunction ValidateServerValidator(sender, args)\n" +
								"{\n" +
								"	var xhr = null;\n" +
								"	if (window.XMLHttpRequest)\n" +
								"	{\n" +
								"		xhr = new XMLHttpRequest();\n" +
								"	}\n" +
								"	else\n" +
								"	{\n" +
								"		xhr = new ActiveXObject('Microsoft.XMLHTTP');\n" +
								"	}\n" +
								"	xhr.open('POST', document.location.href, false);\n" +
								"	xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');\n" +
								"	xhr.setRequestHeader('x-microsoftajax', 'Delta=true');\n" +
								"	xhr.setRequestHeader('x-requested-with', 'XMLHttpRequest');\n" +
								"	xhr.send('__ASYNCPOST=true&__EVENTARGUMENT=' + escape(args.Value) + '&__EVENTTARGET=' + sender.id.replace('_', '%24'));\n" +
								"	var array = xhr.responseText.split('|');\n" +
								"	args.IsValid = (array[0] == 'true');\n" +
								"}\n";

				this.ClientValidationFunction = "ValidateServerValidator";
				this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "ValidateServerValidator", script, true);
			}

			base.OnPreRender(e);
		}

		protected override void OnInit(EventArgs e)
		{
			if ((this.Visible == true) && (this.Enabled == true))
			{
				this.Page.RegisterRequiresRaiseEvent(this);
			}

			base.OnInit(e);
		}

		#region IPostBackEventHandler Members

		void IPostBackEventHandler.RaisePostBackEvent(String eventArgument)
		{
			this.Context.Response.Clear();
			this.Context.Response.Write(String.Concat(this.OnServerValidate(this.Context.Request.Form [ "__EVENTARGUMENT" ]).ToString().ToLower(), "|"));
		}

		#endregion
	}

Now an example declaration on an ASPX page:


<asp:TextBox runat="server" ID="username"/>
<web:ServerValidator runat="server" ControlToValidate="username" ErrorMessage="Username already taken" OnServerValidate="CheckUsername"/>
<asp:Button runat="server" Text="Check Valid Username" />

Hope you enjoy it!

Bookmark and Share
NHibernate 3.0 Cookbook Review

Once again, Packt Publishing offered me a chance to review one of their books; this time, it was NHibernate 3.0 Cookbook. I must say I liked what I read.

The book was written by Jason Dentler, has 328 pages, some of its technical reviewers are well known in the NHibernate community (Fabio Maulo, Jose F. Romaniello, Gabriel Nicolas Schenker, Tuna Toksoz) and is divided into 8 chapters plus an appendix, which you can find here.

It is not a beginner's book, some basic topics are not covered, ID generators coming to my mind. It follows a now classic cookbook style, where each chapter contains a list of recipes. These range from basic to slightly more advanced, none is what I'd call advanced. Each chapter is sequenced naturally, the more basic coming in first. I like the fact that the book doesn't cover only the NHibernate library, but also some of it's most useful sidekicks: for example, the chapter on Models and Mappings covers not only XML mappings but also FluentNHibernate and also ConfORM. However, I don't like the fact that it is based on SQL Server 2008, I think the author should have tried to, at least, support Oracle. Each recipe starts with a use case and then goes on to a (not detailed, which is fine, since this is not for beginners) explanation on how to achieve it.

The first chapter, Models and Mappings, covers regular mappings, with or without a base class, with XML, FluentNHibernate and ConfORM mappings, components and versioning. It is OK, but I feel, for example, that the recipe Handling versioning and concurrency could have described other forms of versioning, such as datetime-based, SQL Server's TIMESTAMP/ROWVERSION or Oracle's ORA_ROWSCN.

Next comes the Configuration and Schema chapter. Nothing to say here, I think it does a good job, covering configuration by code, XML, FluentNHibernate and ConfORM also. It explains how to set up Log4Net and how to generate and script the database using NHibernate Schema Tool.

Chapter 3, Sessions and Transactions, covers basic session-factory and session-related operations, with particular enphasis on web applications (both ASP.NET WebForms and MVC are discussed). Stateless sessions are also covered as well as the less known EntityMode.Map, which, IMO, is very interesting. Some session advanced topics are missing though, for example, Evict is barely mentioned. It provides good guidance on integrating TransactionScope with NHibernate's native (and required) transactions, and also on using uNHAddins' Conversation per Business Transaction functionality.

The chapter on Queries comes in forth, and mostly covers everything, from QueryOver to Criteria API, including LINQ and HQL. MultiCriteria, MutiQuery and Futures are also discussed. All of these APIs leave a lot to be said, which is understandable because these are vast subjects.

Then comes Testing. NHibernate Profiler is presented first and the rest of the recipes rely on NUnit. The classic ghostbuster test is shown as well as the FluentNHibernate Tester, to check for invalid FluentNHibernate mappings.

Chapter 6 is called Data Access Layer. It it several wrapping techniques for NHibernate are presented, such as an implementation of the Repository Pattern for NHibernate and classic paging techniques with. Last comes integration with LINQ Specifications to allow for fine-grained querying of our model based on LINQ predicates, which is interesting.

The next chapter, 7, Extending NHibernate, contains the most advanced repices. These include using Dependency Injection containers, using event listeners and custom user types and an interesting recipe on overriding the default connection provider to allow dynamic connection strings.

The final chapter (8) talks about NHibernate Contribution Projects: NHContrib. Specifically, validation (NHibernate Validation), other 2nd level cache providers (NHibernate Caches), full text search (NHibernate Search: integration with Lucene.NET), database sharding (NHibernate Shards), persistent conversations (NHibernate Burrows) and spatial data support (NHibernate Spatial) is covered, which are some of the most used NHibernate libraries.

The book ends with an appendix where, by topic, we are referenced to the appropriate recipes. Topics are divided in ASP.NET WebForms, ASP.NET MVC and Windows Forms.

In conclusion, I liked this book. I think it is a welcome addition to the NHibernate library, once again by Packt Publishing (where are the others?). It lacks some advanced concepts, but it does a good job presenting it's solutions. Some, however, could be better explained, and at least Oracle should also be mentioned.

Bookmark and Share
Exposing a Class as a COM+ WCF Service

Remember COM+? Well, it is still around, and, in the days of WCF, it still usefull in certain scenarios, mostly because of the services COM+ offers OOTB (transactions, synchronization, object pooling, etc). For those of you that don't know how, I'm gonna demonstrate how you can expose an ordinary .NET class into both a COM+ component and a regular WCF service. Make sure you have Windows SDK 6.x or greater installed.

First, create a class library project in Visual Studio and add a reference to System.EnterpriseServices and System.ServiceModel assemblies. Then create an interface and a class library, with names IComPlusComponent and ComPlusComponent, for example. Here are its contents:


namespace ComPlus
{
	[ServiceContract]					//this interface is a WCF interface
	[Guid("11EF17BF-C276-41ea-AEA1-C371CF7704F1")]
	public interface IComponent
	{
		[OperationContract]				//this method is visible to WCF
		String SayHello(String name);
	}
}

namespace ComPlus
{
	[ProgId("ComPlus.Component")]				//the progid to be used when instantiating this class via COM
	[ClassInterface(ClassInterfaceType.None)]		//no interface will be generated
	[Transaction(TransactionOption.Supported)]		//our components supports transactions
	[Guid("ACC77A18-7F84-4AFA-A42B-8C15E7784BC1")]
	[ComDefaultInterface(typeof(IComPlusComponent))]	//the default interface, not really necessary unless you have more than one
	[Synchronization(SynchronizationOption.Required)]	//synchronization is supported
	public class ComPlusComponent : ServicedComponent, IComPlusComponent
	{
		[AutoComplete(true)]				//the method will automatically commit any existing ambient transaction unless an exception is thrown
		public String SayHello(String name)
		{
			return (String.Format("Hello {0} from {1}", name, Environment.MachineName));
		}
	}
}

Feel free to add your own namespace, by the way, and to generate your own GUIDs, although I guess you can reuse mine.

Next, go to your AssemblyInfo.cs file, add the following attributes and make sure ComVisible is set to true:


[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationName("ComPlus")]
[assembly: ApplicationID("FE8C5234-909E-485E-AF7C-FEFC4729F9DD")]
[assembly: ApplicationAccessControl(AccessChecksLevel = AccessChecksLevelOption.Application, Authentication = AuthenticationOption.None, ImpersonationLevel = ImpersonationLevelOption.Anonymous, Value = false)]

[assembly: ComVisible(true)]

On the project's properties, Build tab, add the following to the Post-build event command line:

  • gacutil /u $(TargetName)
  • gacutil /i $(TargetPath)
  • regsvcs $(TargetPath)

Also on the project's properties, Signing tab, the Sign the assembly checkbox must be checked, and a valid strong name key file must be selected (you can create a new file or reuse an existing one).

Once the project is build and everything is OK, you need to go to the IIS administration and create a virtual path in your default web site. Call it, for example, ComPlus, and have it point to some location in your hard disk, for example, C:\InetPub\WWWRoot\ComPlus.

The final step is running WCF Service Configuration Editor, which ships with Windows SDK, and you can find on the Start Menu, under Microsoft Windows SDK xx\Tools\Service Configuration Editor. Open it and select Integrate on the File menu. Select the ComPlus application and the IComPlusComponent interface of the ComPlus.ComPlusComponent class. Select the SayHello method and the Web hosted hosting mode. Finally, select the IIS virtual directory you created previously (ComPlus).

If all goes well, you can now navigate to http://localhost/ComPlus/ComPlus.ComPlusComponent.svc and you will see the familiar WCF endpoint page. If you need to tweek some parameters, you will find them on the Web.config file under the physical path for the IIS virtual directory.

Bookmark and Share
Posted: Nov 18 2010, 12:19 PM by Ricardo Peres | with no comments
Filed under: , ,
Include LINQ Extension

Updated for NHibernate

Updated thanks to a comment by Samuel Jack! Thanks, Samuel!

As you know, different LINQ O/RMs offer different ways to eagerly fetch associations:

  • NHibernate offers several methods, including the HQL fetch keyword, extension method IQueryable<T>.Fetch and ICriteria.SetFetchMode
  • Entity Framework has ObjectQuery.Include, which, shamelesly, takes a string instead of an expression
  • LINQ to SQL has DataLoadOptions.LoadWith, which has some problems, and DeferredLoadingEnabled, which is all or nothing, and you cannot use on a per-query basis

The extension method below forces loading of any associations (direct or indirect) with the query you specify, and can be fluently combined in order to support multiple associations:


		public static IQueryable<T> Include<T, TInclude>(this IQueryable<T> query, Expression<Func<T, TInclude>> path)  where T: class where TInclude : class
		{
			if (query is ObjectSet<T>)
			{
				//Entity Framework
				Int32 i = path.Body.ToString().IndexOf('.');
				String pathString = path.Body.ToString().Substring(i + 1);
				return ((query as ObjectSet<T>).Include(pathString));
			}
			/*else if (query is NhQueryable<T>)
			{
				//NHibernate
				return((query as NhQueryable<T>).Fetch(path));
			}*/
			else
			{ 
				//LINQ to SQL and others
				ParameterExpression pathParameter = path.Parameters.Single();
				Type tupleType = typeof(Tuple<T, TInclude>);
				Expression<Func<T, Tuple<T, TInclude>>> pathSelector = Expression.Lambda<Func<T, Tuple<T, TInclude>>>(Expression.New(tupleType.GetConstructor(new Type[] { typeof(T), typeof(TInclude) }), new Expression[] { pathParameter, path.Body }, tupleType.GetProperty("Item1"), tupleType.GetProperty("Item2")), pathParameter);
				return (query.Select(pathSelector).Select(t => new { Item1 = t.Item1, Item2 = t.Item2 }).Select(t => t.Item1));
			}
		}

If you are using .NET pre 4.0, you also need to define class Tuple:


		class Tuple<T1, T2>
		{
			public Tuple(T1 item1, T2 item2)
			{
				this.Item1 = item1;
				this.Item2 = item2;
			}

			public T1 Item1
			{
				get;
				private set;
			}

			public T2 Item2
			{
				get;
				private set;
			}
		}

Sample code:


using (MyDataContext ctx = new MyDataContext())
{
	var myItems = from i in ctx.MyItems.Include(itm => itm.MyOtherItems).Include(itm => itm.MyOtherItems.MyAnotherItems)
                      select i;
}

What it does is add a select to the original expression which places the original expression result plus the associated path in a Tuple object, and then only selects the original expression from it. I tested it with LINQ to SQL and Entity Framework, and it seems to be working fine. Feedback is appreciated, as always!

Bookmark and Share
More Posts