SelfServicing and PersistenceInfoProvider

Posts   
 
    
Vikruolis
User
Posts: 9
Joined: 06-Nov-2008
# Posted on: 03-Nov-2009 09:38:20   

Hello,

I am using 2.6 LLBLGen Pro version. I have faced multiple level problem. Lets go step by step.

Preface - First of all company have decided to implement some column level encryption/decryption strategy. It is based on SQL server "EncryptByKey" function. - Next thing that we have many different applications/services which uses same database. This means that we have multiple LLBLGen.lgp projects which uses only DB part for specific app/service. Some projects are self servicing, some are adapter. - information which table columns are encoded is saved in one specific table (not hard-coded). This means that I know what tables columns are encoded only after DAL init.

Ok, here we go: 1. I made some searches in google and here and found that most appropriate way to set column level encryption/decryption is using TypeConverters. 2. Got SDK and source files. Analyzed "BooleanNumericConverter.cs" and how "BooleanNumericConverter" class usage appears in generated source code. It is easy part. 3. Ok, lets try to set type converter dynamically to specific field. Emm..., only "MineEntityFields.MineField.TypeConverterToUse" get property. 4. Found that type converters are assigned to fields during initialization by calling AddElementFieldMapping function. But this is implemented in generated internal sealed class PersistenceInfoProviderSingleton. This means that I cant access that class outside from assembly. Generated code change is IMHO silly.

Now I am stuck. I need somehow access fields mapping information, preferable unified way for self servicing and adapter modes. Access should be done by using table names and columns names. And then I should be able to set own type converter.

Thank you in advance. BR, Marius

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 03-Nov-2009 10:40:51   
  1. Found that type converters are assigned to fields during initialization by calling AddElementFieldMapping function. But this is implemented in generated internal sealed class PersistenceInfoProviderSingleton. This means that I cant access that class outside from assembly. Generated code change is IMHO silly.

Why silly? If you add a class to the generated code project it won't get overwritten when regenerating the code.

Vikruolis
User
Posts: 9
Joined: 06-Nov-2008
# Posted on: 03-Nov-2009 11:25:39   

Silly because of generated code usually goes to separated assembly which is main DAL assembly. Functionality I described is common for all DAL assemblies. If I would go to "implement into all assemblies" strategy I will face: - rise of chance of mistakes, because of a lot of copy paste between projects - chance to forget to add such code in new projects - in any code change I will need to cycle through all project and do appropriate changes to all places

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 03-Nov-2009 11:33:53   

You can try to implement this into a template and use it in code generation, so here you have the code tobe maintained in one place (the template).

Vikruolis
User
Posts: 9
Joined: 06-Nov-2008
# Posted on: 03-Nov-2009 11:36:12   

Now I am looking for 2 answers to questions: 1. how I can dynamically set TypeConverter object to particular entity field in self servicing mode? Out of generated code assembly scope. 2. How can I access table names<->entity names/enums and column names<->entity field names/enums? Out of generated code assembly scope?

Thank you in advance BR, Marius

P.S.

As I see in SDK source code. FieldPersistenceInfo._typeConverterToUse is set only during construction. No later changes is allowed to _typeConverterToUse member in created object.

All FieldPersistenceInfo objects are created by calling PersistenceInfoProviderBase.AddElementFieldMapping which is derived in generated code and made to be singleton and internal class. All persistence objects are placed to PersistenceInfoProviderBase._elementMappings and there is no access executing such code:

ElementToTargetMapping mapping = GetElementMappingInfo( elementName );
mapping.ElementFieldMappings[fieldName] = newPersistenceInfo;
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39863
Joined: 17-Aug-2003
# Posted on: 03-Nov-2009 13:19:17   

The type converters aren't settable at runtime because they are meant to convert from a .net type at the datareader level (e.g. int) to the type a field has in an entity (e.g. bool) and back. This means that setting them dynamically has no real purpose, as you can't change the type of a field in a class at runtime.

Using them to encrypt/decrypt data is only possible if you know up front which fields are encrypted.

Back to your problem. First thing I want to know is: the encryption, does that change the type of the data? E.g. if you have a string value and you want to encrypt it, does that make it a byte[] ? So is the data after encryption of the same type?

