2015-02-06 47 views
1

我想编写一个通用的扩展方法两类链接/对象链接到类通用的方法/对象

类:

public class Model1 
{ 
    public Guid Model2ID { get; set; } 
    public Model2 Model2 { get; set; } 

    // .. other insignificant properties to this question 
} 

public class Model2 
{ 
    public Guid ID { get; set; } 
} 

我试着写,看起来方法是这样的:

public static class ObjectExtensions 
{ 
    public static Void LinkTo<T,U>(
     this T m1, 
     IEnumerable<U> m2, 
     m1p // expression to select what property on m1 is populated 
     Action<bool,T,IEnumerable<U>> expression) 
    { 
     // not sure about this part at all 

     m1p = u2.FirstOrDefault(expression); 
    } 
} 

用法:

var listOfModel2 = //.... 

Model1.LinkTo(listOfModel2, m => m.Model2, (m1,m2) m1.Model2Id == m2.ID); 
+1

我已经删除了关于MVC的所有评论,因为该框架与您的问题无关。 – 2015-02-06 16:49:11

+0

我的错误,措辞严重的问题 - 模型1和模型2来自完全独立的数据源。链接他们唯一的事情就是Guid ID。我可能不应该提到JSON - 这只是巧合,某些数据源可用。 – Octopoid 2015-02-06 16:51:43

+0

哦,你是否试图创建一个通用函数来完成所有模型的工作? – 2015-02-06 16:54:37

回答

1

正如我们在聊天中所讨论的那样,我建议使用轻量级的EF上下文(带反射)。这是完全自定义和动态的,您只需在模型上使用KeyAttributeForeignKeyAttribute,然后将模型添加到您的自定义Context。我只接通了添加,因为剩下的你应该可以自己弄清楚。

类:

public class Staff 
{ 
    [Key] 
    public Guid Id { get; set; } 

    [ForeignKey("Contact")] 
    public Guid ContactId { get; set; } 
    public Contact Contact { get; set; } 
} 

public class Contact 
{ 
    [Key] 
    public Guid Id { get; set; } 

    public string Name { get; set; } 

    [ForeignKey("Dog")] 
    public Guid DogId { get; set; } 
    public Dog Dog { get; set; } 
} 

public class Dog 
{ 
    [Key] 
    public Guid Id { get; set; } 
} 

语境:

public class Context 
{ 
    //Add as many of these as you want. Don't forget to make public properties for them! 
    private ObservableCollection<Staff> _staffs = new ObservableCollection<Staff>(); 
    private ObservableCollection<Contact> _contacts = new ObservableCollection<Contact>(); 
    private ObservableCollection<Dog> _dogs = new ObservableCollection<Dog>(); 


    private List<IForeignKeyRelation> _relations = new List<IForeignKeyRelation>(); 

    public Context() 
    { 
     var observables = this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance) 
      .ToList(); 

