Showing posts with label c#. Show all posts
Showing posts with label c#. Show all posts

Wednesday, October 20, 2010

Working with Entity Framework 1.0 and Stored Procedures - map complex data types

Working with Entity Framework 1.0 and Stored Procedures – how to deal with complex data  types
Last months  I worked on a ASP.NET project with Entity Framework.


The key requirement for a project was to use .NET Framework 3.5 not 4.0, so I used older version of EF that was included in .NET Framework 3.5 Service Pack 1.
 I also must use SQL Server with Stored Procedures, instead of great features from the EF - LINQ.
Entity Framework is not a nice tool. But the problem is when I started to use Stored Procedures instead of generated SQL queries from LINQ.

To get started I recommend following articles:

Microsoft documentation - Quickstart (Entity Framework)
ADO.NET Entity Framework Tutorial and Basics:
ADO.NET team blog: Stored Procedure Mapping

Many examples in Microsoft documentation and blog posts does not show, how to deal with complex types by using Entity Framework and Stored Procedures?

The biggest drawback is, that the references are not automatically loaded by creating instances of objects. This means that child collection and child objects references are equal to null.
(This is not a problem when you are using LINQ. You write a LINQ query, SQL query will be generated and objects hierarchy is populated as you need: See example here.)

When working with EF 1.0 with Stored Procedure, you must deal with a problem manually.



1)      Write  “Select”  Stored Procedures  for object type you need and for all object types you need to reference.
- Select SP for root object - that return one data row by ID
- Select SP for single child objects - that return one data row by a parent ID
- Select SP for child collections - that returns a list of data rows by a parent ID

Because references are empty, you need to realize it with SQL Inner Joins, and query objects by a parent ID.

Example:

When we have such object hierarchy:




Select SP for root Account type:

Create Procedure [dbo].[usp_Account_SelectRow]
      @ID bigint
As
Begin
      Select
            [ID],
            [Name],                
            [AccountTypeId],
            [Supervisor],          
            [ManagerName],
            [Created],
            [Modified]
      From Account
      Where
            [ID] = @ID
End

GO

                Select SP for child type Account Type. Here important is that you load AccountType data by Account.ID by using inner join ( 1 to N relation)

Create Procedure [dbo].[usp_AccountType_SelectRowsByAccountId]
 @AccountID bigint
As
Begin
 Select
  AccountType.ID,
  [Code],
  AccountType.Name as Name
 From AccountType inner join Account on AccountType.ID = Account.AccountTypeId
 Where
  Account.ID = @AccountID
End

GO

Select SP for child collection accountObjectives is similar, the difference is, that it return whole list of data rows (N to N relation)
CREATE Procedure [dbo].[usp_AccountObjective_SelectRowsByAccountId]
      @AccountId bigint
As
Begin
      Select
            [ID],
            [AccountId],
            [ProductId],           
            [Titel],
            [Ranking],
            [Created],
            [Modified]
      From AccountObjective
      Where
            [AccountId] = @AccountId
      Order by [Ranking]
End

SET ANSI_NULLS ON

GO

2)      Map all “Select”Stored Procedures to the Methods using “Function Import” in Visual Studio EF Model browser.





3)      Loading root object by using Imported select function. Attach needed child objects and child collections
In this example I load Account object and its referenced AccountType child object.

        public Account FindById(long id)
        {
             SelectedItem = _dataContext.FindAccountById(id).First();           

            var accountType = _dataContext.GetAccountTypeForAccount(id).ToList();
            if(accountType.Any())
            {
                SelectedItem.AccountTypeReference.Attach(accountType.First());  
            }

            if(IsAuthorizedForSelectedAccount)
            {
                return SelectedItem;   
            }
            else
            {
                throw new ApplicationException("The user does not have permission for selected Account.");
            }           
        }

Thursday, November 26, 2009

I started to use features from C# .NET 3.5

I started to use 3 new features i c#.NET 3.5


1) extension method,

I call this  "static  noon-static" :)

such extension method help get user names in one string for the collection

public static string Namen(this List<Benutzer> instance)
{

var namen = new StringBuilder();
foreach (var benutzer in instance)
{

if (namen.Length > 0)
{

namen.Append(", ");

}
namen.Append(benutzer.Vorname);

}
return namen.ToString();

}


