简体   繁体   中英

Bind DataGrid in WPF to child navigation property (EF) with 1:1 using MVVM

Bind DataGrid in WPF to child navigation property (EF) with 1:1 using MVVM

All,

Using .Net 4 and EF 4.4 (DB first)

I have a ViewModel that contains a reference to an EntityCollection "Entity1".

This Entity has a 1:1 relationship with Entity2.

My problem is that, while I can bind to other Entities that have a 1:many or many:many relationship, I seem to have trouble binding to Entity1.Entity2. My guess is this is because Entity2 is not a collection, and so WPF isn't quite sure what to do with this.

So, outside of adding an ObservableCollection to my Entity1 class (which will only contain 1 instance of Entity2, being a 1:1 relationship) and binding to this, I was wondering if there was perhaps a better way to accomplish my goal of having Entity1.Entity2 display in a DataGrid (without redesigning my DB or creating an unnecessary collection that will ultimately only contain 1 object).

I've already tried putting Entity1.Entity2 inside of a CollectionViewSource, but that doesn't seem to help any.

Thanks.

Why are you using a datagrid for a single entity? I definitely would not stick an ObservableCollection property in your model to cover this.

If you are wanting to show all entity2s for all entity1s. You can bind the ItemsSource of your datagrid to a collection of entity1 and drill in to the properties of entity2.

Another option is to build a custom form for presenting the entity2 data.

Attempt 1

You can definitely drill into the properties in Entity2 from Entity1. Try this:

\n
  1. Populate an ObservableCollection with the instances of Entity1 that you want to use.
  2. Set the ItemsSource on your DataGrid to that ObservableCollection
  3. Set AutoGenerateColumns to false on your DataGrid
  4. Add the columns you desire to the datagrid setting the binding path to be Entity2.PropertyName
  5. You may have to temporarily create an instance of Entity2 for each Entity1 for this to work.

Attempt 2 Entity Framework will allow for a single entity to be constructed from two tables. You may want to consider that implementation. The big question there is "do I really care if I have worthless records in Table2 if Table2 only extends Table1?"

Attempt 3 Instead of working with Entity1 and Entity2 directly, I think you should introduce a new model into your domain. Having a class that would wrap the properties of Entity1 and Entity2 is probably going to be the best choice. Then, when you are ready to update the database, you can determine if you have an instance of Entity2.

Following the WPF Application Framework, you might have something like this:

//In your application layer

public class RecordsViewModel
{
    public ObservableCollection<Record> Records {get;set;}
}


//In your domain layer

public class Record
{
    //Properties from Entity1

    //Properties from Entity2
}

//In your Data Access Layer

public class Repository
{
    public IEnumerable<Record> GetRecords()
    {
        return db.Entity1.Include("Entity2")
                .Select(e => 
                    new Record() 
                        { 
                            //object initialization
                        });
    }

    public void Update(IEnumerable<Record> records)
    {
        var recordIds = records.Select(r => r.Id);

        var entities = db.Entity1.Include("Entity2").Where(e => recordIds.Contains(e.Id));

        foreach(var record in records)
        {
            var entity = entities.SingleOrDefault(e => e.Id == record.Id);

            if (entity == null)
            {
                entity = new Entity1();
                db.Entity1.Add(entity);
            }

            //update properties on Entity1

            //check if Entity2 should exist
            //If so, create/update entity2
            //If not, decide if you should delete entity2 or simply set Entity1.Entity2 to null
        }
    }
}

ATTEMPT 1: In T4 Template that creates Entity classes, change NavigationProperty to:

public string NavigationProperty(NavigationProperty navigationProperty)
{
    var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());

    return string.Format(
        CultureInfo.InvariantCulture,
        "{0}\n\n    {1}  {2}  {3}\n    {{\n      {4}get\n        {{\n           return _{3}; \n        }}\n        {5} set\n        {{\n          _{3}=value; OnSet{3}();\n        }}\n      }}\n\n    {6}",
        string.Format(CultureInfo.InvariantCulture, "{0} _{1};",_typeMapper.GetTypeName(navigationProperty.TypeUsage), _code.Escape(navigationProperty)),
        AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)),
        navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
        _code.Escape(navigationProperty),
        _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
        _code.SpaceAfter(Accessibility.ForSetter(navigationProperty)),
        string.Format(CultureInfo.InvariantCulture, "partial void OnSet{0}();", _code.Escape(navigationProperty)));
}

Then add in partial Entity1 Class:

Partial Class Entity1:EntityBase
{
    public SpecificObservableCollection<Entity2> Entity2_Observable
    {
        get;
        set;
    }

    partial void OnSetEntity2()
    {
        Misc_Observable.Add(Entity2);
    }
    public class SpecificObservableCollection<T> : ObservableCollection<T>
    {
        public Action<T> SetValue { get; set; }
        protected override void InsertItem(int index, T item)
        {
            if (item != null)
            {
                base.InsertItem(index, item);
            }
        }
        protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            base.OnCollectionChanged(e);
            if (this.Count>0)
                SetValue(this[0]);
        }
    }

    protected override void DoStuffOnAdd()
    {
            Entity2_Observable = new SpecificObservableCollection<Entity2>();
            Entity2_Observable.SetValue = a => _Entity2 = a;
    }
}

Then in EntityBase:

public abstract class EntityBase
{
    EntityBase()
    {
        DoStuffOnAdd();
    }
    protected virtual void DoStuffOnAdd() { }
}

For IValueConverter (in order to avoid adding too many records in a 1:1 relationship)

public class CanAddValueConverter : IValueConverter
{
   private Type _T;
   private DataGrid _dg;

   public void SetValues(DataGrid dg, Type T)
   {
       _T = T;
       _dg = dg;
   }

   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
   {
       System.Collections.IEnumerable dgIS = value as System.Collections.IEnumerable;
       if (_dg != null && dgIS == _dg.ItemsSource)
       {
           if (_dg.Items.Count > 0)
               return _dg.Items.Count <= System.Convert.ToInt32(parameter) && _dg.Items[_dg.Items.Count - 1].GetType() != _T;
           else
               return true;
       }
       else
           return false;
   }
}

then in CodeBehind, assign DataGrid to IValueConverter as well as corresponding Entity type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM