Application design

Posts   
 
    
jb_ke
User
Posts: 27
Joined: 12-Jun-2007
# Posted on: 16-Jun-2007 12:27:12   

Hello guys,

As I’m new to llbl (not to the concept of orm mappers) l’ve played around with the llbl interface and finished reading up on every Infrastructure thread available. Before delving into the subject, may I say that I’ve been enjoying the company of peers as it doesn’t happen too often that you get into a discussion group where there are people that actualy know what they are talking about if it comes down to developing an application. Reading up on the Infrastructure threads gave me a few hours of intelectual orgasms. As for that matter, Otis, if you ever decide to sell your brain, I’m buying. (jb_je lights another candle in the Llbl shrine)

The goal of this post is 2 folded. First of all I’d like the commuities input on my design and second of all, it may be a good reference for furter users of the Llbl framework. All in all it wil take some time to explain everything, so If you’re in the position I was last night, get a bottle of your favourate drink, a stach of sigaretes, dimm the lights and start reading.

Let me start by explaining the type of application I need to make so my design decisions can be placed in the correct perspective. The application, an assembly line optimalisation tool, has aleady a production version in VB6 COM+ SQL Server and needs to be redeveloped in c# with a web interface as PL. Aside from the fact that it needs to be web interface, there are some points to take into consideration:

  1. I don’t want the BL to expose Llbl generated objects.

  2. The BL will eventualy have to be able to expose it’s functionality to a wide range of Gui’s ranging from the above mentioned web interface to webservices, pda’s and cellphone’s so it’s imperative that this BL be as transparant as possible.