Frans Bouma | Lead developer LLBLGen Pro
Vikruolis
User
Posts: 9
Joined: 06-Nov-2008
# Posted on: 03-Nov-2009 14:48:04   

lets assume I have such table: testTable{ ID int, Name char(200), PrivateInfo char(500) }

standard data insertion should be like this: insert into testTable (ID, Name, PrivateInfo) values(1, 'testName', 'testPrivateInfo');

encode insert should be like this: insert into testTable (ID, Name, PrivateInfo) values(1, 'testName', EncryptByKey(KEY_GUID('OwnEncryptionKey'), 'testPrivateInfo'));

Only text type columns will be encrypted, so imho LLBLGen .net type should be string. I am concerning only about insert/update statements, because decryption goes in other place by calling special handshake store procedure and real data is retrieved by using view.

And yes, data type in table column is varbinary(170), in view column varchar(60) and I should see in entity as varchar->string object.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39863
Joined: 17-Aug-2003
# Posted on: 03-Nov-2009 18:05:50   

Vikruolis wrote:

lets assume I have such table: testTable{ ID int, Name char(200), PrivateInfo char(500) }

standard data insertion should be like this: insert into testTable (ID, Name, PrivateInfo) values(1, 'testName', 'testPrivateInfo');

encode insert should be like this: insert into testTable (ID, Name, PrivateInfo) values(1, 'testName', EncryptByKey(KEY_GUID('OwnEncryptionKey'), 'testPrivateInfo'));

Only text type columns will be encrypted, so imho LLBLGen .net type should be string. I am concerning only about insert/update statements, because decryption goes in other place by calling special handshake store procedure and real data is retrieved by using view.

And yes, data type in table column is varbinary(170), in view column varchar(60) and I should see in entity as varchar->string object.

I'm still confused: your table only shows character columns, not binary. This is very important. The thing is that you want to store in a table which columns are encrypted, however if encrypting them has an effect on the type of the field, it's of no use to store it in some table, as that could get modified while the field in the table is then suddenly unreadable. (i.o.w.: the type in the table forces it to know up front which fields are encrypted).

So as the target of the encrypted data is binary and not string, you know which fields are encrypted up front, which means you can use typeconverters up front, as changing a field to NOT being encrypted means it will get a string based table field (and thus a different type, which means mappings change) or if you want to encrypt a field, it too changes, as the field the data is saved in is different.

Also please read the (long!) discussion in this thread: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=14929 especially about the difference between typeconversion and encryption/decryption, so these two have to be seen differently.

Also keep in mind that modifying SQL generated on the fly is for selfservicing very difficult.

Frans Bouma | Lead developer LLBLGen Pro
Vikruolis
User
Posts: 9
Joined: 06-Nov-2008
# Posted on: 04-Nov-2009 08:15:14   

thank you for response. I have read given thread it has some same issues, but also differs a lot. Ok, let me give better view how encryption/decryption is implemented.

We have table CUSTOMER_ENCRYPTED { ID int, Name varchar(200), Secret_Info varbinary(170) ... } also we have a view CUSTOMER { ID int, Name varchar(200), Secret_Info varchar(60) ... }

Both view and table structure are identical except encrypted fields - encrypted info (in CUSTOMER_ENCRYPTED) is in varbinary, decrypted field info (in CUSTOMER) is in varchar. Also sizes differes. Data retrieve always goes from CUSTOMER view. In order to correctly work CUSTOMER view there should be open master keys and special handshake function call. It is done once per connection. Data update and insert should always go to CUSTOMER_ENCRYPTED table. And for encrypted columns given data should be converted to "EncryptByKey(KEY_GUID('OwnDataEncryptionKey'), ?)".

As primary source for entities will be used view, because it have correct data types (varchars).

Looking to given thread I find out that Birchoff went to solution to alter DQE code which is in SD.LLBLGen.Pro.DQE.SqlServer namespace. It is deep in LLBLGen own source codes, so I would like to avoid such code changes.

Now I am even looking to solution to override OnCanSaveNewEntity() and OnCanSaveExistingEntity() function in entities. This probably will be done by templates. Overrides will be kinda hidden save data to apropriate table and return false. But such solutions also looks more like hack not a real solution.

BR, Marius

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39863
Joined: 17-Aug-2003
# Posted on: 04-Nov-2009 10:10:37   

