Динамическое использование инструментов SqlBulkTools

#c# #sql-server #sqlbulktools

Вопрос:

Я пытаюсь создать универсальный sqlbulktools метод, чтобы он мог использовать любую модель в качестве входных данных. Вот основной метод:

 public void InsertOrUpdateTableMatchOnString<T>(string tableName, List<T> models) where T : class, IModel
    {
        BulkOperations bulk = new BulkOperations();

        using (TransactionScope trans = new TransactionScope())
        {
            using (SqlConnection conn = new SqlConnection(MyConString))
            {
                bulk.Setup<T>()
                    .ForCollection(models)
                    .WithTable(tableName)
                    .AddAllColumns()
                    .BulkInsertOrUpdate()
                    .SetIdentityColumn(x => x.Id)
                    .MatchTargetOn(x => x.AgreementId)
                    .MatchTargetOn(x => x.GetMatchOn())
                    .Commit(conn);
            }

            trans.Complete();
        }
    }
 

Вот пример модели, которая используется в качестве входных данных:

 public class Company : IModel
{
    public long? Id { get; set; }
    public string Name { get; set; }
    public string BaseCurrency { get; set; }
    public string Address1 { get; set; }
    public string PostalCode { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
    public string TelephoneNumber { get; set; }
    public string Contact { get; set; }
    public string WebSite { get; set; }
    public string Email { get; set; }
    public string CINumber { get; set; }
    public DateTime SignUpDate { get; set; }
    public int AgreementId { get; set; }

    public PropertyInfo GetMatchOn()
    {
        return this.GetType().GetProperties().First(p => p.Name == "Name");
    }

    public Company SetProperties(CompanyData data, Customer customer)
    {
        Name = data.Name;
        BaseCurrency = data.BaseCurrencyHandle.Code;
        Address1 = data.Address1;
        PostalCode = data.PostalCode;
        City = data.City;
        Country = data.Country;
        TelephoneNumber = data.TelephoneNumber;
        Contact = data.Contact;
        WebSite = data.WebSite;
        Email = data.Email;
        CINumber = data.CINumber;
        SignUpDate = data.SignUpDate;
        AgreementId = customer.ecid;

        return this;
    }
}
 

И вот интерфейс, который реализует модель:

 public interface IModel
{
    long? Id { get; set; }
    int AgreementId { get; set; }
    PropertyInfo GetMatchOn();
}
 

Проблема в том , что я не могу использовать GetMatchOn() метод в .MatchTargetOn(x => x.GetMatchOn()) вызове метода — он будет принимать только такое свойство, как .MatchTargetOn(x => x.AgreementId) , иначе я получу ошибку. Но мне нужно, чтобы это был метод, так как это не одно и то же свойство от модели к модели, которое я хочу сопоставить. Возможно ли это каким-либо образом?

Комментарии:

1. Чтобы сделать это эффективно , вам нужна перегрузка MatchTargetOn , которая занимает a PropertyInfo , чтобы обеспечить динамическое использование. Вы также можете рассмотреть перегрузку, которая просто принимает имя свойства как a string и эффективно реализует то, что GetMatchOn происходит сейчас (кстати, использование nameof там лучше, чем жестко закодированный литерал), поскольку она сохраняет проверку, которая PropertyInfo действительно относится к свойству рассматриваемого класса (а не к какому-либо свойству другого класса).

2. Обратите внимание, что оптимизацию динамического свойства доступ (который вам может понадобиться, когда вы копируете много и много строк), то ли сделать через имя, PropertyInfo или Expression ,- это его собственная задача; сост делегатов простой, но не проанализировать, а также динамический метод, который имеет доступ ко всем свойствам в типизированном образе (хотя производя этот метод значительно сложнее получить право).

3. Спасибо, что так быстро ответили 🙂 Извините, если я вас неправильно понял, но, похоже, вы думаете, что я сам могу сделать перегрузку MatchTargetOn, но SqlBulkTools — это пакет, который я использую, поэтому я не могу изменить эту часть кода-означает ли это, что это невозможно сделать?

4. Это все еще можно сделать, если нет перегрузки, но менее удобно. MatchTargetOn примет либо a Func<T, TProp> , либо an Expression<Func<T, TProp>> для указания свойства; вы можете построить любой из них динамически, хотя это совсем не красиво, на самом деле, от задействованного кода у меня немного болит голова. Вы могли бы рассмотреть возможность инвертирования ответственности и передачи любого типа .BulkInsertOrUpdate() в метод IModel , чтобы сам тип мог вызывать .MatchTargetOn() . По общему признанию, это, вероятно, также позволит ему вызывать методы, которые он не должен, но это проще.

Ответ №1:

Спасибо, что указали мне правильное направление, Йерун Мостерт. Что в итоге сработало для меня, так это добавление метода в модели — fx, подобного этому:

 public Expression<Func<Company, object>> GetMatchOn()
        {
            ParameterExpression parameter = Expression.Parameter(typeof(Company));
            MemberExpression property = Expression.Property(parameter, "Name");
            UnaryExpression convertedProperty = Expression.Convert(property, typeof(object));
            Expression<Func<Company, object>> lambda = Expression.Lambda<Func<Company, object>>(convertedProperty, parameter);

            return lambda;
        }
 

а затем вызываем его из основного метода следующим образом:

 public void InsertOrUpdateTableMatchOnString<T>(string tableName, List<T> models) where T : class, IModel<T, object>
        {
            BulkOperations bulk = new BulkOperations();

            using (TransactionScope trans = new TransactionScope())
            {
                using (SqlConnection conn = new SqlConnection(MyConString))
                {
                    bulk.Setup<T>()
                        .ForCollection(models)
                        .WithTable(tableName)
                        .AddAllColumns()
                        .BulkInsertOrUpdate()
                        .SetIdentityColumn(x => x.Id)
                        .MatchTargetOn(x => x.AgreementId)
                        .MatchTargetOn(models.First().GetMatchOn())
                        .Commit(conn);
                }

                trans.Complete();
            }
        }