The reason I don’t want to expose the Llbl binaries is not because I’m of the opinion that it it’s bad design decision, but soly for the fact that I am an independent software developer and I don’t want my customers having the opportunity to directly interact with the generated Llbl binaries. They wouldn’t have the BL logic, but would still be able to fetch, change and persist entities. I realise that I cannot force a customer to not reference the Llbl binaries, as they will need to be distributed when the application is finished, but I sure can modify the generated Llbl binaries with the ??? key in the Assembly file (can't remember the name right now) and the public key token of my BL binaries so mine would be the only binairies allowed to use the Llbl generated ones and thus forcing the customer to pass through the BL if they needed extra functionalit. Ye, I know it’s harsch, but I do have to make money toostuck_out_tongue_winking_eye An extra advantage is that the customer doesn’t have to learn the Llbl API as it is, in all fairness, not the easiest API around.

The BL would be seperated in 4 namespaces.

The first, mycompany.applicationname.core, would contain manager classes encapsulation all the behavior of the entities, their business logic and field/entity validation. The functionality would be devided in separate manager classes that would each encapsulate a different, logical part of the application which in my humble opinion is good for code maintainability and diminishes the learning curve of other developers using my BL. On a side note, I’m planning on working with private constructors in these classes as I love the fact that I’m able to decide how developers use my code as they only have static factory methods and cannot instantiate these classes. After all, it’s the BL developer that needs to tell PL developers how to use the BL. Not allowing them to instantiate the BL classes makes this happen. I love the concept of manager classes as it makes for process based code instead of pure db mapping code. A manager class could be using several entities for all I know to accommodate PL functionality.

The second BL namespace would be a mycompany.applicationname.entity namespace that would contain a lightweight class (ie expose the entity’s fields) for every entity to hold entity’s data. These classes would not contain any functionality aside from a simple ToString() overload. They would merely exist as data containers for the manager entities. As the persistance is done in the manager namespace mycompany.applicationname.core, it will use the mycompany.applicationname.entity classes to pass the objects around through the different layers. These classes would be generated by Llbl based on a template so it it easy to maintain and to update. If anything fundamentaly changes to the entity, ie an extra field, I can just regenerate the code. The idea behind this layer is that it allows me to split the behavior code with the pure data code plus the extra advantage, in my case, of hiding the Llbl Api.

The third BL namespace mycompany.applicationname.service would contain only static methods like static GetEntity() and a few dozen overloads to accommodate the filling of grids and cohorts. These static methods would all return DataViews for consistency. Other static methods would include entity functionality that doesn’t require an instance of the object. These classes would eventually result in a namespace that could work as a service based layer in view of remoting/web services. (SOA like architecture).

The fourth and last namespace mycompany.applicationname.validation would contain all the validation logic and implement ValidatorBase. This way my validation logic is centralised and it can be injected on the fly in the manager classes.

I didn’t use inheritance as all the objects(entities) refer to base types in my application. If a later implementation of the software would result in extending an entities functionality, I would just have to add the table with a 1:1 relation in the db, include the inheritance relation in the designer, regenerate and be done with it.

I did use a lot of typeconvertor classes because I prefer working with strong typed objects as it is alot easier to code and its much better to get a compile error then a runtime error. I have centralized these type converters in the mycompany.applicationname.typeconvertors namespace as I need to reference the binary in the generated Llbl binaries, not to mention the fact that I love to divide things into meaningful sectionssmile Talking about type converters, I found a bugcry The typeconvertor generate a ‘+’ instead of a ‘.’ In the generation process. Cfr image. It’s not that big of a deal as I can replace it quickly, but it’s still a bugwink

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;

namespace Company.Application.TypeConverters
{
  public partial class Enumerations
  { 
    public enum LineTypes
    {
      enum1 = 1,
      enum2 = 2
    }
  }

  public class NumericLineTypeConvertor : System.ComponentModel.TypeConverter
  {
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
      switch (sourceType.FullName)
      {
        case "System.Byte":
          return true;
        default:
          return false;
      }
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
      switch (destinationType.FullName)
      {
        case " Company.Application.TypeConverters.Enumerations.LineTypes":
          return true;
        default:
          return false;
      }
    }

    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
      Company.Application.TypeConverters.Enumerations.LineTypes enumLineType =
       Company.Application.TypeConverters.Enumerations.LineTypes.AssemblyLine;

      switch (value.GetType().FullName)
      {
        case "System.Int32":
        case "System.Byte":
   enumLineType = (Company.Application.TypeConverters.Enumerations.LineTypes)    Enum.ToObject(typeof(Company.Application.TypeConverters.Enumerations.LineTypes), value);
          break;
        default:
          throw new NotSupportedException("Conversion from a value of type '" + value.GetType().ToString() +
            "' to Company.Application.TypeConverters.Enumerations.LineTypes isn't supported");
      }

      return enumLineType;

    }

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
      if (value == null)
      {
        throw new ArgumentNullException("value", "Value can't be null");
      }

      if (!(value is Company.Application.TypeConverters.Enumerations.LineTypes))
      {
        throw new ArgumentException("Value isn't of type Company.Application.TypeConverters.Enumerations.LineTypes", "value");
      }

      Company.Application.TypeConverters.Enumerations.LineTypes enumLineType =
        (Company.Application.TypeConverters.Enumerations.LineTypes)value;

      switch (destinationType.FullName)
      {
        case "System.Int32":
          return (int)enumLineType;
        case "System.Int16":
          return (short)enumLineType;
        default:
          throw new NotSupportedException("Conversion to a value of type '" + destinationType.ToString() + "' isn't supported");
      }

    }

    public override object CreateInstance(ITypeDescriptorContext context, System.Collections.IDictionary propertyValues)
    {
      return Company.Application.TypeConverters.Enumerations.LineTypes.AssemblyLine;
    }
  }
}

Well, thats it. Please be gentle with the commentsflushed if there is a hole in my logic the size of an elefantfrowning

Carl

Attachments
Filename File size Added on Approval
bug.JPG 16,706 16-Jun-2007 12:27.21 Approved
jb_ke
User
Posts: 27
Joined: 12-Jun-2007
# Posted on: 16-Jun-2007 15:47:10   

Talking about type converters, I found a bug The typeconvertor generate a ‘+’ instead of a ‘.’ In the generation process. Cfr image. It’s not that big of a deal as I can replace it quickly, but it’s still a bug

I found this post http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=8738&HighLight=1. Otis, eventhough I understand the underlying winksimple_smile , the enum type is public. Isn't a type internal only if declared so with the internal keyword?

Carl

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 16-Jun-2007 20:53:05   

I havent' read your first post yet, though to answer the question about the '+', if you don't explicitly specify 'public' a type isn't public, it's private. simple_smile

So: namespace Foo { class Bar {} }

here, Bar is private.

though, it seems the generator can'thandle nested types as it seems. I'll see what I can do to fix that simple_smile

Frans Bouma | Lead developer LLBLGen Pro
jb_ke
User
Posts: 27
Joined: 12-Jun-2007
# Posted on: 18-Jun-2007 02:55:28   

though to answer the question about the '+', if you don't explicitly specify 'public' a type isn't public, it's private

Otis, Altough this is absolutely true, I don't see the relevence to the problemconfused .

Odd, though what I understand is that the '+' signals an internal type inside the type left to the '+'. 

I think I understand what you are saying. If the enumeration type would be defined as internal in the typeconverter class, it would produce the '+' notation. But as far as I know, MSDN's internal access modifier definition states:

Internal types or members are accessible only within files in the same assembly

witch defenatly isn't the case presented in the thread mentioned above as the attachment clearly shows that the defined enumeration type is in a different namespace then the typeconverter itself.

Or am I missing something.flushed

PS: Just so you know, I realy don't feel comfertable second guessing your brilliant mindsunglasses So please, feel free to put me on my mery waywink

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 18-Jun-2007 12:52:33   

I'm not that briliant, I just type a lot wink

the code you posted in the start post shows a public enum indeed, so it shouldn't have a '+' sign. I'll see what the cause is of this '+' sign, however my guess without looking at the code is that it's the name obtained from reflection, in which case I can't do a thing about it.

An internal type can be placed inside another namespace, as long as it's in the same assembly. So if I have:

namespace One { internal class Foo { } }

and

namespace Two { internal class Bar : Foo {} }

and place it in the same assembly (dll) it works. Perhaps this gives some light at the material.

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 18-Jun-2007 13:06:43   

Doesn't the '+' mean its a nested class rather than describe its visibility?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 18-Jun-2007 13:14:12   

I'll now dig into your lengthy post simple_smile

jb_ke wrote:

As I’m new to llbl (not to the concept of orm mappers) l’ve played around with the llbl interface and finished reading up on every Infrastructure thread available. Before delving into the subject, may I say that I’ve been enjoying the company of peers as it doesn’t happen too often that you get into a discussion group where there are people that actualy know what they are talking about if it comes down to developing an application. Reading up on the Infrastructure threads gave me a few hours of intelectual orgasms. As for that matter, Otis, if you ever decide to sell your brain, I’m buying. (jb_je lights another candle in the Llbl shrine)

Whoa, thanks for the feedback smile

(snip) The reason I don’t want to expose the Llbl binaries is not because I’m of the opinion that it it’s bad design decision, but soly for the fact that I am an independent software developer and I don’t want my customers having the opportunity to directly interact with the generated Llbl binaries. They wouldn’t have the BL logic, but would still be able to fetch, change and persist entities. I realise that I cannot force a customer to not reference the Llbl binaries, as they will need to be distributed when the application is finished, but I sure can modify the generated Llbl binaries with the ??? key in the Assembly file (can't remember the name right now) and the public key token of my BL binaries so mine would be the only binairies allowed to use the Llbl generated ones and thus forcing the customer to pass through the BL if they needed extra functionalit. Ye, I know it’s harsch, but I do have to make money toostuck_out_tongue_winking_eye An extra advantage is that the customer doesn’t have to learn the Llbl API as it is, in all fairness, not the easiest API around.

You can use adapter for this. Adapter is designed to avoid persistence logic in the PL and prevent the PL from taking shortcuts: if your BL tier refers to the DB specific code but the PL doesn't (it doesn't have to), then the PL can reference the generated entities and runtimes, but you'll never be able to persist anything, you'll need the dbspecific code for that, which is only available in the BL tier, so you'll have to call into the BL tier, which is what you want simple_smile

(snip) I did use a lot of typeconvertor classes because I prefer working with strong typed objects as it is alot easier to code and its much better to get a compile error then a runtime error. I have centralized these type converters in the mycompany.applicationname.typeconvertors namespace as I need to reference the binary in the generated Llbl binaries, not to mention the fact that I love to divide things into meaningful sectionssmile Talking about type converters, I found a bugcry The typeconvertor generate a ‘+’ instead of a ‘.’ In the generation process. Cfr image. It’s not that big of a deal as I can replace it quickly, but it’s still a bugwink

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;

namespace Company.Application.TypeConverters
{
  public partial class Enumerations
  { 
    public enum LineTypes
    {
      enum1 = 1,
      enum2 = 2
    }
  }

