通过WCF发送通用存储库的最佳方法是什么?

时间:2014-04-01 11:42:03

标签: c# wcf generics

我有一个像这样的存储库:

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;
    }
}

服务合同如下:

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

现在我知道我不能通过wcf发送这个,我必须关闭开放的泛型类。 但问题是我的域数据存储库中有很多实体,我希望它应该由客户端决定所需的实体可能是通过反射或预定义的已知类型。

所以我的问题: 是否有智能或虚假的方式通过wcf发送这些泛型服务? 我的目标是我不想为每个实体编写此服务合同。 非常感谢。

编辑:你们在下面的app.config文件中看到了这个Here Tweak:

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

有人可以解释一下这个合同是如何实施的。 有没有人试图在app.config文件中实现这个调整。我已经尝试过,但现在还没有为我工作。需要有用的答案!

6 个答案:

答案 0 :(得分:4)

你看看WCF Data Services了吗?这似乎是你想要走下去而不用手工制作接口和自己管道的路线。

正如您所说,接口不如WCF好。一个特别的缺点是IQueryable<T>对WCF的期望,这根本不起作用。即使IEnumerable<T>也不会一直给出预期的结果。

答案 1 :(得分:3)

  

是否有通过wcf发送这些仿制药服务的智能或虚假方式?   我的目标是我不想为每个人写这个服务合同   每个实体。非常感谢。

嗯,为什么不呢?

让我们尝试以下方法:

此界面是必需的,因为它将识别您的存储库可以使用哪些对象。我不知道您的T实体的实现是什么或您的CRUD操作如何工作;但是,如果你没有覆盖它,我们也将添加methid GetPrimaryKeys。

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

所以现在我们需要一个存储库,因为你最关心的是你不想重写代码,你应该尝试这样的东西:

此实现意味着无论我们的数据库条目是什么,它们都必须支持默认构造函数。这对于此接口的实现非常重要:

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();
}

现在我们将提供服务:

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

请注意没有泛型?这很重要。现在我们需要为我们的存储库创建一个创造性的实现。我将给两个,一个用于内存,因此可以完成单元测试,另一个用于数据库。

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
    }

内存操作的第二个实现是:

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);
    }
}

哇,那是很多代码。现在有一些非标准功能。以下是它们的全部内容:

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;
    }
}

现在,我们如何把所有东西捆绑在一起很简单,这个我没有编辑就写下了我的头脑所以请耐心等待我:

假设我们有以下类继承自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:
}

那是我写过的最长的答案: 我构建了this DLL来启动这种存储数据的方法;但是,它真正用于Oracle。也就是说,它很容易适应您的需求。

答案 2 :(得分:2)

我的建议是不要对抗WCF约束,并可能使解决方案比必要的更复杂。相反,尝试使用代码生成器或自行部署生成应用程序所需的众多服务合同。

答案 3 :(得分:2)

在您当前的实现中,您没有在合约界面上设置OperationContract属性。

尝试这样的事情:

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

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

这可能看起来多余,但我认为仿制品会不小心弄乱操作名称,并且您需要指定它们。

答案 4 :(得分:1)

WCF是否会为该合同生成WSDL并允许您托管该服务?您遇到的问题是序列化和已知类型吗?如果是这样,您可能需要查看this blog post中的SharedTypeResolver。这是一个非常简单和令人敬畏的魔术,它允许您透明地传递数据协定的任何子类,而不必声明它,只要该类型在客户端和服务器之间共享。

然后你可以免除泛型,简单地谈论TEntity。在服务内部,您可以将调用映射到通用服务实现;将WCF服务视为非通用外观以公开您的泛型类。调用者会知道期望什么类型,因为他们首先给你,所以可以投。你可以提供一个客户端,如果施放冒犯,就会在其周围放置一个通用的包装器。

答案 5 :(得分:1)

由于您使用的是BasicHttpBinding,我假设您是通过网络发送的。我还假设你正在使用SOAP / XML。如果是这种情况,请尝试以下方法:

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

现在您所要做的就是解析您收到的XML并返回您认为合适的XML!我做了类似的事情,我有一个抽象基类,有2个方法,一个用于生成XML来表示对象,另一个用于解析XML以填充对象的属性。这样做的一个缺点是你的接口实现仍然需要知道类层次结构中所有类型的对象。