2) Auto-Implemented Properties, code snippet in VisualStudio 2008  "prop"

[DataMember]
public stirng Wert { get; set; }

[DataMember]
public string WertTyp { get; set; }


3) implicit type declaration

var masse = new Masse();



Wednesday, November 18, 2009

Consuming WCF services with channel factory



Creating WCF services hosted on IIS server is a very common scenario.
Often service proxy files for a service are generated with help of Visual Studio or by using SvcUtil.exe
Generating proxy is very easy, but I read, that using channel factory is much better and more profesional aproach.

you can read about this on this site:
"...Now we may turn our attention to the client application. To begin, let me start off by reminding everyone that you shouldn't ever use "Add Service Reference" in Visual Studio for magical service client creation. The code is incredibly verbose, hard to manageable, edits are prone to being overwritten, and it's almost always used as an excuse to not actually learn WCF. There are few things worse than having to deal with people who thing they know a product simply because they know how to use a mouse. There are reasons why Juval Lowy, in all his books and talks, repeatedly tells people to avoid using this flawed feature. Fortunately, as professionals, we have the ability to understand how to do things without magic."

For me very important reason is, that I can use my data contract clases from WCF service in my client application. I don't lose functionality implemented in these classes.

Here is my WCF servicve and its client with channel factory

in web application project I inserted service file SVC:

Repository.svc


I skiped implementation of the service class. Important is, that this class implement interface defined in service contract. In this case it is IRepository

WCF configuration in the web application (my service host)





   1:  <service behaviorConfiguration="returnFaults" name="Services.BusinessLayer.Pakete.PaketRepository">
   2:      <endpoint binding="basicHttpBinding" bindingConfiguration="myBinding" contract="Tesa.Etv.Interface.TvAuftrag.ITvAuftragRepository"/>
   3:      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
   4:  service>


Service implementation is hosted on specific web site for example on:

http://localhost/Test/Repository.svc

 now its time for a client.
because we don't generate service proxy file, we can reuse assembly with a contract from the server in a client application.
This way we declare service endpoint in clients configuration file:


   1:  <endpoint address="http://localhost/Test&Repository.svc"
   2:  behaviorConfiguration="Delegation" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding"
   3:  contract="Repository" name="BasicHttpBinding_IRepository" />


Channel factory as a property, to comunicate with service in a web client:




   1:  protected ITvAuftragRepository TvAuftragRepository
   2:  {
   3:      get
   4:      {
   5:          if (Session["_tvService"] == null)
   6:          {
   7:              Session["_tvService"] = new ChannelFactory("BasicHttpBinding_ITvAuftragRepository").CreateChannel();
   8:          }
   9:   
  10:          return Session["_tvService"] as ITvAuftragRepository;
  11:      }
  12:  }
this way wen can comnunicate with WCF from the client application, and reuse classes created for the server interfaces

Thursday, November 12, 2009

WCF use control that require STA thread



My WCF service use WebBrowser .NET control from System.Windows.Forms.

This control requires STA thread:
The WebBrowser class can only be used in threads set to single thread apartment (STA) mode. To use this class, ensure that your Main method is marked with the STAThreadAttribute attribute.

and WCF service is always MTA thread.

to use webBrowser control in my service i found the workaround here:

I just reused this code sample to my needs and it works great!
With this implementation you can run control that requires STA thread in WCF services, that are MTA.