  public class NumericLineTypeConvertor : System.ComponentModel.TypeConverter
  {
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
      switch (sourceType.FullName)
      {
        case "System.Byte":
          return true;
        default:
          return false;
      }
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
      switch (destinationType.FullName)
      {
        case " Company.Application.TypeConverters.Enumerations.LineTypes":
          return true;
        default:
          return false;
      }
    }

    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
      Company.Application.TypeConverters.Enumerations.LineTypes enumLineType =
       Company.Application.TypeConverters.Enumerations.LineTypes.AssemblyLine;

      switch (value.GetType().FullName)
      {
        case "System.Int32":
        case "System.Byte":
   enumLineType = (Company.Application.TypeConverters.Enumerations.LineTypes)    Enum.ToObject(typeof(Company.Application.TypeConverters.Enumerations.LineTypes), value);
          break;
        default:
          throw new NotSupportedException("Conversion from a value of type '" + value.GetType().ToString() +
            "' to Company.Application.TypeConverters.Enumerations.LineTypes isn't supported");
      }

      return enumLineType;

    }

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
      if (value == null)
      {
        throw new ArgumentNullException("value", "Value can't be null");
      }

      if (!(value is Company.Application.TypeConverters.Enumerations.LineTypes))
      {
        throw new ArgumentException("Value isn't of type Company.Application.TypeConverters.Enumerations.LineTypes", "value");
      }

      Company.Application.TypeConverters.Enumerations.LineTypes enumLineType =
        (Company.Application.TypeConverters.Enumerations.LineTypes)value;

      switch (destinationType.FullName)
      {
        case "System.Int32":
          return (int)enumLineType;
        case "System.Int16":
          return (short)enumLineType;
        default:
          throw new NotSupportedException("Conversion to a value of type '" + destinationType.ToString() + "' isn't supported");
      }

    }

    public override object CreateInstance(ITypeDescriptorContext context, System.Collections.IDictionary propertyValues)
    {
      return Company.Application.TypeConverters.Enumerations.LineTypes.AssemblyLine;
    }
  }
}

AssemblyLine is a value in the enum, I pressume? It indeed with this code should give a proper '.', and not a "+". If you move the enums out of the Enumerations class, does it work then?

What's the type for the field in the designer? Does it there also have a '+' ? The designer simply does a GetType() on the object returned from CreateInstance of the typeconverter.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 18-Jun-2007 13:15:22   

simmotech wrote:

Doesn't the '+' mean its a nested class rather than describe its visibility?

Correct! flushed

Frans Bouma | Lead developer LLBLGen Pro
jb_ke
User
Posts: 27
Joined: 12-Jun-2007
# Posted on: 18-Jun-2007 14:46:13   

jb_ke wrote:

The second BL namespace would be a mycompany.applicationname.entity namespace that would contain a lightweight class (ie expose the entity’s fields) for every entity to hold entity’s data. These classes would not contain any functionality aside from a simple ToString() overload. They would merely exist as data containers for the manager entities. As the persistance is done in the manager namespace mycompany.applicationname.core, it will use the mycompany.applicationname.entity classes to pass the objects around through the different layers. These classes would be generated by Llbl based on a template so it it easy to maintain and to update. If anything fundamentaly changes to the entity, ie an extra field, I can just regenerate the code. The idea behind this layer is that it allows me to split the behavior code with the pure data code plus the extra advantage, in my case, of hiding the Llbl Api.

As you stated, I could use the entityclasses to pass the objects around, but that would expose the virtual functions from these enitities to the PL, eventhough they woudn't be able to persist anything due to the Adapter implementation. By creating a new template that could run as an extra task in the designer, I can easely create these "wrapper" entity classes stripping them down to a mere set of properties creating them in a seperate project augmenting maintenance. I realise that I would have todo explicit casts in the manager classes, but I can live with that as the reward is great imo, the hiding of the Llbl functionality from the PL.

I realise that any PL dev, especialy if it were mesmile , would use reflector and examine all the binaries thus rapidly finding out the llbl implementation (if they know their professionwink ), but still, my application BL binairies would never expose any llbl functionality and all in all, it woudn't be so much "rewriting" generated code as it would be "generating an extra layer".

Otis wrote:

