【问题标题】:What's the best way to send generic repository via WCF?通过 WCF 发送通用存储库的最佳方式是什么?
【发布时间】:2014-07-15 03:31:08
【问题描述】:

我有一个这样的存储库:

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 文件中实现这个调整。我已经尝试过,但现在不适合我。需要有用的答案!

【问题讨论】:

  • 并非如此 - WCF 是一个基于 XML 的消息传递系统,它支持任何可以用 XML 模式表达的东西。不幸的是,XML 模式 支持泛型。 WCF 只能处理具体类型——没有接口,没有泛型——真的。
  • 感谢 marc_s 的回复,我也阅读了有关此主题的其他答案,并且我知道没有这样的方法可以通过 wcf 发送泛型,我试图找到一些调整,使这些通用服务没有写每个实体就关闭了。
  • @ThomasBecker:你可以选择添加这个作为答案。
  • @PatrickHofman 我仍在尝试一些与反射的组合和一些 app.config 调整,一旦我得到一些工作我会发布:)
  • 我也对解决方案感兴趣.. 感谢@ThomasBecker 的链接

标签: c# wcf generics


【解决方案1】:

你看过WCF Data Services吗?这似乎是您想要走的路线,而无需手工制作界面和自己进行管道。

正如您所说,接口不如 WCF。一个特殊的缺陷是对 WCF 的 IQueryable&lt;T&gt; 的期望,这根本不起作用。即使IEnumerable&lt;T&gt; 也不会一直给出预期的结果。

【讨论】:

    【解决方案2】:

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

    嗯,为什么不呢?

    让我们尝试以下方法:

    这个接口是必要的,因为它将识别哪些对象可以被你的存储库使用。我不知道你的 T 实体的实现是什么或者你的CRUD 操作是如何工作的;但是,以防万一您没有涵盖它,我们还将添加方法 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。也就是说,它很容易适应您的需求。

    【讨论】:

      【解决方案3】:

      我的建议是不要与 WCF 限制作斗争,并可能使您的解决方案变得比必要的复杂。相反,请尝试使用代码生成器或推出您自己的代码生成器来生成您的应用程序所需的大量服务合同。

      【讨论】:

        【解决方案4】:

        在您当前的实现中,您没有在合约接口上设置 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;
            }
        }
        

        这似乎是多余的,但我相信泛型会意外地与操作名称混淆,您需要指定它们。

        【讨论】:

          【解决方案5】:

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

          然后,您可以放弃泛型,简单地以TEntity 谈论事物。在服务内部,您可以将调用映射到您的通用服务实现;将 WCF 服务视为公开泛型类的非泛型外观。调用者将知道预期的类型,因为他们首先将其提供给您,因此可以强制转换。如果强制转换有问题,您可以提供一个在此周围放置通用包装器的客户端。

          【讨论】:

            【解决方案6】:

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

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

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

            【讨论】:

              猜你喜欢
              • 2021-03-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-09-10
              • 2014-01-15
              • 2014-12-17
              • 1970-01-01
              相关资源
              最近更新 更多