Vikruolis wrote:

thank you for response. I have read given thread it has some same issues, but also differs a lot. Ok, let me give better view how encryption/decryption is implemented.

We have table CUSTOMER_ENCRYPTED { ID int, Name varchar(200), Secret_Info varbinary(170) ... } also we have a view CUSTOMER { ID int, Name varchar(200), Secret_Info varchar(60) ... }

Both view and table structure are identical except encrypted fields - encrypted info (in CUSTOMER_ENCRYPTED) is in varbinary, decrypted field info (in CUSTOMER) is in varchar. Also sizes differes. Data retrieve always goes from CUSTOMER view. In order to correctly work CUSTOMER view there should be open master keys and special handshake function call. It is done once per connection. Data update and insert should always go to CUSTOMER_ENCRYPTED table. And for encrypted columns given data should be converted to "EncryptByKey(KEY_GUID('OwnDataEncryptionKey'), ?)".

As primary source for entities will be used view, because it have correct data types (varchars).

An entity can be mapped onto 1 target, e.g. a view or a table, but not a table AND a view. This is logical, as mapping info is static data and if that's manipulated at runtime, it needs to be volatile and thus slower, and also it will give problems because in your case fields differ in type and length.

To work easy with customer data, IMHO it's essential that you have one entity which is used for both saving and fetching.

There's an easy solution for your problem simple_smile . I'll describe the steps below. - map your Customer entity on the VIEW, you don't need to map an entity on the table. - add an INSERT and an UPDATE trigger to the view. In these triggers, you insert a row in the CUSTOMER_ENCRYPTED table, or update the row in the CUSTOMER_ENCRYPTED table. Please see books online of sqlserver about triggers on views for inserts and updates. LLBLGen pro will generate a normal insert or update query on the view. The trigger will intercept that query (as a view is readonly and not a table) and act accordingly. You can then insert the data encrypted. - if you don't want to have the data encrypted anymore, drop the triggers, drop the view and rename the table to CUSTOMER, replace the varbinary field with a varchar field and it will work automatically.

The OnCan... functions are for authorization, so not important for you.

Please let me know if this works for you.

Frans Bouma | Lead developer LLBLGen Pro
Vikruolis
User
Posts: 9
Joined: 06-Nov-2008
# Posted on: 04-Nov-2009 11:55:35   

Got from our db guy that answer: "No it is not possible. You cannot insert data through view which is handling automatic decrypting."

Now thinking about that change: 1. in separate assembly implement LocalDQE derived from SD.LLBLGen.Pro.DQE.SqlServer.DynamicQueryEngine. Override only these functions: - CreateSingleTargetInsertDQ - CreateSingleTargetUpdateDQ - CreateSingleTargetUpdateDQ - CreateSingleTargetDeleteDQ - because table name differs - CreateSingleTargetDeleteDQ 2. Change DbUtils template: - add using new assembly namespace - SD.LLBLGen.Pro.DQE.SqlServer.DynamicQueryEngine.FactoryToUse.CreateConnection(); -> OwnDQE.SqlServer.LocalDQE.FactoryToUse.CreateConnection(); 3. In generated project add reference to new assembly

In such way I will have control how connection is established, also on insert/update/delete statements. Logic behind will like "check does current table is encrypted tables list, if yes run own code, else base.CreateSingleTarget...DQ( ".

What you think, may it work? It is still on analysis level. Also, I didn't done any projects yet on adapter mode. Do adapter.Insert/Update/Delete at very bottom functionality referes to DynamicQueryEngine?

Edit: any con I see is that on every new LLBLGen update I will have to check does mine own code still coresponds to original SD.LLBLGen.Pro.DQE.SqlServer.DynamicQueryEngine functionality.

BR, Marius

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39863
Joined: 17-Aug-2003
# Posted on: 04-Nov-2009 14:47:57   

Vikruolis wrote:

Got from our db guy that answer: "No it is not possible. You cannot insert data through view which is handling automatic decrypting."

Hmm... this is a restriction of sqlserver then? I mean, if you insert data in a view and you have an instead of trigger, that trigger is executed, which should work, but perhaps it gives an error if a column does decryption, I don't know that detail, but I'd be surprised if this is really the case, but as I'm no DBA, I don't know if this issue is valid in this situation.

Now thinking about that change: 1. in separate assembly implement LocalDQE derived from SD.LLBLGen.Pro.DQE.SqlServer.DynamicQueryEngine. Override only these functions: - CreateSingleTargetInsertDQ - CreateSingleTargetUpdateDQ - CreateSingleTargetUpdateDQ - CreateSingleTargetDeleteDQ - because table name differs - CreateSingleTargetDeleteDQ 2. Change DbUtils template: - add using new assembly namespace - SD.LLBLGen.Pro.DQE.SqlServer.DynamicQueryEngine.FactoryToUse.CreateConnection(); -> OwnDQE.SqlServer.LocalDQE.FactoryToUse.CreateConnection();

  1. In generated project add reference to new assembly

You need to change the DQE instance passed to the DaoBase from each generated Dao class. This is a template change. The Dao classes generated pass a new DQE instance to the base ctor, and if you pass your dqe instance instead, it will use your DQE.

To change this, simply create a copy of the dao template and create a NEW templatebindings file with just a binding for that dao template copy (which contains the change in the ctor) and place that templatebindings file above the normal one at tab 2 in the code generation configuration dialog.

In such way I will have control how connection is established, also on insert/update/delete statements. Logic behind will like "check does current table is encrypted tables list, if yes run own code, else base.CreateSingleTarget...DQ( ".

ah ok, makes sense though you could use a static lookup perhaps as well? I mean, as your whole table / view setup is very static, (any change to it will require code changes anyway). So in a static ctor, first load all the tables which are encrypted, then use that lookup in your custom DQE. That way you don't have to go to the db for each query, as it doesn't make sense anyway to store this info in a table as it's static: the table with the encrypted table names has no meaning anyway other than a helper list for the DBA. (and as you can see from the naming of the tables, it's also not really useful for that either)

What you think, may it work? It is still on analysis level. Also, I didn't done any projects yet on adapter mode. Do adapter.Insert/Update/Delete at very bottom functionality referes to DynamicQueryEngine?

Edit: any con I see is that on every new LLBLGen update I will have to check does mine own code still coresponds to original SD.LLBLGen.Pro.DQE.SqlServer.DynamicQueryEngine functionality.

As you override methods, I don't think you'll run into any problems.

Frans Bouma | Lead developer LLBLGen Pro
Vikruolis
User
Posts: 9
Joined: 06-Nov-2008
# Posted on: 20-Nov-2009 08:19:34   

Hello,

Sorry for late answer. Was sick little simple_smile I have managed to make it working. Details bellow:

Introduction There is 4 layers from DB point of view: - DB server - LLBLGen assemblies - DAL - LLBLGen generated source codes - Application/service side

I have some limits: - DB server: no changes at all, I have to ajust what is in server side - LLBLGen assemblies: no changes at all, just because I think it is very bad idea because of future new versions maybe copyright and etc. - DAL: as little changes to generated code as possible. All changes with templates only. IMHO it is risky place, because such custom changes increase chance of bugs or mistakes. - Application/service side: no changes at all. Because such change will require to rebuild and republish many applications. Also application should use same interface to DAL as before.

Solution was to inject some code between DAL and LLBLGen assemblies. To make some workaround for specific functions. Functions are creation of insert, update and delete queries - these one should act regarding some data on database. To simplify usage this injected code was separate library assembly.

Generate code changes (still not created templates but going to do ASAP):

DBUtils.cs


...
        public static DbConnection CreateConnection(string connectionString)
        {
[s]         DbConnection toReturn = SD.LLBLGen.Pro.DQE.SqlServer.DynamicQueryEngine.FactoryToUse.CreateConnection();[/s]
[b]     DbConnection toReturn = Own.LLBLGen.Pro.DQE.SqlServer.DynamicQueryEngine.FactoryToUse.CreateConnection();[/b]
            toReturn.ConnectionString = connectionString;
            return toReturn;
        }
...

GeneratedEntityDAO.cs


...
using DAL.CollectionClasses;
using DAL.HelperClasses;
using DAL;

using SD.LLBLGen.Pro.ORMSupportClasses;
[s]using SD.LLBLGen.Pro.DQE.SqlServer;[/s]
[b]using Own.LLBLGen.Pro.DQE.SqlServer;[/b] // change of name space


