2014-04-01 112 views
11

我有这样一个库:通过WCF发送通用库的最佳方式是什么?

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发送此,我必须使开放式泛型类clossed。 但问题是我在我的域数据存储库中有许多实体,我希望它应该由客户端决定它需要的实体可能是通过反射或预定义的已知类型。

所以我的问题: 是否有一种聪明或假的方式通过wcf发送这些仿制药服务? 我的目标是我不想为每个实体写这个servicecontract。 非常感谢。

编辑:伙计们,你见过在下面app.config文件这个Here调整:

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

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

+4

不是真的 - WCF是一个基于XML的消息传递系统,它支持任何可以用XML模式表达的东西。不幸的是,XML模式不支持泛型。 WCF只能处理具体类型 - 没有界面,没有泛型 - 真的。 –

+0

你应该看看WCF数据服务。 –

+2

感谢您的回复marc_s我也读过关于这个主题的其他答案,我知道没有这种方式可以通过wcf发送泛型,我试图找到的是一些调整,使得这些通用服务关​​闭而不写入每个实体。 – ThomasBecker

回答

1

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

然后,您可以放弃仿制药,并简单地将事情说成TEntity。在服务内部,您可以将调用映射到通用服务实现;将WCF服务视为非泛型门面来公开您的泛型类。调用者会知道什么类型的期望,因为他们首先给你,所以可以施放。你可以提供一个客户端,这个客户端会在这个如果施放冒犯的情况下放置一个通用包装器

1

由于您使用的是BasicHttpBinding,因此我假定您通过网络发送此内容。我也会假设你正在使用SOAP/XML。如果是这样的情况下,尝试这样的:

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

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

1

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

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

你看看WCF Data Services?这似乎是你想要下手而不用手工制作界面和管道的路线。

正如你所说的,接口在WCF上不是很好。 WCF的IQueryable<T>的期望是一个特定的缺陷,它根本不起作用。即使是IEnumerable<T>也不会一直给出预期的结果。

3

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

嗯,为什么不呢?

让我们尝试以下操作:

这个接口是必要的,因为它会识别可以通过您的Repository.I使用不知道你的你的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); 
} 

通知没有仿制药?这很重要。现在我们需要为我们的Repository创建一个实现。我将给出两个,一个用于Memory,因此可以完成单元测试,另一个用于数据库。

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设计的。这就是说,它很容易适应你的需求。

相关问题