     foreach(var observable in observables) 
     { 
      var notifyCollection = observable.GetValue(this) as INotifyCollectionChanged; 
      if (notifyCollection != null) 
      { 
       notifyCollection.CollectionChanged += CollectionChanged; 
       Type principalType = observable.FieldType.GetGenericArguments()[0]; 

       var relations = principalType.GetProperties(BindingFlags.Public | BindingFlags.Instance) 
        .ToList() 
        .Where(p => p.GetCustomAttribute(typeof(ForeignKeyAttribute)) as ForeignKeyAttribute != null) 
        .Select(p => new { PrincipalForeignKeyInfo = p, ForeignKey = p.GetCustomAttribute(typeof(ForeignKeyAttribute)) as ForeignKeyAttribute }) 
        .Where(p => principalType.GetProperty(p.ForeignKey.Name) != null) 
        .Select(p => { 
         var principalForeignKeyInfo = p.PrincipalForeignKeyInfo; 
         var principalRelationInfo = principalType.GetProperty(p.ForeignKey.Name); 
         var dependantType = principalRelationInfo.PropertyType; 
         var dependantKeyProperties = dependantType.GetProperties(BindingFlags.Public | BindingFlags.Instance) 
          .ToList() 
          .Where(dp => dp.GetCustomAttribute(typeof(KeyAttribute)) as KeyAttribute != null) 
          .ToList(); 
         var dependantKeyInfo = dependantKeyProperties.FirstOrDefault(); 

         var isValid = (dependantKeyInfo != null) 
          // Don't allow INT to GUID comparisons 
          // Keys need to be of same type; 
          && (principalForeignKeyInfo.PropertyType == dependantKeyInfo.PropertyType); 


         return new { 
          IsValid = isValid, 
          PrincipalRelationInfo = principalRelationInfo, 
          DependantType = dependantType, 
          PrincipalCollection = observable.GetValue(this), 
          PrincipalForeignKeyInfo = principalForeignKeyInfo, 
          DependantKeyInfo = dependantKeyInfo        
         }; 
        }) 
        .Where(r => r.IsValid) 
        .Select(r => 
        {   
         var relationType = typeof(ForeignKeyRelation<,>).MakeGenericType(principalType, r.DependantType); 
         var relation = Activator.CreateInstance(relationType) as IForeignKeyRelation; 
         relation.GetType().GetProperty("PrincipalCollection").SetValue(relation, observable.GetValue(this)); 
         relation.DependantKeyInfo = r.DependantKeyInfo; 
         relation.PrincipalForeignKeyInfo = r.PrincipalForeignKeyInfo; 
         relation.PrincipalRelationInfo = r.PrincipalRelationInfo; 

         return relation; 
        }) 
        .ToList(); 

       _relations.AddRange(relations); 

      } 
     } 
    } 

    // Makes storing Generic types easy when the 
    // Generic type doesn't exist; 
    private interface IForeignKeyRelation 
    { 
     PropertyInfo PrincipalForeignKeyInfo { get; set; } 
     PropertyInfo PrincipalRelationInfo { get; set; } 
     PropertyInfo DependantKeyInfo { get; set; } 
     void Add<T>(T value); 
    } 

    // Class to hold reflected values 
    // Reflection 
    private class ForeignKeyRelation<P,D> : IForeignKeyRelation 
    { 
     // Staff.ContactId 
     public PropertyInfo PrincipalForeignKeyInfo { get; set; } 
     public Collection<P> PrincipalCollection { get; set; } 
     // Staff.Contact 
     public PropertyInfo PrincipalRelationInfo { get; set; } 
     // Contact.Id 
     public PropertyInfo DependantKeyInfo { get; set; } 

     public void Add<T>(T value) 
     { 
      if (value.GetType() == typeof(D)) 
      { 
       var dependantKey = DependantKeyInfo.GetValue(value); 

       var principals = PrincipalCollection.Where(p => this.PrincipalForeignKeyInfo.GetValue(p).Equals(dependantKey)) 
        .ToList(); 

       foreach(var principal in principals) 
       { 
        PrincipalRelationInfo.SetValue(principal, value); 
       } 
      } 
     } 
    } 

    private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args) 
    { 
     switch (args.Action) 
     { 
      case NotifyCollectionChangedAction.Add: 
       foreach(var relation in this._relations) 
       { 
        foreach(var item in args.NewItems) 
        { 
         relation.Add(item); 
        } 
       } 
       break; 
      case NotifyCollectionChangedAction.Move: 
       break; 
      case NotifyCollectionChangedAction.Remove: 
       break; 
      case NotifyCollectionChangedAction.Replace: 
       break; 
      case NotifyCollectionChangedAction.Reset: 
       break; 
      default: 
       throw new NotImplementedException(args.Action.ToString()); 
     } 
    } 

    public IList<Staff> Staffs 
    { 
     get 
     { 
      return _staffs; 
     } 
    } 
    public IList<Contact> Contacts 
    { 
     get 
     { 
      return _contacts; 
     } 
    } 
    public IList<Dog> Dogs 
    { 
     get 
     { 
      return _dogs; 
     } 
    } 
} 

简单的例子程序:

public static void Main() 
{ 
    var context = new Context(); 
    var staff = new Staff() { Id = Guid.NewGuid() }; 

    var contact = new Contact(); 
    contact.Id = Guid.NewGuid(); 
    contact.Name = "Hello DotFiddle!"; 

    staff.ContactId = contact.Id; 

    context.Staffs.Add(staff); 

    Console.WriteLine("staff contact is null: " + (staff.Contact == null).ToString()); 

    context.Contacts.Add(contact); 

    Console.WriteLine("staff contact is null: " + (staff.Contact == null).ToString()); 

    Console.WriteLine("Staff.Contact.Name: "+ staff.Contact.Name); 
} 

结果:

人员合作ntact是零:真

工作人员联系为空:假

Staff.Contact.Name:您好DotFiddle!

This Entire Example on DotNetFiddle.net

+0

确实将它们联系起来 - 我的问题是我有很多复杂的联系,我宁愿将这些数据存储在模型本身中,并让控制器根据它自动链接所有数据。这对我来说似乎更加正确,因为它阻止控制器包含model * data * - 而只是控制模型。它还使得查看单个模型如何链接到另一个模型更容易。 – Octopoid 2015-02-06 16:55:55