namespace DAL.DaoClasses
{
...

Generated DAL project should also include reference to Own.LLBLGen.Pro.DQE.SqlServer assembly.

Helper assembly Own.LLBLGen.Pro.DQE.SqlServer library Consist of 4 classes:

  • internal static class EncryptionHelper - encryption logic hidden here
  • public class DbConnection : System.Data.Common.DbConnection - used to initialize early connection
  • public class DbProviderFactory : System.Data.Common.DbProviderFactory - used to link to own DbConnection
  • public class DynamicQueryEngine : SD.LLBLGen.Pro.DQE.SqlServer.DynamicQueryEngine - own work around for DQE

Lets go through all of these except EncryptionHelper - company rules do not allow to share that info simple_smile

DbConnection I have found that usually very first DQE is generated before any connectiond to DB done. This is not right for me, because I should know encryption info (from special table). So, I made simple hack in ConnectionString property override:


...
    public class DbConnection : System.Data.Common.DbConnection
    {
        #region Variables
        private System.Data.Common.DbConnection _DbConnection;
        private bool _Encrypted;
        private bool _Initialized;
        #endregion
...
        public override string ConnectionString
        {
            get{ return this._DbConnection.ConnectionString; }
            set
            {
                this._DbConnection.ConnectionString = value;
                if (this._Initialized)
                    return;

                try
                {
                    if (this._DbConnection.State != ConnectionState.Open)
                        this._DbConnection.Open();

                    this._Encrypted = EncryptionHelper.IsConnectionEncrypted(this._DbConnection);
                    if (this._Encrypted)
                        EncryptionHelper.InitializeEncryptedConnection(this._DbConnection);
                    this._Initialized = true;
                }
                catch(Exception ex)
                {
                    string exMsg = ex.Message;
                }
            }
        }
...
        public override string Database
        {
            get { return this._DbConnection.Database; }
        }

        public override string DataSource
        {
            get { return this._DbConnection.DataSource; }
        }
...

There are 2 helper functions and all other overrides just redirect functionality to real connection.

DbProviderFactory This class created for single purpose: "DynamicQueryEngine._FactoryToUse = new DbProviderFactory();" - to catch own DbConnection. Other functionality is just redirected to original code.


...
    public class DbProviderFactory : System.Data.Common.DbProviderFactory
    {
        #region Variables
        private System.Data.Common.DbProviderFactory _DbProviderFactory;
        #endregion

        #region Construction
        public DbProviderFactory()
            : base()
        {
            this._DbProviderFactory = null;
        }
        #endregion

        #region Properties
        public System.Data.Common.DbProviderFactory ProviderFactory
        {
            get { return this._DbProviderFactory; }
            set { this._DbProviderFactory = value; }
        }
        #endregion

        #region Overrides
        public override System.Data.Common.DbConnection CreateConnection()
        {
            return new Own.LLBLGen.Pro.DQE.SqlServer.DbConnection(this._DbProviderFactory.CreateConnection());
        }

        public override bool CanCreateDataSourceEnumerator
        {
            get
            {
                return this._DbProviderFactory.CanCreateDataSourceEnumerator;
            }
        }

        public override DbCommand CreateCommand()
        {
            return this._DbProviderFactory.CreateCommand();
        }
...

DynamicQueryEngine Main changes here.


    public class DynamicQueryEngine : SD.LLBLGen.Pro.DQE.SqlServer.DynamicQueryEngine
    {
        #region Variables
        private static DbProviderFactory _FactoryToUse;
        #endregion

        #region Construction
        public DynamicQueryEngine()
            : base()
        { 
        }

        static DynamicQueryEngine()
        {
            DynamicQueryEngine._FactoryToUse = new DbProviderFactory();
        }
        #endregion

        #region Overrides
        #region Dynamic Delete Query construction methods.
        protected override IActionQuery CreateSingleTargetDeleteDQ(IFieldPersistenceInfo[] fieldsPersistenceInfo, IDbConnection connectionToUse, IPredicate deleteFilter)
        {
            return base.CreateSingleTargetDeleteDQ(this.FixFieldPersistanceInfo(fieldsPersistenceInfo, this.TableName(fieldsPersistenceInfo)), (SqlConnection)(DbConnection)connectionToUse, deleteFilter);
        }

