Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the best way to send generic repository via WCF?

Tags:

c#

generics

wcf

I have a repository like this :

public abstract class DbRepository : IDbRepository
{
    public TEntity Insert<TEntity>(TEntity entity) where TEntity : class
    {
        _context.Entry(entity).State = EntityState.Added;
        return entity;
    }

    public TEntity Update<TEntity>(TEntity entity) where TEntity : class
    {
        _context.Entry(entity).State = EntityState.Modified;
        return entity;
    }
}

Service Contract is like :

[ServiceContract]
public interface IDbRepository
{
    [OperationContract]
    TEntity Insert<TEntity>(TEntity entity) where TEntity : class;
    [OperationContract]
    TEntity Update<TEntity>(TEntity entity) where TEntity : class;
}

Now I know I cant send this via wcf, I have to make the open generic class clossed. But the problem is I have many entities in my Domain data repository and I want it should be decided by the client what entity it is needed may be via reflection or predefined known types.

So my question : Is there a smart or fake way to send these generics service via wcf ? My Goal is I dont want to write this servicecontract for each and every entity. Many thanks.

Edit: Guys have you seen this Here Tweak in app.config file below:

<endpoint 
    address="myAddress" binding="basicHttpBinding" 
    bindingConfiguration="myBindingConfiguration1"
    contract="Contracts.IEntityReadService`1[[Entities.mySampleEntity, Entities]],   Service.Contracts"  />

Can somebody please explain this how this contract has been implemented. Has anybody tried to implement this tweak in app.config file. I have tried but not working for me for now. Need helpful answer !

like image 204
ThomasBecker Avatar asked Apr 01 '14 11:04

ThomasBecker


3 Answers

Have you look into WCF Data Services? This seems to be the route you want to go down without hand crafting the interfaces and plumbing yourself.

As you have stated, interfaces are not good over WCF. One particular flaw is the expectation of the IQueryable<T> over WCF, which does not work at all. Even IEnumerable<T> doesn't give the expected results all of the time.

like image 193
Dominic Zukiewicz Avatar answered Nov 09 '22 02:11

Dominic Zukiewicz


Is there a smart or fake way to send these generics service via wcf ? My Goal is I dont want to write this servicecontract for each and every entity. Many thanks.

hmm, why not?

Lets us try the following :

This interface is necessary as it will identify which objects can be used by your Repository.I don't know what your implementation of your T Entity is or how your CRUD operations work; however, just in case you don't have it covered, we're also going to add the methid GetPrimaryKeys.

public interface IRepositoryEntry
{
    IList<String> GetPrimaryKeys();
}

So now we need a repository, since your biggest concern is that you don't want to rewrite code you should try something like this:

This implementation means that whatever our database entries are, they must support the default constructor. This is important for the implementation of this this interface :

public interface IRepository<T> where T : IRepositoryEntry, new()
{
    event EventHandler<RepositoryOperationEventArgs> InsertEvent;
    event EventHandler<RepositoryOperationEventArgs> UpdateEvent;
    event EventHandler<RepositoryOperationEventArgs> DeleteEvent;
    IList<String> PrimaryKeys { get; }

    void Insert(T Entry);
    void Update(T Entry);
    void Delete(Predicate<T> predicate);
    bool Exists(Predicate<T> predicate);
    T Retrieve(Predicate<T> predicate);

    IEnumerable<T> RetrieveAll();
}

Now we will make our service:

[ServiceContract]
public interface IDbRepository
{
    [OperationContract]
    object Insert(object entity);
    [OperationContract]
    object Update(object entity);
}

Notice no generics? That's important. Now we need to have a creative implementation for our Repository. I'm going to give two, one for Memory so unit testing can be done and the other for a database.

public class OracleRepository 
{
    const string User = "*";
    const string Pass = "*";
    const string Source = "*";
    const string ConnectionString = "User Id=" + User + ";" + "Password=" + Pass + ";" + "Data Source=" + Source + ";";

    public static  IDbConnection GetOpenIDbConnection(){
        //Not really important; however, for this example I Was using an oracle connection
        return new OracleConnection(ConnectionString).OpenConnection(); 
    }