You can use adapter for this. Adapter is designed to avoid persistence logic in the PL and prevent the PL from taking shortcuts: if your BL tier refers to the DB specific code but the PL doesn't (it doesn't have to), then the PL can reference the generated entities and runtimes, but you'll never be able to persist anything, you'll need the dbspecific code for that, which is only available in the BL tier, so you'll have to call into the BL tier, which is what you want.

I realised this after reading the documentation and the architecture threads and as usual, you are correct. Only, my problem, or rather issuesimple_smile is that, when I would e.g. inherit the generated entityfactories in my BL, I am exposing functionality like ProfileEntityFactory.Create() and other virtual methods and this is exactly what I don't want as the PL dev will see these and that doesn't fit into my master plan of keeping the PL dev oblivient of the underlying llbl framework. Am I making any sence here?flushed

Otis wrote:

AssemblyLine is a value in the enum, I pressume? It indeed with this code should give a proper '.', and not a "+". If you move the enums out of the Enumerations class, does it work then?

What's the type for the field in the designer? Does it there also have a '+' ? The designer simply does a GetType() on the object returned from CreateInstance of the typeconverter.

You presume correct. The DB type = tinyint. I have tried moving the enumeration type outside of the class and that seemed todo the tricksmile

On a side note: I found out that if you have the typeconverter binairy updated in which one removes/redifines the type, opening the project file in the designer will trow an unhandled exception sayin, to no surprisewink , that he cannot find the type and the project file doesn't open until you refactor the code again to the old situation, removing the type, closing the designer, replacing the old convertor with the new binairy and re-assign the type. Maybe you could remove the unexisting types and reset them to their default values or some sort of message?

Carl

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 19-Jun-2007 11:01:24   

jb_ke wrote:

jb_ke wrote:

The second BL namespace would be a mycompany.applicationname.entity namespace that would contain a lightweight class (ie expose the entity’s fields) for every entity to hold entity’s data. These classes would not contain any functionality aside from a simple ToString() overload. They would merely exist as data containers for the manager entities. As the persistance is done in the manager namespace mycompany.applicationname.core, it will use the mycompany.applicationname.entity classes to pass the objects around through the different layers. These classes would be generated by Llbl based on a template so it it easy to maintain and to update. If anything fundamentaly changes to the entity, ie an extra field, I can just regenerate the code. The idea behind this layer is that it allows me to split the behavior code with the pure data code plus the extra advantage, in my case, of hiding the Llbl Api.

As you stated, I could use the entityclasses to pass the objects around, but that would expose the virtual functions from these enitities to the PL, eventhough they woudn't be able to persist anything due to the Adapter implementation. By creating a new template that could run as an extra task in the designer, I can easely create these "wrapper" entity classes stripping them down to a mere set of properties creating them in a seperate project augmenting maintenance. I realise that I would have todo explicit casts in the manager classes, but I can live with that as the reward is great imo, the hiding of the Llbl functionality from the PL.

True, though what I wonder is: why do you need to hide things which are harmless or simply part of the entity's features, for the PL developer? The only reason I can think of is that the PL has to be written o/r mapper agnostic so you could swap out the o/r mapper, but that's not as easy as it might seem: all code using the o/r mapper has to be rewritten in that case, no matter what o/r mapper you're using.

I realise that any PL dev, especialy if it were mesmile , would use reflector and examine all the binaries thus rapidly finding out the llbl implementation (if they know their professionwink ), but still, my application BL binairies would never expose any llbl functionality and all in all, it woudn't be so much "rewriting" generated code as it would be "generating an extra layer".

Though, adding another abstraction layer will also make the design more complex, and thus harder to maintain, unless the abstraction layer is needed for some reason. What I don't understand in full is what the reason could be that you want to hide things which are there to make life easier simple_smile

Otis wrote:

You can use adapter for this. Adapter is designed to avoid persistence logic in the PL and prevent the PL from taking shortcuts: if your BL tier refers to the DB specific code but the PL doesn't (it doesn't have to), then the PL can reference the generated entities and runtimes, but you'll never be able to persist anything, you'll need the dbspecific code for that, which is only available in the BL tier, so you'll have to call into the BL tier, which is what you want.

I realised this after reading the documentation and the architecture threads and as usual, you are correct. Only, my problem, or rather issuesimple_smile is that, when I would e.g. inherit the generated entityfactories in my BL, I am exposing functionality like ProfileEntityFactory.Create() and other virtual methods and this is exactly what I don't want as the PL dev will see these and that doesn't fit into my master plan of keeping the PL dev oblivient of the underlying llbl framework. Am I making any sence here?flushed