        protected override IActionQuery CreateSingleTargetDeleteDQ(IFieldPersistenceInfo[] fieldsPersistenceInfo, IDbConnection connectionToUse, IPredicate deleteFilter, IRelationCollection relationsToWalk)
        {
            return base.CreateSingleTargetDeleteDQ(this.FixFieldPersistanceInfo(fieldsPersistenceInfo, this.TableName(fieldsPersistenceInfo)), (SqlConnection)(DbConnection)connectionToUse, deleteFilter, relationsToWalk);
        }
        #endregion

        #region Dynamic Select Query construction methods.
        protected override IRetrievalQuery CreateSelectDQ(IEntityFieldCore[] selectList, IFieldPersistenceInfo[] fieldsPersistenceInfo, IDbConnection connectionToUse, IPredicate selectFilter, long maxNumberOfItemsToReturn, ISortExpression sortClauses, IRelationCollection relationsToWalk, bool allowDuplicates, IGroupByCollection groupByClause, bool relationsSpecified, bool sortClausesSpecified)
        {
            return base.CreateSelectDQ(selectList, fieldsPersistenceInfo, (SqlConnection)(DbConnection)connectionToUse, selectFilter, maxNumberOfItemsToReturn, sortClauses, relationsToWalk, allowDuplicates, groupByClause, relationsSpecified, sortClausesSpecified);
        }
        #endregion

        #region Dynamic Insert Query construction methods.
        protected override IActionQuery CreateSingleTargetInsertDQ(IEntityFieldCore[] fields, IFieldPersistenceInfo[] fieldsPersistenceInfo, IDbConnection connectionToUse, ref System.Collections.Generic.Dictionary<IEntityFieldCore, IDataParameter> fieldToParameter)
        {
            string tableName = this.TableName(fieldsPersistenceInfo);
            IFieldPersistenceInfo[] newPersistenceInfo = this.FixFieldPersistanceInfo(fieldsPersistenceInfo, tableName);
            return base.CreateSingleTargetInsertDQ(
                this.FixFields(fields, newPersistenceInfo, tableName),
                newPersistenceInfo, 
                (SqlConnection)(DbConnection)connectionToUse, 
                ref fieldToParameter);
        }
        #endregion

        #region Dynamic Update Query construction methods.
        protected override IActionQuery CreateSingleTargetUpdateDQ(IEntityFieldCore[] fields, IFieldPersistenceInfo[] fieldsPersistenceInfo, IDbConnection connectionToUse, IPredicate updateFilter)
        {
            string tableName = this.TableName(fieldsPersistenceInfo);
            IFieldPersistenceInfo[] newPersistenceInfo = this.FixFieldPersistanceInfo(fieldsPersistenceInfo, tableName);
            IActionQuery retVal = base.CreateSingleTargetUpdateDQ(
                this.FixFields(fields, newPersistenceInfo, tableName),
                newPersistenceInfo, 
                (SqlConnection)(DbConnection)connectionToUse,
                this.FixFilter(updateFilter, tableName));
            return retVal;
        }

        protected override IActionQuery CreateSingleTargetUpdateDQ(IEntityFieldCore[] fields, IFieldPersistenceInfo[] fieldsPersistenceInfo, IDbConnection connectionToUse, IPredicate updateFilter, IRelationCollection relationsToWalk)
        {
            string tableName = this.TableName(fieldsPersistenceInfo);
            IFieldPersistenceInfo[] newPersistenceInfo = this.FixFieldPersistanceInfo(fieldsPersistenceInfo, tableName);
            IActionQuery retVal = base.CreateSingleTargetUpdateDQ(
                this.FixFields(fields, newPersistenceInfo, tableName),
                newPersistenceInfo, 
                (SqlConnection)(DbConnection)connectionToUse,
                this.FixFilter(updateFilter, tableName), 
                relationsToWalk);
            return retVal;
        }
        #endregion

        #endregion

        #region Helper functions
        private string TableName(IFieldPersistenceInfo[] fieldsPersistenceInfo)
        {
            return fieldsPersistenceInfo[0].SourceObjectName;
        }

