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

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

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

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

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


10668
2018-04-01 11:42


起源

不是 - WCF是一个基于XML的消息系统,它支持任何可以用XML模式表达的东西。不幸的是,XML架构确实如此 不 支持泛型。 WCF只能处理具体类型 - 没有接口,没有泛型 - 真的。 - marc_s
感谢回复marc_s我也读到了关于这个主题的其他答案,我明白没有这样的方法通过wcf发送泛型,我想要找到的是一些调整,使这些通用服务关​​闭而无需为每个实体编写。 - ThomasBecker
@ThomasBecker:您可以选择添加此作为答案。 - Patrick Hofman
@PatrickHofman我仍在尝试使用反射和一些app.config调整的一些组合,一旦我得到一些工作我将发布:) - ThomasBecker
我也对解决方案感兴趣..感谢链接@ThomasBecker - MKMohanty


答案:


你看看了吗? WCF数据服务?这似乎是您想要下载的路线,无需手工制作接口和管道自己。

如您所述,接口不如WCF好。一个特别的缺点是期望 IQueryable<T> 在WCF上,根本不起作用。甚至 IEnumerable<T> 不会一直给出预期的结果。


4
2017-07-03 08:18





是否有智能或虚假的方式通过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:
}

这必须是我写过的最长的答案: 我建造了 这个DLL 让我开始研究这种存储数据的方法;但是,它真正用于Oracle。也就是说,它很容易适应您的需求。


3
2017-07-14 19:31





在当前实现中,您没有在合同界面上设置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;
    }
}

它可能看起来多余,但我相信泛型会意外地混淆操作名称,您需要指定它们。


2
2018-06-08 02:35





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

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


1
2018-04-03 17:08





由于您使用的是BasicHttpBinding,我将假设您通过网络发送此内容。我还假设您正在使用SOAP / XML。如果是这种情况尝试这样的事情:

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

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


1
2018-05-23 17:02





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


1
2018-06-03 06:43