I'm having trouble taking a subject of
public Subject<IEnumerable<Person>> PersonDataSubject;
And converting it to:
public Subject<IEnumerable<BornInYear>> BornInYearSubject;
... using some linq aggregation.
The example below puts it in more context, and where I'm struggling is working out how to get an IEnumerable into the BornInYearSubject from a subscription to the PersonDataSubject.
Whatever I try I end up with an IObservable<BornInYear>, not an IObservable<IEnumerable<BornInYear>>.
The goal is for clients of the class to be able to subscribe to both subjects and get an IEnumerable of the respective type on each 'next' notification.
public class ReactiveTest
{
public class Person
{
public string name;
public DateTime dob;
};
public class BornInYear
{
public int Year;
public int Count;
}
public Subject<IEnumerable<Person>> PersonDataSubject = new Subject<IEnumerable<Person>>();
public Subject<IEnumerable<BornInYear>> BornInYearSubject= new Subject<IEnumerable<BornInYear>>();
public void LoadData()
{
// Go to hypotheritical web service and get batch of people.
IEnumerable<Person> people = WebService.Fetch();
// Notify subscribers we have a fresh batch of data.
PersonDataSubject.OnNext(people);
}
public ReactiveTest()
{
// Hookup BornInYearSubject to listen to PersonDataSubject and publish the summarised data.
PersonDataSubject.Subscribe(pd => pd.GroupBy(p => p.dob.Year)
.Select(ps => new BornInYear { Year = ps.Key, Count = ps.Count()})
.AsParallel()
);
// How do I get the results of this out and published onto BornInYearSubject?
}
}
Now I know I could achieve this using Task.Factory.StartNew(...)... as my subscribe OnNext for the PersonDataSubject but I believe it must be possible staying more Reactive?
How about:
PersonDataSubject
.GroupBy(x => x.Dob.Year)
.Select(x => x.Aggregate(new List<BornInYear>(), (acc, x) => { acc.Add(new BornInYear { Year = ps.Key }); return acc; }))
Ok this works. Thanks for the ideas guys - the answer seems strikingly obvious in hindsight!
using System;
using System.Collections.Generic;
using System.Linq;
namespace TestReactive
{
public class ReactiveTest
{
public class Person
{
public string name;
public DateTime dob;
};
public class BornInYear
{
public int Year;
public int Count;
}
public Subject<IEnumerable<Person>> PersonDataSubject = new Subject<IEnumerable<Person>>();
public Subject<IEnumerable<BornInYear>> BornInYearSubject = new Subject<IEnumerable<BornInYear>>();
public void LoadData()
{
IEnumerable<Person> people = new List<Person> {
new Person() {name = "Bill", dob = DateTime.Now.AddYears(-10)},
new Person() {name = "Pete", dob = DateTime.Now.AddYears(-5)},
new Person() {name = "Judy", dob = DateTime.Now.AddYears(-1)},
new Person() {name = "Mike", dob = DateTime.Now.AddYears(-5)},
new Person() {name = "Jake", dob = DateTime.Now.AddYears(-5)},
new Person() {name = "Fred", dob = DateTime.Now.AddYears(-13)},
};
// Notify subscribers we have a fresh batch of data.
PersonDataSubject.OnNext(people);
}
public ReactiveTest()
{
var subj = PersonDataSubject.Select(pds => pds.GroupBy(pd => pd.dob.Year)
.Select(p => new BornInYear {
Year = p.Key, Count = p.Count()
}).AsParallel());
subj.Subscribe(BornInYearSubject);
BornInYearSubject.Subscribe( x=> Console.WriteLine("{0}", x.Count()));
LoadData();
}
}
class Program
{
static void Main(string[] args)
{
ReactiveTest rt = new ReactiveTest();
}
}
}
Related
I am building a simple DateTime calculator using a ReactiveUI ReactiveObject viewmodel.
It has a calculate command that calculates a value and correctly updates the result backed by an ObservableAsPropertyHelper<DateTime?> field.
I also have a ReactiveUICommand that is supposed to reset all of the UI values.
The problem that I am running into is how to reset the readonly "Result" value.
public class VM : ReactiveObject
{
private readonly ObservableAsPropertyHelper<DateTime?> _result;
[Reactive]
public DateTime Start { get; set; } = DateTime.Now;
[Reactive]
public int Days { get; set; } = 1;
public DateTime? Result => _result.Value;
public ReactiveCommand<Unit, DateTime?> CalculateCommand { get; }
public ReactiveCommand<Unit, Unit> ResetCommand { get; }
public VM()
{
CalculateCommand = ReactiveCommand
.CreateFromTask<Unit, DateTime?>((_, cancellationToken) => CalculateAsync(cancellationToken));
_result = CalculateCommand.ToProperty(this, nameof(Result));
// everything above this works.
ResetCommand = ReactiveCommand.Create(() =>
{
Start = DateTime.Now;
Days = 1;
// how do I reset "_result" or "Result" back to null???
});
}
private Task<DateTime?> CalculateAsync(CancellationToken _)
{
// to be replaced by an API call later.
return Task.FromResult<DateTime?>(Start.AddDays(Days));
}
}
How do I reset a value backed by an instance of ObservableAsPropertyHelper?
Figured this out myself:
namespace WpfApp4;
using System.Reactive.Linq;
using System.Threading.Tasks;
using ReactiveUI;
using System;
using System.Reactive;
public class MainViewModel : ReactiveObject
{
public ReactiveCommand<Unit, DateTime?> StartCommand { get; }
public ReactiveCommand<Unit, DateTime?> ResetCommand { get; }
private DateTime _start = DateTime.Now;
private int _processingDays = 1;
private readonly ObservableAsPropertyHelper<DateTime?> _result;
public DateTime? Result => _result.Value;
public DateTime Start
{
get => _start;
set => this.RaiseAndSetIfChanged(ref _start, value);
}
public int ProcessingDays
{
get => _processingDays;
set => this.RaiseAndSetIfChanged(ref _processingDays, value);
}
public MainViewModel()
{
StartCommand = ReactiveCommand.CreateFromTask<DateTime?>(() => Task.FromResult<DateTime?>(Start.AddDays(ProcessingDays)));
ResetCommand = ReactiveCommand.Create<DateTime?>(execute: () => null);
_ = ResetCommand.Subscribe(onNext: (_) =>
{
Start = DateTime.Now;
ProcessingDays = 1;
});
_result = StartCommand.Merge(ResetCommand).ToProperty(this, nameof(Result));
}
}
Still working out concepts in EF6 (learning) This is not production code; it's only for learning...
I'm experimenting with migrating to EF6 from direct SQL ODBC calls and trying to duplicate features of methods I have currently in my code.
Consider this code:
public class user
{
[Key]
public int pkID;
public string ForeignID;
public string UserName;
public virtual List<userData> UserDatas { get; set; }
}
public class userData
{
[Key]
public int pkID;
public int fkUserID;
public string ColumnName;
public string ColumnValue;
public bool IsOverrode;
public string OverrideValue;
}
class Program
{
static TestDataEntities db = new TestDataEntities();
static void Main(string[] args)
{
var record = db.Users.Select(x => new { x.UserName, x.pkID }).FirstOrDefault();
Console.WriteLine(record);
var record2 = db.Users.Include(x => x.UserDatas).FirstOrDefault();
record2.UserName = "JohnTestNew";
db.SaveChanges();
var ud = record2.UserDatas.Where(x => x.ColumnName.ToUpper().Contains("FIRSTNAME")).FirstOrDefault();
foreach (var item in record2.UserDatas)
Console.WriteLine(item.ColumnName);
Console.ReadLine();
}
static userData TestReturn()
{
var record2 = db.Users.Include(x => x.UserDatas).FirstOrDefault();
var ud = record2.UserDatas.Where(x => x.ColumnName.ToUpper().Contains("FIRSTNAME")).FirstOrDefault();
return (userData)ud;
}
I'm getting a compile error on the last line:
return (userData)ud;
'Cannot convert type 'TestLinq.UserData' to 'TestLinq.userData'
I kind of understand what is going on, but I thought I could cast the class on the return. The UserData is contained in the returned query, the userData is the raw class of what UserData is.
Is there a way to return this object using the base class?
We are porting our existing MVC6 EF6 application to Core.
Is there a simple method in EF Core to update a many-to-many relationship?
My old code from EF6 where we clear the list and overwrite it with the new data no longer works.
var model = await _db.Products.FindAsync(vm.Product.ProductId);
model.Colors.Clear();
model.Colors = _db.Colors.Where(x =>
vm.ColorsSelected.Contains(x.ColorId)).ToList();
This will work for you.
Make a class to have the relationship in:
public class ColorProduct
{
public int ProductId { get; set; }
public int ColorId { get; set; }
public Color Color { get; set; }
public Product Product { get; set; }
}
Add a ColorProduct collection to your Product and Color classes:
public ICollection<ColorProduct> ColorProducts { get; set; }
Then use this extension I made to remove the unselected and add the newly selected to the list:
public static void TryUpdateManyToMany<T, TKey>(this DbContext db, IEnumerable<T> currentItems, IEnumerable<T> newItems, Func<T, TKey> getKey) where T : class
{
db.Set<T>().RemoveRange(currentItems.Except(newItems, getKey));
db.Set<T>().AddRange(newItems.Except(currentItems, getKey));
}
public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKeyFunc)
{
return items
.GroupJoin(other, getKeyFunc, getKeyFunc, (item, tempItems) => new { item, tempItems })
.SelectMany(t => t.tempItems.DefaultIfEmpty(), (t, temp) => new { t, temp })
.Where(t => ReferenceEquals(null, t.temp) || t.temp.Equals(default(T)))
.Select(t => t.t.item);
}
Using it looks like this:
var model = _db.Products
.Include(x => x.ColorProducts)
.FirstOrDefault(x => x.ProductId == vm.Product.ProductId);
_db.TryUpdateManyToMany(model.ColorProducts, vm.ColorsSelected
.Select(x => new ColorProduct
{
ColorId = x,
ProductId = vm.Product.ProductId
}), x => x.ColorId);
In order to avoid the LINQ hell in the above answer, the templated "Except" method can be rewritten as such:
public static IEnumerable<TEntity> LeftComplementRight<TEntity, TKey>(
this IEnumerable<TEntity> left,
IEnumerable<TEntity> right,
Func<TEntity, TKey> keyRetrievalFunction)
{
var leftSet = left.ToList();
var rightSet = right.ToList();
var leftSetKeys = leftSet.Select(keyRetrievalFunction);
var rightSetKeys = rightSet.Select(keyRetrievalFunction);
var deltaKeys = leftSetKeys.Except(rightSetKeys);
var leftComplementRightSet = leftSet.Where(i => deltaKeys.Contains(keyRetrievalFunction.Invoke(i)));
return leftComplementRightSet;
}
Furthermore the UpdateManyToMany method can be updated to include entities that have been modified as such:
public void UpdateManyToMany<TDependentEntity, TKey>(
IEnumerable<TDependentEntity> dbEntries,
IEnumerable<TDependentEntity> updatedEntries,
Func<TDependentEntity, TKey> keyRetrievalFunction)
where TDependentEntity : class
{
var oldItems = dbEntries.ToList();
var newItems = updatedEntries.ToList();
var toBeRemoved = oldItems.LeftComplementRight(newItems, keyRetrievalFunction);
var toBeAdded = newItems.LeftComplementRight(oldItems, keyRetrievalFunction);
var toBeUpdated = oldItems.Intersect(newItems, keyRetrievalFunction);
this.Context.Set<TDependentEntity>().RemoveRange(toBeRemoved);
this.Context.Set<TDependentEntity>().AddRange(toBeAdded);
foreach (var entity in toBeUpdated)
{
var changed = newItems.Single(i => keyRetrievalFunction.Invoke(i).Equals(keyRetrievalFunction.Invoke(entity)));
this.Context.Entry(entity).CurrentValues.SetValues(changed);
}
}
which uses another custom templated "Intersect" function to find the intersection of the two sets:
public static IEnumerable<TEntity> Intersect<TEntity, TKey>(
this IEnumerable<TEntity> left,
IEnumerable<TEntity> right,
Func<TEntity, TKey> keyRetrievalFunction)
{
var leftSet = left.ToList();
var rightSet = right.ToList();
var leftSetKeys = leftSet.Select(keyRetrievalFunction);
var rightSetKeys = rightSet.Select(keyRetrievalFunction);
var intersectKeys = leftSetKeys.Intersect(rightSetKeys);
var intersectionEntities = leftSet.Where(i => intersectKeys.Contains(keyRetrievalFunction.Invoke(i)));
return intersectionEntities;
}
In our app we use ReactiveUI to follow the MVVM pattern. In one view we want to show a UITableView. Data are usually passed to a UITableView.Source but with ReactiveUI we use ReactiveTableViewSource<TSource>.
What I don't understand is how I can bind my data that I got in my view model in a ReactiveList to the ReactiveTableViewSource.
What we now have is that we create a table inside a UIView like this:
Table = new UITableView(UIScreen.MainScreen.Bounds);
Table.Source = new TableSource(Table, MyReactiveList, (NSString) CellIdentifier, _cellHeight, cell => Debug.WriteLine(cell));
Btw: What is the cell action used for?
Furthermore we have a Table source class that looks like this:
internal sealed class TableSource : ReactiveTableViewSource<MyListObject>
{
public TableSource(UITableView tableView, IReactiveNotifyCollectionChanged<MyListObject> collection, NSString cellKey, float sizeHint, Action<UITableViewCell> initializeCellAction = null) : base(tableView, collection, cellKey, sizeHint, initializeCellAction)
In my view model I have a service that is updating my ReactiveList. It looks like this:
public sealed class MyService : ReactiveObject, IMyService
{
public IReactiveList<MyListObject> MyReactiveList { get; }
public async Task UpdateMyReactiveListAsync() {//...}
Where do I bind the table source to the ReactiveList? Where do I subscribe for events? Is there any documentation or example code I might have missed?
Working with ReactiveTableViewSource is quite easy:
Just connect the List with your UITableView
var tableView = new UITableView ();
// Bind the List agains the table view
// SampleObject is our model and SampleCell the cell
ViewModel.WhenAnyValue (vm => vm.Items).BindTo<SampleObject, SampleCell> (tableView, 46, cell => cell.Initialize());
Then create a custom cell where you bind the model data against the cell.
public class SampleCell : ReactiveTableViewCell, IViewFor<SampleObject>
{
public SampleCell () : base() { }
public SampleCell (IntPtr handle) : base(handle) { }
private SampleObject _viewModel;
public SampleObject ViewModel
{
get { return _viewModel; }
set { this.RaiseAndSetIfChanged (ref _viewModel, value); }
}
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = value as SampleObject; }
}
public void Initialize()
{
this.WhenAnyValue (v => v.ViewModel.Name).BindTo (
this,
v => v.TextLabel.Text);
}
}
A compelling example you can find here: https://github.com/reicheltp/ReactiveTableViewSource-Sample
Update 2016/03/09: Better do binding in a separated Initialize method to prevent multiple calls.
If you have more questions you can ask me on twitter: #reicheltp
May be you miss this https://github.com/reactiveui/ReactiveUI/blob/275eca3dc2e5fc93bddb137e60be32885f788688/docs/basics/rx-cocoa-delegates.md
To subscribe the event you can assign a UITableViewDelegateRx
var tvd = new UITableViewDelegateRx ();
Table.Delegate = tvd;
tvd.RowSelectedObs.Subscribe (c => { });
Just to make a small update using ReactiveUI version 9.16.6:
Example that lists some cities with their according postal codes.
=> My ViewController inherites from ReactiveViewController
=> My CityViewModel inherites from ReactiveObject
=> My CityItemViewModel inherites from ReactiveObject
=> My CityTableViewCell inherites from ReactiveTableViewCell
public class CityViewModel : ReactiveObject
{
private ICityService CityService { get; }
private SourceCache<CityItemViewModel, int> _citiesCache;
private IObservable<IChangeSet<CityItemViewModel, int>> _citiesOperations => _citiesCache.Connect();
public readonly ReadOnlyObservableCollection<CityItemViewModel> Cities;
public CityViewModel(ICityService cityService)
{
CityService = cityService;
}
#region CityViewModelCommand
public ReactiveCommand<CityItemViewModel, Unit> CityClickCommand { get; }
#endregion
private async Task<IEnumerable<CityItemViewModel>> SearchCitiesAsync(string searchText, CancellationToken token)
{
IEnumerable<CityItemViewModel> items;
if (string.IsNullOrEmpty(searchText))
items = await CityService.ToListWithCountryCodeAsync(cancellationToken: token);
else
items = await CityService.ToListBySearchWithCountryCodeAsync(searchText, cancellationToken: token);
_citiesCache.Edit(innerList =>
{
innerList.Clear();
innerList.AddOrUpdate(items);
});
return items;
}
}
public class CityItemViewModel : ReactiveObject
{
public int Id { get; set; }
public string Name { get; set; }
public string PostalCode { get; set; }
public override string ToString()
=> $"[CityItemViewModel: Id={Id}, Name={Name}, PostalCode={PostalCode}]";
public CityItemViewModel() : base()
{
}
}
In my ViewController's ViewDidLoad method:
this.WhenActivated((cleanup) =>
{
this.Bind(ViewModel,
x => x.SearchText,
x => x.searchBar.Text)
.DisposeWith(cleanup);
this.WhenAnyValue(v => v.ViewModel.Cities)
.BindTo<CityItemViewModel, CityTableViewCell>(tableView, CityTableViewCell.Key, 50, cell => cell.Initialize(),
source => source.ElementSelected.InvokeCommand(ViewModel.CityClickCommand))
.DisposeWith(cleanup);
});
In my Initialize method of my UITableViewCell:
public void Initialize()
{
this.WhenAnyValue(v => v.ViewModel.Name)
.Subscribe(name => cityLabel.Text = name);
this.WhenAnyValue(v => v.ViewModel.PostalCode)
.Subscribe(x => postalCodeLabel.Text = x);
}
Hope that can help someone ;)
I have a demo class "User" like the following:
public partial class User {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[StringLength(30)]
[Required]
public string LoginName { get; set; }
[StringLength(120)]
[Required]
[DataType(DataType.Password)]
public string Pwd { get; set; }
[StringLength(50)]
public string Phone { get; set; }
[StringLength(100)]
public string WebSite { get; set; }
...
...
}
As you can see, "LoginName" and "Pwd" are "Required".
Some time , I only want to update user's "WebSite" , So I do like this:
public void UpdateUser(User user , params string[] properties) {
this.rep.DB.Users.Attach(user);
this.rep.DB.Configuration.ValidateOnSaveEnabled = false;
var entry = this.rep.DB.Entry(user);
foreach(var prop in properties) {
var entProp = entry.Property(prop);
//var vas = entProp.GetValidationErrors();
entProp.IsModified = true;
}
this.rep.DB.SaveChanges();
this.rep.DB.Configuration.ValidateOnSaveEnabled = true;
}
Parameter "user" like this:
new User(){
ID = 1,
WebSite = "http://www.stackoverflow.com"
}
Notice , I don't specify "LoginName" and "Pwd"
This function can work fine , but I wouldn't set ValidateOnSaveEnabled to false.
Is there any way only validate "WebSite" when ValidateOnSaveEnabled is true?
Thanks.
As I know validation executed in SaveChanges always validates the whole entity. The trick to get selective validation for property is commented in your code but it is not part of the SaveChanges operation.
I get a solution.
First define PartialValidationManager:
public class PartialValidationManager {
private IDictionary<DbEntityEntry , string[]> dics = new Dictionary<DbEntityEntry , string[]>();
public void Register(DbEntityEntry entry , string[] properties) {
if(dics.ContainsKey(entry)) {
dics[entry] = properties;
} else {
dics.Add(entry , properties);
}
}
public void Remove(DbEntityEntry entry) {
dics.Remove(entry);
}
public bool IsResponsibleFor(DbEntityEntry entry) {
return dics.ContainsKey(entry);
}
public void ValidateEntity(DbEntityValidationResult result) {
var entry = result.Entry;
foreach(var prop in dics[entry]){
var errs = entry.Property(prop).GetValidationErrors();
foreach(var err in errs) {
result.ValidationErrors.Add(err);
}
}
}
}
2, Add this Manager to My DbContext:
public class XmjDB : DbContext {
public Lazy<PartialValidationManager> PartialValidation = new Lazy<PartialValidationManager>();
protected override System.Data.Entity.Validation.DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry , IDictionary<object , object> items) {
if(this.PartialValidation.Value.IsResponsibleFor(entityEntry)) {
var result = new DbEntityValidationResult(entityEntry , new List<DbValidationError>());
this.PartialValidation.Value.ValidateEntity(result);
return result;
} else
return base.ValidateEntity(entityEntry , items);
}
...
...
Update Method :
public void UpateSpecifyProperties(T t, params string[] properties) {
this.DB.Set<T>().Attach(t);
var entry = this.DB.Entry<T>(t);
this.DB.PartialValidation.Value.Register(entry , properties);
foreach(var prop in properties) {
entry.Property(prop).IsModified = true;
}
this.DB.SaveChanges();
this.DB.PartialValidation.Value.Remove(entry);
}
Ok, it work fine.