        private IPredicate FixFilter(IPredicate updateFilter, string tableName)
        {
            if (!EncryptionHelper.IsTableEncrypted(tableName))
                return updateFilter;
            string sourceTableName = EncryptionHelper.EncryptedTableName(tableName);
            List<object> filterElements = updateFilter.GetFrameworkElementsInPredicate();
            object filterElement = null;
            PredicateExpression retVal = new PredicateExpression();
            for (int i=0; i<filterElements.Count; i++)
            {
                filterElement = filterElements[i];
                FieldCompareValuePredicate predicate = filterElement as FieldCompareValuePredicate;
                if (predicate != null)
                {
                    FieldCompareValuePredicate newPredicate = new FieldCompareValuePredicate(
                        predicate.Field,
                        this.FixSinglePersistanceInfo(predicate.PersistenceInfo, sourceTableName),
                        predicate.Operator);
                    if (i == 0)
                        retVal.Add(newPredicate);
                    else
                        retVal.AddWithAnd(newPredicate);
                }
            }
            return retVal;
        }

        private IEntityFieldCore[] FixFields(IEntityFieldCore[] fields, IFieldPersistenceInfo[] fieldsPersistenceInfo, string tableName)
        {
            if (!EncryptionHelper.IsTableEncrypted(tableName))
                return fields;
            IEntityFieldCore[] retVal = new IEntityFieldCore[fields.Length];

            
            for (int i = 0; i < fields.Length; i++)
            {
                IEntityFieldCore iEntityFieldCore = fields[i] as IEntityFieldCore;
                EntityField entityField = new EntityField(fields[i], fieldsPersistenceInfo[i]);
                entityField.CurrentValue = fields[i].CurrentValue;
                entityField.IsChanged = fields[i].IsChanged;
                if (EncryptionHelper.IsTableColumnEncrypted(tableName, entityField.SourceColumnName) && fields[i].IsChanged)
                {
                    entityField.ExpressionToApply = EncryptionHelper.EncryptionExpresion(entityField.CurrentValue);
                }
                retVal[i] = entityField;
            }

            return retVal;
        }

        private IFieldPersistenceInfo[] FixFieldPersistanceInfo(IFieldPersistenceInfo[] fieldsPersistenceInfo, string tableName)
        {
            if (!EncryptionHelper.IsTableEncrypted(tableName))
                return fieldsPersistenceInfo;
            IFieldPersistenceInfo[] retVal = new IFieldPersistenceInfo[fieldsPersistenceInfo.Length];

            string sourceTableName = EncryptionHelper.EncryptedTableName(tableName);

            for (int i=0; i<fieldsPersistenceInfo.Length; i++)
            {
                retVal[i] = this.FixSinglePersistanceInfo(fieldsPersistenceInfo[i], sourceTableName);
            }
            return retVal;
        }

        private IFieldPersistenceInfo FixSinglePersistanceInfo(IFieldPersistenceInfo fieldPersistenceInfo, string newTableName)
        {
            return new FieldPersistenceInfo(
                    fieldPersistenceInfo.SourceCatalogName, fieldPersistenceInfo.SourceSchemaName, newTableName,
                    fieldPersistenceInfo.SourceColumnName, fieldPersistenceInfo.SourceColumnIsNullable, fieldPersistenceInfo.SourceColumnDbType,
                    fieldPersistenceInfo.SourceColumnMaxLength, fieldPersistenceInfo.SourceColumnScale, fieldPersistenceInfo.SourceColumnPrecision,
                    fieldPersistenceInfo.IsIdentity, fieldPersistenceInfo.IdentityValueSequenceName, fieldPersistenceInfo.TypeConverterToUse,
                    fieldPersistenceInfo.ActualDotNetType);
        }
        #endregion

        #region Properties
        new public static DbProviderFactory FactoryToUse
        {
            get { 
                return DynamicQueryEngine._FactoryToUse;
            }
        }
        #endregion
    }

Don't judge me simple_smile Originally I am C++ developer, not c# simple_smile I know code have some weakness. I found few places ridiculous for me (maybe I just lack of info): - I can't change FieldPersistenceInfo.SourceObjectName. In order to do that I have to create new FieldPersistenceInfo object. - same with filters.

And EncryptByKey db function call was used in expresion to apply.

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 20-Nov-2009 10:30:04   

Thanks for the feedback.