Just for my understanding: why shouldn't the PL dev be aware about the entities s/he's working with? IMHO, the more openness the better.

Otis wrote:

AssemblyLine is a value in the enum, I pressume? It indeed with this code should give a proper '.', and not a "+". If you move the enums out of the Enumerations class, does it work then?

What's the type for the field in the designer? Does it there also have a '+' ? The designer simply does a GetType() on the object returned from CreateInstance of the typeconverter.

You presume correct. The DB type = tinyint. I have tried moving the enumeration type outside of the class and that seemed todo the tricksmile

Still odd that the enum type name contains the '+'.

On a side note: I found out that if you have the typeconverter binairy updated in which one removes/redifines the type, opening the project file in the designer will trow an unhandled exception sayin, to no surprisewink , that he cannot find the type and the project file doesn't open until you refactor the code again to the old situation, removing the type, closing the designer, replacing the old convertor with the new binairy and re-assign the type. Maybe you could remove the unexisting types and reset them to their default values or some sort of message?

Carl

The thing is that this error is raised by the deserializer code of .NET. It can't find a type to instantiate and therefore it can't proceed. The exception appears in mid-air really and is exposed through the appdomain to the code so it can handle it THERE, however that place is completely somewhere else than where the entity definition is rebuild.

It's a bit of a pain, as there's little to no info what to expect BEFORE the deserialization and during the deserialization one can't quite trap the exception at the spot where the info is present (so reverting to a given type is possible), so it's hard if not impossible to replace the expected type with a different type, like using a surrogate.

We hope to address this in the fall with a textual project file format in a DSL.

Frans Bouma | Lead developer LLBLGen Pro
jb_ke
User
Posts: 27
Joined: 12-Jun-2007
# Posted on: 24-Jun-2007 21:14:12   

k Otis, you made clear arguments and after some playing around, I must admit your reservations are more then valid. Aside from the fact that the PL dev will have to understand the LLbL api, there is no gain in hiding everything as the persistance can only be achieved by using the manager classes in the BL code.

As a consequence, I've created my BL objects by letting them inherit the created entity so my CustomerEntity would be implemented into a Customer giving me the opportunity to retain the entity's generated functionality and to add more properties if needed. I also did the same for the generated factories and inherited them into a new CustomerFactory. (inheriting from the gererated CustomerEntityFactory).

Inherited Customer code


namespace Application.Core
{
  public class Customer : CustomerEntity
  {
    public Customer(IEntityFields2 Id) : base(Id)
    {
    }

    public Customer(Guid Id)
    {
      base.Id = Id;
      using (DataAccessAdapter adapter = new DataAccessAdapter(true))
      {
        adapter.FetchEntity(this);
      }
    }

    public Customer() : base()
    {
    }
    
    /// <summary> The DisplayName property of the inherited Entity Customer<br/><br/>
    /// </summary>
    /// <remarks>Does not map on table fields!<br/>
    /// The name to display<br/>
    /// If the Customer's clientype property equals ClientType.BusinessCustomer, the DisplayName
    /// property will return a FirstName + " " + LastName construct if the CompanyName property is
    /// empty or null.
    /// </remarks>
    public virtual String DisplayName
    {
      get
      {
        string firstName = (string) GetCurrentFieldValue((int) CustomerFieldIndex.Firstname);
        string lastName = (string) GetCurrentFieldValue((int) CustomerFieldIndex.Lastname);
        string companyName = (string) GetCurrentFieldValue((int) CustomerFieldIndex.CompanyName);
        ClientType clientType = (ClientType) GetCurrentFieldValue((int) CustomerFieldIndex.ClientType);
        object valueToReturn = null;

        switch (clientType)
        {
          case ClientType.PrivateCustomer:
            valueToReturn = firstName + " " + lastName;
            break;
          case ClientType.BusinessCustomer:
            if (companyName == string.Empty || companyName == null)
            {
              valueToReturn = firstName + " " + lastName;
            }
            else
            {
              valueToReturn = companyName;
            }
            break;
          default:
            throw new InvalidEnumArgumentException(
              string.Format("Supplied Application.TypeConvertors.ClientType {0} not supported.", clientType));
        }
        return (String) valueToReturn;
      }
    }