    protected IEnumerable<String> GetEntryPropertyNames(Type type){
        foreach (var propInfo in type.GetProperties())
            yield return propInfo.Name;
    }
}

 public class OracleRepository<T> : OracleRepository,IDisposable, IRepository<T> where T :  IRepositoryEntry, new()
    {
        #region Public EventHandlers
        public event EventHandler<RepositoryOperationEventArgs> InsertEvent;
        public event EventHandler<RepositoryOperationEventArgs> UpdateEvent;
        public event EventHandler<RepositoryOperationEventArgs> DeleteEvent;
        #endregion
        #region Public Properties
        public IList<String> PrimaryKeys{ get { return primaryKeys.AsReadOnly(); } }
        public IList<String> Properties { get; private set; }
        public String InsertText { get; private set; }
        public String UpdateText { get; private set; }
        public String DeleteText { get; private set; }
        public String SelectText { get; private set; }
        #endregion
        #region Private fields
        List<String> primaryKeys;
        IDbConnection connection;
        IDbTransaction transaction;
        bool disposed;
        #endregion
        #region Constructor(s)
        public OracleRepository()
        {
            primaryKeys = new List<String>(new T().GetPrimaryKeys());
            Properties = new List< String>(GetEntryPropertyNames(typeof(T))).AsReadOnly();
            SelectText = GenerateSelectText();
            InsertText = GenerateInsertText();
            UpdateText = GenerateUpdateText();
            DeleteText = GenerateDeleteText();
            connection = GetOpenIDbConnection();
        }
        #endregion
        #region Public Behavior(s)
        public void StartTransaction() 
        {
            if (transaction != null)
                throw new InvalidOperationException("Transaction is already set. Please Rollback or commit transaction");
            transaction = connection.BeginTransaction();
        }
        public void CommitTransaction() 
        {
            using(transaction)
                transaction.Commit();
            transaction = null;
        }
        public void Rollback() 
        {
            using (transaction)
                transaction.Rollback();
            transaction = null;
        }
        public void Insert(IDbConnection connection, T entry)
        {
            connection.NonQuery(InsertText, Properties.Select(p => typeof(T).GetProperty(p).GetValue(entry)).ToArray());
            if (InsertEvent != null) InsertEvent(this, new OracleRepositoryOperationEventArgs() { Entry = entry, IsTransaction = (transaction != null) });
        }
        public void Update(IDbConnection connection, T entry)
        {
            connection.NonQuery(UpdateText, Properties.Where(p => !primaryKeys.Any(k => k == p)).Concat(primaryKeys).Select(p => typeof(T).GetProperty(p).GetValue(entry)).ToArray());
            if (UpdateEvent != null) UpdateEvent(this, new OracleRepositoryOperationEventArgs() { Entry = entry, IsTransaction = (transaction != null) });
        }
        public void Delete(IDbConnection connection, Predicate<T> predicate)
        {
            foreach (var entry in  RetrieveAll(connection).Where(new Func<T, bool>(predicate)))
            {
                connection.NonQuery(DeleteText, primaryKeys.Select(p => typeof(T).GetProperty(p).GetValue(entry)).ToArray());
                if (DeleteEvent != null) DeleteEvent(this, new OracleRepositoryOperationEventArgs() { Entry = null, IsTransaction = (transaction != null) });
            }
        }
        public T Retrieve(IDbConnection connection, Predicate<T> predicate)
        {
            return RetrieveAll(connection).FirstOrDefault(new Func<T, bool>(predicate));
        }
        public bool Exists(IDbConnection connection, Predicate<T> predicate)
        {
            return RetrieveAll(connection).Any(new Func<T, bool>(predicate));
        }
        public IEnumerable<T> RetrieveAll(IDbConnection connection)
        {
            return connection.Query(SelectText).Tuples.Select(p => RepositoryEntryBase.FromPlexQueryResultTuple(new T(), p) as T);
        }
        #endregion
        #region IRepository Behavior(s)
        public void Insert(T entry)
        {
            using (var connection = GetOpenIDbConnection())
                Insert(connection, entry);
        }
        public void Update(T entry)
        {
            using (var connection = GetOpenIDbConnection())
                Update(connection, entry);
        }

        public void Delete(Predicate<T> predicate)
        {
            using (var connection = GetOpenIDbConnection())
                Delete(connection, predicate);
        }

        public T Retrieve(Predicate<T> predicate)
        {
            using (var connection = GetOpenIDbConnection())
                return Retrieve(connection, predicate);         
        }
        public bool Exists(Predicate<T> predicate)
        {
            using (var connection = GetOpenIDbConnection())
                return Exists(predicate);
        }

        public IEnumerable<T> RetrieveAll()
        {
            using (var connection = GetOpenIDbConnection())
                return RetrieveAll(connection);
        }
        #endregion
        #region IDisposable Behavior(s)
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
        #region Protected Behavior(s)
        protected virtual void Dispose(Boolean disposing)

        {
            if(disposed)
                return;
            if (disposing)
            {
                if(transaction != null)
                    transaction.Dispose();
                if(connection != null)
                    connection.Dispose();
            }
            disposed = true;
        }
        #endregion
        #region Private Behavior(s)
        String GenerateInsertText()
        {
            String statement = "INSERT INTO {0}({1}) VALUES ({2})";
            //Do first entry here becasse its unique input.
            String columnNames = Properties.First();

            String delimiter = ", ";
            String bph = ":a";

            String placeHolders = bph + 0;

            //Start @ 1 since first entry is already done
            for (int i = 1; i < Properties.Count; i++)
            {
                columnNames += delimiter + Properties[i];
                placeHolders += delimiter + bph + i;
            }

            statement = String.Format(statement, typeof(T).Name, columnNames, placeHolders);
            return statement;
        }
        String GenerateUpdateText()
        {
            String bph = ":a";
            String cvpTemplate = "{0} = {1}";
            String statement = "UPDATE {0} SET {1} WHERE {2}";

            //Can only set Cols that are not a primary Keys, Get those Columns
            var Settables = Properties.Where(p => !PrimaryKeys.Any(k => k == p)).ToList();

            String cvp = String.Format(cvpTemplate, Settables.First(), bph + 0);
            String condition = String.Format(cvpTemplate, PrimaryKeys.First(), bph + Settables.Count);

            //These are the values to be set | Start @ 1 since first entry is done above.
            for (int i = 1; i < Settables.Count; i++)
                cvp += ", " + String.Format(cvpTemplate, Settables[i], bph + i);

            //This creates the conditions under which the values are set. | Start @ 1 since first entry is done above.
            for (int i = Settables.Count + 1; i < Properties.Count; i++)
                condition += " AND " + String.Format(cvpTemplate, PrimaryKeys[i - Settables.Count], bph + i);

            statement = String.Format(statement, typeof(T).Name, cvp, condition);
            return statement;
        }
        String GenerateDeleteText()
        {
            String bph = ":a";
            String cvpTemplate = "{0} = {1}";
            String statement = "DELETE FROM {0} WHERE {1}";
            String condition = String.Format(cvpTemplate, PrimaryKeys.First(), bph + 0);

            for (int i = 1; i < PrimaryKeys.Count; i++)
                condition += " AND " + String.Format(cvpTemplate, PrimaryKeys[i], bph + i);

            statement = String.Format(statement, typeof(T).Name, condition);
            return statement;
        }
        String GenerateSelectText()
        {
            String statement = "SELECT * FROM {0}";
            statement = String.Format(statement, typeof(T).Name);
            return statement;
        }
        #endregion
        #region Destructor
        ~OracleRepository()
        {
            Dispose(false);
        }
        #endregion
    }