create class:

  1:  [AttributeUsage(AttributeTargets.Method)]
   2:  public class STAOperationBehavior : Attribute, System.ServiceModel.Description.IOperationBehavior
   3:      {
   4:      //- @AddBindingParameters -//
   5:   
   6:      public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
   7:      {
   8:      //+ blank
   9:      }
  10:   
  11:      //- @ApplyClientBehavior -//
  12:      public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
  13:      {
  14:      }
  15:   
  16:      //- @ApplyDispatchBehavior -//
  17:      public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
  18:      {    
  19:          dispatchOperation.Invoker = new STAInvoker(dispatchOperation.Invoker);
  20:      }
  21:   
  22:      //- @Validate -//
  23:      public void Validate(OperationDescription operationDescription)
  24:      {
  25:      //+ blank
  26:      }
  27:  }
  28:   
  29:   
  30:  public class STAInvoker : System.ServiceModel.Dispatcher.IOperationInvoker
  31:  {
  32:      //- $InnerOperationInvoker -//
  33:   
  34:      private IOperationInvoker InnerOperationInvoker { get; set; }
  35:      //+
  36:   
  37:      //- @Ctor -//
  38:      public STAInvoker(IOperationInvoker operationInvoker)
  39:      {
  40:          this.InnerOperationInvoker = operationInvoker;
  41:      }
  42:   
  43:      //+
  44:      //- @AllocateInputs -//
  45:      public Object[] AllocateInputs()
  46:      {
  47:          return InnerOperationInvoker.AllocateInputs();
  48:      }
  49:   
  50:      //- @Invoke -//
  51:      public Object Invoke(Object instance, Object[] inputs, out Object[] outputs)
  52:      {
  53:          Object result = null;
  54:          Object[] staOutputs = null;
  55:   
  56:          System.ServiceModel.OperationContext context = System.ServiceModel.OperationContext.Current;
  57:          System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(delegate
  58:          {
  59:              using (System.ServiceModel.OperationContextScope scope = new System.ServiceModel.OperationContextScope(context))
  60:              {
  61:                  result = InnerOperationInvoker.Invoke(instance, inputs, out staOutputs);
  62:              }
  63:          }));
  64:   
  65:          thread.SetApartmentState(System.Threading.ApartmentState.STA);
  66:          thread.Start();
  67:          thread.Join();
  68:          //+
  69:          outputs = staOutputs;
  70:          //+
  71:          return result;
  72:      }
  73:   
  74:      //- @InvokeBegin -//
  75:      public IAsyncResult InvokeBegin(Object instance, Object[] inputs, AsyncCallback callback, Object state)
  76:      {
  77:          return InnerOperationInvoker.InvokeBegin(instance, inputs, callback, state);
  78:      }
  79:   
  80:      //- @InvokeEnd -//
  81:      public Object InvokeEnd(Object instance, out Object[] outputs, IAsyncResult result)
  82:      {
  83:          return InnerOperationInvoker.InvokeEnd(instance, out outputs, result);
  84:      }
  85:   
  86:      //- @IsSynchronous -//
  87:      public bool IsSynchronous
  88:      {
  89:          get { return InnerOperationInvoker.IsSynchronous; }
  90:      }
  91:  }
  92:   

now decorate you service methode with "STAOperationBehavior" attribute, 
and then it should work with controls that require STA.

now my WCF service works with with BrowserControl!

  1:  [STAOperationBehavior]
  2:  public fResultDO Berechne(StoffRequestDO stoffRequestDO)



Thursday, November 5, 2009

Web Scraping class - using WebBrowser .NET control

Web scraping is a computer software technique of extracting information from websites
In my project I need to calculate some results using specific external web site.
I checked what .NET framework offers me to perform operations on websites

1) System.Web HTTPRequest
http://msdn.microsoft.com/en-us/library/system.web.httprequest.aspx
2) System.Net WebClient
http://msdn.microsoft.com/en-us/library/system.net.webclient(VS.80).aspx
3) System.Windows.Forms .WebBrowser
http://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowser.aspx


All 3 classes you can use, when you need just read html from the specific website.
I used most advanced WebBrowser class from System.Windows.Forms namespace.
It allows performing more advanced operations on the website by accessing its elements with HTML DOM and also work as a JavaScript runtime.

With WebBrowser control I can perfom following operations:

- input data into website controls
- perform click on the buttons
- reading values