    protected override IEntityFactory2 CreateEntityFactory()
    {
      return new CustomerFactory();
    }
  }
}

CustomerFactory


namespace Application.Core
{
  public class CustomerFactory : CustomerEntityFactory
  {
    /// <summary>CTor</summary>
    public CustomerFactory(): base()
    {
    }

    /// <summary>Creates a new, empty Customer object.</summary>
    /// <returns>A new, empty Customer object.</returns>
    public override IEntity2 Create()
    {
      IEntity2 toReturn = new Customer();
      return toReturn;
    }

    /// <summary>Creates a new Customer instance but uses a special constructor which will set the Fields object of the new
    /// IEntity2 instance to the passed in fields object. Implement this method to support multi-type in single table inheritance.</summary>
    /// <param name="fields">Populated IEntityFields2 object for the new IEntity2 to create</param>
    /// <returns>Fully created and populated (due to the IEntityFields2 object) IEntity2 object</returns>
    public override IEntity2 Create(IEntityFields2 fields)
    {
      IEntity2 toReturn = new Customer(fields);
      return toReturn;
    }

    /// <summary>returns the name of the entity this factory is for, e.g. "EmployeeEntity"</summary>
    public override string ForEntityName
    {
      get { return "Customer"; }
    }
  }
}


In my CustomerManager, BL code, I've implemented a simple private method to return all the Customer objects. The method in itself wouldn't be exposed but would be used as some sort of engine that can handle all possible overloads. As you can see I've been trying to dynamicaly add sortexpressions. The bold text was something i've played with but doesn't work with EntityCollection as it obviously doesn't implement the GetFieldsInfo method. Is there a cleaner way todo this?

I've been looking over the docs but can only find examples of predifined sortexpression, ie, the ones you know in advance. Wot if you wanted to make this dynamic instead of writing an overload for each and every property you wanted to sort on?


private EntityCollection<Customer> GetCustomers([b]string[] SortFields[/b])
    {
      EntityCollection<Customer> customer = new EntityCollection<Customer>(new CustomerFactory());
        
        [b]sorter.Add((EntityField2)customer.GetFieldsInfo()[SortFields[i].ToString()] | SortOperator);[/b]

      try
      {
        using (DataAccessAdapter adapter = new DataAccessAdapter(true))
        {
          adapter.FetchEntityCollection(customer, null);
        }
      }
      catch (Exception e)
      {
        throw new InvalidOperationException(e.Message);
      }
      return customer;
    }

A second question is the implementation of the DisplayName property in the inherited Customer class. Will I be able to sort on this property just by inheriting the generated CustomerEntity?

Carl

Walaa avatar
Walaa
Support Team
Posts: 14983
Joined: 21-Aug-2005
# Posted on: 25-Jun-2007 09:08:42   

I've created my BL objects by letting them inherit the created entity so my CustomerEntity would be implemented into a Customer giving me the opportunity to retain the entity's generated functionality and to add more properties if needed.

A small suggestion: Did you check the Two Class Presets of the code generation template groups? Look at the third tab of the code generation window, look for the "Selected Preset" dropdownList.

Using Adapter TwoClasses Preset/or SelfServicing TwoClasses Preset. Will generate classes that inherits from the EntityClasses to be used the same way you want.

jb_ke
User
Posts: 27
Joined: 12-Jun-2007
# Posted on: 25-Jun-2007 21:04:22   

Hey W,

thx for the response, but if I'm not mistaken, thats wot I am already doing. The mapped entities all have their own entityclasses generated as their factories.

I completely understand the functionality of the task generation process as I've been playing around with it in my previous architecural attempt to automaticaly generate an extra layer. Thx to Otis, I have seen the lightwink and am now trying to get the most out of the generated code.

Will generate classes that inherits from the EntityClasses

is a task I don't seem to find, nor can I find a task taht would generate inherited entity factory classes.

I realise I can easely make the templates and have it all generated, but I don't seem to see the preset ones. Am I missing something here?

PS: Aside from the autogeneration, is the code implementation a good way to go?

Carl

jb_ke
User
Posts: 27
Joined: 12-Jun-2007
# Posted on: 28-Jun-2007 17:16:48   

I found the templatessimple_smile