+0

这正在成为一个宗教问题。有些人更喜欢MVC中的控制器(逻辑)写出所有没有逻辑的模型,并根据需要更多[DTO](http://en.wikipedia.org/wiki/Data_transfer_object)。其他人则认为模型是完全封装的。对我而言,这取决于谁消费对象。我倾向于始终使用DTO并将它们传递给DataLayer以创建域对象。 (这并不能真正帮助回答问题)。 – 2015-02-06 16:59:00

+0

很高兴知道 - 我并不完全熟悉MVC针对这些事情的最佳实践,而且看起来我可能已经选择了一两个“更适合辩论”的“正确”。我会更新这个问题来反映。 – Octopoid 2015-02-06 17:01:31

1

我不知道你要什么了,但其中的一个完成它:底部的两个是“前途未卜”,你可以通过所有的if语句看到。简单地说,编译器无法确定它们能够工作,因为您可以轻松地传递一个不好的propertyExpression。

class Program 
{ 
    static void Main(string[] args) 
    { 
     var guids = Enumerable.Range(0, 10).Select(i => Guid.NewGuid()).ToList(); 
     var m2s = guids.Select(g => new Model2 { ID = g }).ToList(); 
     var model1 = new Model1 { Model2ID = m2s[4].ID }; 
     model1.LinkTo(m2s, (m1, m2) => m1.Model2 = m2, (m1, m2) => m2.ID == m1.Model2ID); 

     var model1a = new Model1 { Model2ID = m2s[4].ID }; 
     model1a.LinkTo(m2s, m1 => m1.Model2, m1 => m1.Model2ID, m2 => m2.ID); 

     var model1b = new Model1 { Model2ID = m2s[4].ID }; 
     model1b.LinkTo(m2s, m1 => m1.Model2, (m1, m2) => m1.Model2ID == m2.ID); 
    } 
} 

public static class ObjectExtensions 
{ 
    public static void LinkTo<T, U>(this T m1, IEnumerable<U> m2s, Action<T, U> action, Func<T, U, bool> filter) 
    { 
     if (m2s.Any(m2 => filter(m1, m2))) 
     { 
      var x = m2s.First(m2 => filter(m1, m2)); 
      action(m1, x); 
     } 
    } 

    public static void LinkTo<T, U>(this T m1, IEnumerable<U> m2s, Expression<Func<T, U>> propertyExpression, Func<T, U, bool> filter) 
    { 
     var results = m2s.Where(m2 => filter(m1, m2)); 

     if (!results.Any()) 
      return; 

     var x = results.FirstOrDefault(); 

     if (x != null) 
     { 
      var me = (propertyExpression.Body as MemberExpression); 
      if (me != null) 
      { 
       var pi = me.Member as PropertyInfo; 
       if (pi != null) 
       { 
        var setter = pi.GetSetMethod(); 
        if (setter != null) 
        { 
         setter.Invoke(m1, new object[] { x }); 
        } 
       } 
      } 
     } 
    } 

    public static void LinkTo<T, U, Key>(this T m1, IEnumerable<U> m2s, Expression<Func<T, U>> propertyExpression, Func<T, Key> tKey, Func<U, Key> uKey) 
    { 
     var results = Enumerable.Repeat(m1, 1) 
      .Join(m2s, tKey, uKey, (t, u) => u); 

     if(!results.Any()) 
      return; 

     var x = results 
      .FirstOrDefault(); 

     if (x != null) 
     { 
      var me = (propertyExpression.Body as MemberExpression); 
      if (me != null) 
      { 
       var pi = me.Member as PropertyInfo; 
       if (pi != null) 
       { 
        var setter = pi.GetSetMethod(); 
        if (setter != null) 
        { 
         setter.Invoke(m1, new object[] { x }); 
        } 
       } 
      } 
     } 
    } 
} 
0

您应该阅读关于扩展方法。他们被称为“从”对象。 例如,您可以编写如下的通用扩展方法

class Program 
{ 
    static void Main(string[] args) 
    { 
     var listOfModel2 = new Model1(); 

     //You can call it from "object" 
     listOfModel2.MyExtensionMethod(); 

     //Or directly as it is declared 
     ObjectExtensions.MyExtensionMethod(listOfModel2); 
    } 
} 

public static class ObjectExtensions 
{ 
    public static void MyExtensionMethod<T>(this T t) 
    { 
     //Do somthing 
    } 
}