The second implementation for in memory operation is this :

public class InMemoryRepository<T> : IRepository<T> where T : IRepositoryEntry, new()
{
    //RepositoryEntryBase,
    public event EventHandler<RepositoryOperationEventArgs> InsertEvent;
    public event EventHandler<RepositoryOperationEventArgs> UpdateEvent;
    public event EventHandler<RepositoryOperationEventArgs> DeleteEvent;

    public IList<String> PrimaryKeys { get; protected set; }
    List<T> data;
    public InMemoryRepository()
    {
        PrimaryKeys = new List<String>(new T().GetPrimaryKeys());
        data = new List<T>();
    }

    public void Insert(T Entry)
    {
        if (Get(Entry) != null)
            throw new Exception("Duplicate Entry - Identical Key already exists");
        data.Add(Entry);
        if (InsertEvent != null)
            InsertEvent(this, new RepositoryOperationEventArgs() { Entry = Entry });
    }

    public void Update(T Entry)
    {
        var obj = Get(Entry);
        if (obj == null)
            throw new Exception("Object does not exist");
        obj = Entry;
        if (UpdateEvent != null)
            UpdateEvent(this, new RepositoryOperationEventArgs() { Entry = obj });
    }

    public void Delete(Predicate<T> predicate)
    {
        data.RemoveAll(predicate);
        if (DeleteEvent != null)
            DeleteEvent(this, new RepositoryOperationEventArgs() { Entry = null });
    }

