Какой лучший способ отправить общий репозиторий через 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 ниже:

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

Может кто-нибудь объяснить это, как этот контракт был реализован. Кто-нибудь пытался реализовать эту настройку в файле app.config. Я пробовал, но пока не работал на меня. Нужен полезный ответ!

Ответ 1

Загляните в службы передачи данных WCF? Кажется, это маршрут, по которому вы хотите спуститься без ручного управления интерфейсами и сантехникой самостоятельно.

Как вы уже сказали, интерфейсы не очень хороши для WCF. Один из недостатков - ожидание IQueryable<T> по WCF, которое вообще не работает. Даже IEnumerable<T> не дает ожидаемых результатов все время.

Ответ 2

Есть ли умный или поддельный способ отправки этих дженериков через wcf? Моя цель: я не хочу писать этот servicecontract для каждого и каждой организации. Большое спасибо.

Хмм, почему бы и нет?

Позволяет нам попробовать следующее:

Этот интерфейс необходим, поскольку он будет определять, какие объекты могут использоваться вашим репозиторием. Я не знаю, какова ваша реализация вашей T-организации или как ваш CRUD работает; однако, на всякий случай, если вы его не покрыли, мы также добавим metid 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);
}

Обратите внимание на отсутствие дженериков? Это важно. Теперь нам нужно создать креативную реализацию для нашего репозитория. Я собираюсь дать два, один для 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:
}

Это был самый длинный ответ, который я когда-либо писал: Я построил эту DLL, чтобы начать мой переход по этому методу хранения данных; однако он действительно предназначен для Oracle. Тем не менее, его легко адаптировать к вашим потребностям.

Ответ 3

В вашей текущей реализации у вас нет ваших атрибутов 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 генерировать WSDL для этого контракта и разрешить вам размещать услугу? Проблема, с которой вы столкнулись, до сериализации и известных типов? Если это так, вы можете посмотреть SharedTypeResolver в этом сообщении в блоге. Это довольно простой и удивительный кусок магии, который позволяет вам прозрачно передавать любой подкласс контракта с данными без объявления его, пока тип разделяется между клиентом и сервером.

Вы могли бы отказаться от дженериков и просто говорить о вещах как TEntity. Внутри службы вы можете сопоставить вызов с вашими универсальными реализациями; подумайте о службе WCF как неосновном фасаде, чтобы разоблачить ваши общие классы. Вызывающий будет знать, какого типа ожидать, потому что он дал его вам, в первую очередь, поэтому он может быть использован. Вы можете предоставить клиенту, который ставит обходную обертку вокруг этого, если вы бросаете оскорбления.

Ответ 5

Поскольку вы используете BasicHttpBinding, я собираюсь предположить, что вы отправляете это через Интернет. Я также предполагаю, что вы используете SOAP/XML. Если это так, попробуйте что-то вроде этого:

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

Теперь все, что вам нужно сделать, - это проанализировать полученный XML и вернуть любой XML, который вам подходит! Я сделал что-то подобное, когда у меня был абстрактный базовый класс, который имеет 2 метода для генерации XML для представления объекта и один для анализа XML для заполнения свойств объекта. Один из недостатков этого заключается в том, что для реализации интерфейса все равно нужно знать обо всех типах объектов в иерархии классов.

Ответ 6

Мое предложение состоит в том, чтобы не бороться с ограничениями WCF и, возможно, сделать ваше решение более сложным, чем необходимо. Вместо этого попробуйте использовать генераторы кода или развернуть свои собственные, чтобы генерировать многочисленные контракты на обслуживание, требуемые вашим приложением.