My web scraping implementation:
At first main method, to calculate results using external website with help of WebBrowser control:

 1:  [STAOperationBehavior]
   2:   
   3:  public XYZResultDO Calculate(XYZRequestDO xyzRequestDO)
   4:  {
   5:      Log.DebugFormat("Calculate: {0}", gefahrstoffRequestDO);
   6:   
   7:      var result = new XYZResultDO(); 
   8:      try
   9:      {
  10:          InitBrowser();
  11:          LoadPage(PageUrl);
  12:          
  13:          if (!ValidatePage())
  14:          {
  15:              Log.Fatal("online tool is not available or not valid!");
  16:              throw new ApplicationException("online tool is not available or not valid!");
  17:          }
  18:          
  19:          foreach (XyzDO xyzDO in requestDO.xyzs)
  20:          {
  21:              InsertInput(xyzDO);
  22:          }
  23:   
  24:          result = ReadResults();
  25:          Log.DebugFormat("Page Web scraping. Calculated result {0}", result.ToString());
  26:   
  27:          return result; 
  28:      }
  29:      catch (Exception e)
  30:      {
  31:          Log.FatalFormat("Fehler bei Berechnung {0}", e.ToString());
  32:          return result;
  33:      }
  34:      finally
  35:      {
  36:          if(_webBrowser !=null)
  37:          {
  38:              _webBrowser.Dispose();
  39:              _webBrowser = null;
  40:          } 
  41:      }
  42:  }

This way I initialize my browser control

   1:  private void InitBrowser()
   2:  {
   3:      Log.Debug("InitBrowser() InitializeBrowser");
   4:      _webBrowser = new WebBrowser();
   5:   
   6:      _webBrowser.DocumentCompleted += WebBrowser_DocumentCompleted;
   7:      _webBrowser.ProgressChanged += webBrowser_ProgressChanged; 
   8:  }


Loading page into the browser control:
   1:  private void LoadPage(string uri)
   2:  {
   3:      if (_webBrowser != null)
   4:      {
   5:          Log.DebugFormat("LoadPage()  Page wird geladen von {0}", info.FullName);
   6:          _webBrowser.Navigate(url);
   7:          WaitToLoadDocument();
   8:      }
   9:   

handle problems by loading
  1:  private void WaitToLoadDocument()
   2:  {
   3:      _browserBusy = true;
   4:      DateTime startSeiteLaden = DateTime.Now;
   5:      Log.DebugFormat("WaitToLoadDocument() Laden von Webseite in Browser.");
   6:      while (_browserBusy && startSeiteLaden.AddSeconds(30) > DateTime.Now)
   7:      {
   8:          Application.DoEvents(); 
   9:      }
  10:  }
  11:   
  12:  private void WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
  13:  {
  14:      _browserBusy = false;
  15:      Log.DebugFormat("ValidateKolumbusPage() Dokument {0} geladen in Browser.", e.Url);
  16:  }


Validate loaded page, to ensure that all required elements are found
   1:  private bool ValidatePage()
   2:  {
   3:      Log.Debug("ValidatePage() Validiere geladenene Page");
   4:      //check if document is loaded into the browser
   5:      if (_webBrowser == null  _webBrowser.Document == null)
   6:      {
   7:          Log.Warn("ValidatePage() Browser oder Page fehlt.");
   8:          return false;
   9:      }
  10:   
  11:      //check if all document elements existis
  12:      if (_webBrowser.Document.GetElementById("Ergebnis") == null 
  13:      _webBrowser.Document.GetElementById("fehler") == null 
  14:      _webBrowser.Document.GetElementById("bild1") == null 
  15:      _webBrowser.Document.GetElementById("bild2") == null 
  16:      _webBrowser.Document.GetElementById("value") == null  
  17:      _webBrowser.Document.GetElementById("neu") == null 
  18:      _webBrowser.Document.GetElementById("Absenden") == null)
  19:      {
  20:          Log.Warn("ValidatePage() Page ist nicht valid.");
  21:          return false;
  22:      }
  23:   
  24:      return true;
  25:  }

Perfom operations on the website using HTML DOM
- Reading element value, 
- set element value, 
- click operation

   1:  private string GetPageElementValue(string elementName)
   2:  {
   3:      HtmlElement element = _webBrowser.Document.GetElementById(elementName);
   4:      return element.GetAttribute("value");
   5:   
   6:  }
   7:   
   8:   
   9:   
  10:   
  11:  private void SetPageElementValue(string elementName, string elementValue)
  12:  {
  13:      HtmlElement element = _webBrowser.Document.GetElementById(elementName);
  14:      if (element != null)
  15:      {
  16:          element.SetAttribute("value", elementValue);
  17:      }
  18:  }
  19:   
  20:   
  21:   
  22:  private void ClickPageElement(string elementName)
  23:  { 
  24:      var element = _webBrowser.Document.GetElementById(elementName); 
  25:      element.InvokeMember("click"); 
  26:  }