    public bool Exists(Predicate<T> predicate)
    {
        return data.Exists(predicate);
    }

    public T Retrieve(Predicate<T> predicate)
    {
        return data.FirstOrDefault(new Func<T, bool>(predicate));
    }

    public IEnumerable<T> RetrieveAll()
    {
        return data.ToArray();
    }

    T Get(T Entry)
    {
        //Returns Entry based on Identical PrimaryKeys
        Type entryType = typeof(T);
        var KeyPropertyInfo = entryType.GetProperties().Where(p => PrimaryKeys.Any(p2 => p2 == p.Name));
        foreach (var v in data)
        {
            //Assume the objects are identical by default to prevent false positives.
            Boolean AlreadyExists = true;
            foreach (var property in KeyPropertyInfo)
                if (!property.GetValue(v).Equals(property.GetValue(Entry)))
                    AlreadyExists = false;
            if (AlreadyExists)
                return v;
        }
        return default(T);
    }
}

Whew, that was a lot of code. Now there are a few non standard functions. These are what they all are:

public static class IDbConnectionExtensions
{

    public static IDbCommand CreateCommand(this IDbConnection Conn, string CommandText, params object[] Parameters)
    {
        var Command = Conn.CreateCommand();
        Command.CommandText = CommandText;
        foreach (var p in Parameters ?? new object[0])
            Command.Parameters.Add(Command.CreateParameter(p));
        return Command;
    }

    public static IDbDataParameter CreateParameter(this IDbCommand Command, object Value)
    {
        var Param = Command.CreateParameter();
        Param.Value = Value;
        return Param;
    }

    public static PlexQueryResult Query(this IDbConnection conn, String CommandText, params object[] Arguments)
    {
        using (var Comm = conn.CreateCommand(CommandText, Arguments))
        using (var reader = Comm.ExecuteReader(CommandBehavior.KeyInfo))
            return new PlexQueryResult(reader);
    }
    public static int NonQuery(this IDbConnection conn, String CommandText, params object[] Arguments)
    {
        using (var Comm = conn.CreateCommand(CommandText, Arguments))
            return Comm.ExecuteNonQuery();
    }

    public static IDbConnection OpenConnection(this IDbConnection connection)
    {
        connection.Open();
        return connection;
    }
}

Now, how we tie everything together is simple, this one I'm writing off the top of my head with no editor so please bear with me :

Lets say we have the following class which inherits from IRepostoryEntry:

//Feel free to ignore RepostoryEntryBase
public class COMPANIES : RepositoryEntryBase, IRepositoryEntry
{
    public string KEY { get; set; } //KEY   VARCHAR2(20)    N   
    public int COMPANY_ID { get; set; }   //COMPANY_ID  NUMBER(10)  N       
    public string DESCRIPTION { get; set; }//DESCRIPTION    VARCHAR2(100)   N

    public COMPANIES() : base ()
    {
        primaryKeys.Add("COMPANY_ID");
    }
}

public abstract class DbRepository : IDbRepository
{
    public Dictionary<Type,IRepository> Repositories { get;set; }

    public DbRepository(){
        Repositories = new Dictionary<Type,IRepository>();
        Repositories .add(typeof(COMPANIES)),new OracleRepository<COMPANIES>());
    }
    public object Insert(object entity)
    {
        if(!(entity is IRepositoryEntry))
            throw new NotSupportedException("You are bad and you should feel bad");
        if(!Repositories.ContainsKey(entity.GetType()))
            throw new NotSupportedException("Close but no cigar");
         Dictionary[entity.GetType()].Insert(entity);
    }

    //You can add additional operations here:
}

That had to be the longest answer I ever wrote: I built this DLL to get my jump started on this method of storing data; however, its really intended for Oracle. That said, its easy to adapt to your needs.

like image 31
Aelphaeis Avatar answered Nov 09 '22 04:11

Aelphaeis


My suggestion is to not fight WCF constraints and possibly making your solution more complex than necessary. Instead, try using code generators or rollout your own to generate the numerous service contracts your application requires.

like image 2
Mark Menchavez Avatar answered Nov 09 '22 04:11

Mark Menchavez