Mapping dependent observable to sub list - c#

Say I have the following two observable streams
IObservable<LibraryView> GetLibrariesSource();
IObservable<FolderView> GetLibraryFolderSource(LibraryView library);
so IObservable<FolderView> depends upon a LibraryView. Also these views are flat, i.e. they have no navigational properties.
How would I map these two streams to an IObservable<Library> stream, where Library has a list of Folders
public class Library
{
public List<Folder> Folders { get; set; }
}
Assuming that I can easily map from a LibraryView to a Library and from a FolderView to a Folder.
Also, My end goal is to simply get a List<Library> using e.g., ForEachAsync. So it may not be necessary to create an IObservable<Library>.

Assuming your problem space looks like this:
async Task Do()
{
var libraryMapper = new Func<LibraryView, Library>(lv => /*implement me*/ new Library());
var folderMapper = new Func<FolderView, Folder>(fv => /*implement me*/ new Folder());
var librariesSource = new Func<IObservable<LibraryView>>(() => /*implement me*/ Observable.Empty<LibraryView>());
var libraryFolderSource = new Func<LibraryView, IObservable<FolderView>>(lv => /*implement me*/ Observable.Empty<FolderView>());
}
public class Library
{
public List<Folder> Folders { get; set; }
}
public class Folder { }
public class LibraryView { }
public class FolderView { }
Then something like this will work inside Do():
var libraryList = await librariesSource()
.Select(lv => (LibraryView: lv, FolderViewObservable: libraryFolderSource(lv)))
.SelectMany(async t => (LibraryView: t.LibraryView, FolderViews: await t.Item2.ToList()))
.Select(t =>
{
var newLibrary = libraryMapper(t.LibraryView);
newLibrary.Folders = t.FolderViews.Select(fv => folderMapper(fv)).ToList();
return newLibrary;
})
.ToList();
Use Select to map, SelectMany to apply await calls, and await to get from IObservable<IList<T>> to IList<T>.

Related

Trying to deep clone the row values with in the entity except Id

I have project entity structure like this as below with project name and project number and along with some list objects as well as properties.
public class DesignProject
{
[Key, GraphQLNonNullType]
public string ProjectNumber { get; set; }
public string Name { get; set; }
[Column(TypeName = "jsonb")]
public ProjectSectionStatus SectionStatuses { get; set; } = new ProjectSectionStatus();
[ForeignKey("AshraeClimateZone"), GraphQLIgnore]
public Guid? AshraeClimateZoneId { get; set; }
public virtual AshraeClimateZone AshraeClimateZone { get; set; }
[Column(TypeName = "jsonb")]
public List<ProjectObject<classA>> classAList{ get; set; } = new List<ProjectObject<classA>>();
[Column(TypeName = "jsonb")]
public List<ProjectObject<classB>> classBList{ get; set; } = new List<ProjectObject<classB>>();
......
......
......
Some more json columns
}
and the project object class like this
public class ProjectObject<T>
{
public Guid? Id { get; set; }
public T OriginalObject { get; set; }
public T ModifiedObject { get; set; }
[GraphQLIgnore, JsonIgnore]
public T TargetObject
{
get
{
return ModifiedObject ?? OriginalObject;
}
}
}
and ClassA entity structure like as below
public class ClassA
{
public string Edition { get; set; }
public string City { get; set; }
}
and i have some similar children entities (ClassA) same like as above, I want to copy the contents and statuses from one project entity to other project entity.
I have project entity with ProjectNumber 1212 and have another project having ProjectNumber like 23323 so i would like copy entire project contents from 1212 to 23323. So is there any way we can achieve this with C# and i am using .Net Core with Entity framework core.
Here the source design project that i am going to copy have same structure with destination design project and i am fine with overriding the destination project values and i don't want to update the project number here.
Could any one please let me know the how can i achieve this copying? Thanks in advance!!
Please let me know if i need to add any details for this question
Update : Deep copy related code
public InsertResponse<string> CopyBookmarkproject(string SourceProjectNumber, string destinationProjectNumber)
{
var sourceDesignProject = this._dbContext.DesignProject.Where(a => a.ProjectNumber == SourceProjectNumber).SingleOrDefault();
var destinationProject = this._dbContext.DesignProject.Where(a => a.ProjectNumber == destinationProjectNumber).SingleOrDefault();
CopyProject(sourceDesignProject, destinationProject);
// Need to update the Db context at here after deep copy
}
private void CopyProject(DesignProject sourceDesignProject, DesignProject destinationProject)
{
destinationProject.classAList= sourceDesignProject.classAList; // Not sure whether this copy will works
destinationProject.AshraeClimateZone = sourceDesignProject.AshraeClimateZone; // not sure whether this copy will works also
}
Updated solution 2:
var sourceDesignProject = this._dbContext.DesignProjects.AsNoTracking()
.Where(a => a.ProjectNumber == sourceProjectNumber)
.Include(a => a.PrimaryBuildingType)
.Include(a => a.AshraeClimateZone).SingleOrDefault();
var targetDesignProject = this._dbContext.DesignProjects.Where(a => a.ProjectNumber == targetProjectNumber).SingleOrDefault();
sourceDesignProject.ProjectNumber = targetDesignProject.ProjectNumber;
sourceDesignProject.SectionStatuses.AirSystemsSectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
sourceDesignProject.SectionStatuses.CodesAndGuidelinesSectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
sourceDesignProject.SectionStatuses.ExecutiveSummarySectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
sourceDesignProject.SectionStatuses.ExhaustEquipmentSectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
sourceDesignProject.SectionStatuses.InfiltrationSectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
this._dbContext.Entry(sourceDesignProject).State = EntityState.Modified; // getting the below error at this line
this._dbContext.SaveChanges();
ok = true;
getting an error like as below
The instance of entity type 'DesignProject' cannot be tracked because another instance with the same key value for {'ProjectNumber'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
There's a simple and short solution which uses .AsNoTracking() for source entry, assigning destination's ProjectNumber to the source and then changing the EntityState as Modified for it. It will copy all the properties to the destination entry :
var sourceDesignProject = this._dbContext.DesignProject.AsNoTracking().Where(a => a.ProjectNumber == SourceProjectNumber).SingleOrDefault();
var destinationProject = this._dbContext.DesignProject.Where(a => a.ProjectNumber == destinationProjectNumber).SingleOrDefault();
sourceDesignProject.ProjectNumber = destinationProject.ProjectNumber;
this._dbContext.Entry(sourceDesignProject).State = System.Data.Entity.EntityState.Modified;
this._dbContext.SaveChanges();
Make sure your relational properties are loaded before.
You can make use of Reflection. As you have both, source and destination objects and are of the same type. You can just traverse through source object and fetch the properties and replace the values.
Something like :
if (inputObjectA != null && inputObjectB != null)
{
//create variables to store object values
object value1, value2;
PropertyInfo[] properties = inputObjectA.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
//get all public properties of the object using reflection
foreach (PropertyInfo propertyInfo in properties)
{
//get the property values of both the objects
value1 = propertyInfo.GetValue(inputObjectA, null);
value2 = propertyInfo.GetValue(inputObjectB, null);
}
Ignore the properties that are not needed to be copied. The method will have a signature like below :
CopyObjects(object inputObjectA, object inputObjectB, string[] ignorePropertiesList)
Two choices bring to mind for deep copy of objects:
1- Using BinaryFormatter (Needs Serializable attribute on your class, See original answer):
public static T DeepClone<T>(this T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T) formatter.Deserialize(ms);
}
}
2- Using JsonSerializers like NewtoneSoft:
public static T CloneJson<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
// initialize inner objects individually
// for example in default constructor some list property initialized with some values,
// but in 'source' these items are cleaned -
// without ObjectCreationHandling.Replace default constructor values will be added to result
var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
Update
Here's the code you can use with NewtonSoft Json library:
private void CopyProject(DesignProject sourceDesignProject, ref DesignProject destinationProject)
{
if (Object.ReferenceEquals(sourceDesignProject, null))
{
destinationProject = null;
return;
}
var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
var destProjNumber = destinationProject.ProjectNumber;
destinationProject = JsonConvert.DeserializeObject<DesignProject>
(JsonConvert.SerializeObject(sourceDesignProject), deserializeSettings);
destinationProject.ProjectNumber = destProjNumber;
}
I always prefer using AutoMapper for these type of work. It's optimized and lower risk of making mistakes. And better than serialize/deserialize method since that would cause performance issues.
In your constructor (or in startup.cs and passing via dependency injection)
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<DesignProject, DesignProject>().ForMember(x => x.Id, opt => opt.Ignore());
cfg.CreateMap<ProjectObject<ClassA>, ProjectObject<ClassA>>().ForMember(x => x.Id, opt => opt.Ignore()));
cfg.CreateMap<ProjectObject<ClassA>, ProjectObject<ClassB>>().ForMember(x => x.Id, opt => opt.Ignore()));
cfg.CreateMap<ProjectObject<ClassB>, ProjectObject<ClassB>>().ForMember(x => x.Id, opt => opt.Ignore()));
cfg.CreateMap<ProjectObject<ClassB>, ProjectObject<ClassA>>().ForMember(x => x.Id, opt => opt.Ignore()));
});
var mapper = config.CreateMapper();
In your method :
var newProject = mapper.Map(sourceProject, new DesignProject());
OR
mapper.Map(sourceProject, targetProject);
This shouldn't work. If I understand you want to copy all elements from list into new list, but to make new object. Than maybe you can use extension method to clone complete list as described here link. For updating navigation properties check this post link.

AutoMapper 5.1 - Map Model and extended Model

I'm new with AutoMapper and i'm using 5.1.1 version.
I tried to map a list of model with a list of extended model. I get empty object in return.
My source model
public class spt_detail
{
public string Name { get; set; }
public string Age { get; set; }
}
My destination model
public class spt_detail_extended : spt_detail
{
public string Mm1 { get; set; }
public string Mm2 { get; set; }
}
AutoMapper code
spt_detail details = db.spt_detail.ToList();
Mapper.Initialize(n => n.CreateMap<List<spt_detail>, List<spt_detail_extended>>());
List<spt_creance_detail_enrichi> cenr = AutoMapper.Mapper.Map<List<spt_detail>, List<spt_detail_enrichi>>(details);
Issue : details contains 8 rows but cenr 0.
Someone can helps me ?
Mapping for spt_detail to spt_detail_extended.
You should create map rules only for models, like that:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<spt_detail, spt_detail_extended>();
});
And after that, you hould use the construction:
List<spt_detail_extended> extendeds = Mapper.Map<List<spt_detail_extended>>(details);
If you want to map other models, just add or edit your configuration.
Don't map the list, instead map like so:
Mapper.Initialize(n => n.CreateMap<spt_detail, spt_detail_extended>());
You call to do the map would stay the same:
List<spt_detail_extended> cenr = AutoMapper.Mapper.Map<List<spt_detail>, List<spt_detail_extended>>(details);
You have missed two steps in here.
The first one is that you need to initialize a list of your business object instead of initializing only a single one.
You retrieve a list from your database (you use .Tolist())
here is a sample on how i initialized an object:
List <SptDetail> details = new List <SptDetail> {
new SptDetail {
Age = "10",
Name = "Marion"
},
new SptDetail {
Age = "11",
Name = "Elisabeth"
}
};
The second misstep is that you were mapping lits, i suggest work with your single business class objects instead like following:
Mapper.Initialize(n => n.CreateMap<SptDetail, SptDetailExtended>()
.ForMember(obj => obj.ExProp1, obj => obj.MapFrom(src => src.Name))
.ForMember(obj => obj.ExProp2, obj => obj.MapFrom(src => src.Age)));
And the key in the hole story is to use .ForMember to specify wich member goes where because the properties does not have same name.
here is a code sample that runs like a charm with:
internal class Program
{
public static List<SptDetailExtended> InitializeExtendedObjects()
{
var details = new List<SptDetail>
{
new SptDetail
{
Age = "10",
Name = "Marion"
},
new SptDetail
{
Age = "11",
Name = "Elisabeth"
}
};
//this is wrong db.spt_detail.ToList();
Mapper.Initialize(n => n.CreateMap<SptDetail, SptDetailExtended>()
/*you need to use ForMember*/ .ForMember(obj => obj.ExProp1, obj => obj.MapFrom(src => src.Name))
.ForMember(obj => obj.ExProp2, obj => obj.MapFrom(src => src.Age)));
//instead of this Mapper.Initialize(n => n.CreateMap<List<spt_detail>, List<spt_detail_extended>>());
//change your mapping like following too
var cenr = Mapper.Map<List<SptDetailExtended>>(details);
return cenr;
}
private static void Main(string[] args)
{
var result = InitializeExtendedObjects();
foreach (var sptDetailExtended in result)
{
Console.WriteLine(sptDetailExtended.ExProp1);
Console.WriteLine(sptDetailExtended.ExProp2);
}
Console.ReadLine();
}
}
Hope this helps!

Separate class for the same purpose

I have this class
public class BlessingDTO
{
public List<string> BlessingCategoryName;
public List<string> Blessings;
}
I am Getting the response of the two lists this way:
public async Task<List<BlessingDTO>> GetBlessing(string UserType)
{
string blessing = "Blessing_" + UserType;
List<BlessingDTO> results = new List<BlessingDTO>();
using (DTS_OnlineContext context = new DTS_OnlineContext())
{
var items = await context.Messages.AsNoTracking().Where(x => x.MessageContext == blessing).GroupBy(x=>x.GroupKey).Select(b=>b.OrderBy(x=>x.Sort)).ToListAsync();
if (items.Count() > 0)
{//Notes.Select(x => x.Author).Distinct();
results = items.ToList().ConvertAll(x => new BlessingDTO()
{ BlessingCategoryName = x.ToList().Select(y => y.MessageName).Distinct().ToList(),
Blessings = x.ToList().Select(y => y.MessageText).ToList()
});
}
}
return results;
}
if I am changing the class, for my porpuse to be:
public class BlessingDTO
{
public List<string> BlessingCategoryName;
public List<bless> Blessings;
}
public class bless
{
public string text;
public int length;
}
how can I initialize the new class ?
Blessings = new bless
won't give the results. how can I save the data to bring them in the response
Let's focus in this part:
items
.ToList()
.ConvertAll(x =>
new BlessingDTO()
{
BlessingCategoryName = x.ToList().Select(y => y.MessageName).Distinct().ToList(),
Blessings = x.ToList().Select(y => y.MessageText).ToList()
}
);
where items is probably a List<List<Message>>, thus x being a List<Message>.
Now what is causing an error is the following: Blessings = x.ToList().Select(y => y.MessageText).ToList(). This creates a new list for the list of messages, then selects the MessageText from that list, which results in IEnumerable<string>. In the end a new list is created for these strings. This list of strings isn't assignable to List<bless>, thus will generate an error.
What you want is a result of List<bless>, so we need to convert the List<Message> list into a List<bless> somehow. We know how to do that, namely with a select: x.Select(message => new bless()).ToList(). All we have to do is fill in the properties of bless: x.Select(message => new bless { text = message.MessageText }).ToList(). The other property is up to you.
You can initialise the list like this:
public class BlessingDTO
{
public List<string> BlessingCategoryName;
public List<bless> Blessings = new List<bless>();
}
Although, I would recommend these fields are changes to properties, as that is more idiomatic in C#
public class BlessingDTO
{
public List<string> BlessingCategoryName {get;set;}
public List<bless> Blessings {get;set;} = new List<bless>();
}

How to wrap bulk upsert in Mongo with Task in C#

I have some model
public partial class Option
{
[BsonId]
public int Id { get; set; }
public Quote Quote { get; set; }
public Contract Contract { get; set; }
}
Method that does bulk write
public Task SaveOptions(List<Option> contracts)
{
var context = new MongoContext();
var processes = new List<Task<UpdateResult>>();
var collection = context.Storage.GetCollection<Option>("options");
contracts.ForEach(contract =>
{
var item = Builders<Option>.Update.Set(o => o, contract);
var options = new UpdateOptions { IsUpsert = true };
processes.Add(collection.UpdateOneAsync(o => o.Id == contract.Id, item, options));
});
return Task.WhenAll(processes);
}
Call for the method above
Task.WhenAll(service.SaveOptions(contracts)) // also tried without Task.WhenAll
For some reason, it doesn't create any record in Mongo DB
Update
Tried to rewrite bulk write this way, still no changes.
public Task SaveOptions(List<Option> contracts)
{
var context = new MongoContext();
var records = new List<UpdateOneModel<Option>>();
var collection = context.Storage.GetCollection<Option>("options");
contracts.ForEach(contract =>
{
var record = new UpdateOneModel<Option>(
Builders<Option>.Filter.Where(o => o.Id == contract.Id),
Builders<Option>.Update.Set(o => o, contract))
{
IsUpsert = true
};
records.Add(record);
});
return collection.BulkWriteAsync(records);
}
I think you want to use ReplaceOneAsync:
processes.Add(collection.ReplaceOneAsync(o => o.Id == contract.Id, contract, options));
The problem with using UpdateOneAsync here is that you're supposed to specify a field to update, and o => o doesn't do that. Since you want to replace the entire object, you need to use ReplaceOneAsync.
** Note that you can also do this with BulkWriteAsync by creating a ReplaceOneModel instead of an UpdateOneModel.

Many many-to-many filters

Today we faced a quite simple problem that were made even simpler by the dear predicates. We had a kind of event log and wanted to filter it client side (Windows Forms) using a list of criterias. We began by implementing to filter by a number of categories.
private List<Events> FilterEventsByCategory(List<Events> events,
List<Category> categories)
{
return events.FindAll(ev =>
categories.Exists(category => category.CategoryId==ev.CategoryId));
}
The next step is to implement a couple of other filters. Do you know of a nice way to generalize these to maybe somehow not having to write one method per filter? Or at least a clean way to have a dynamic list of filters that we want to apply simultaneously.
The clients are still on framework 3.0 so no LINQ.
Update:
I had a hard time deciding who I should give credit for my solution. Marc had some nice ideas and is really good at explaining them. Most probaly I would have got my answer from him if I only had explained my problem a little better. Ultimately it was the generic Filter class that cmartin provided that got me on track. The Filter class used below can be found in cmartins' answer and the User class you get to dream up yourself.
var categoryFilter = new Filter<Event>(ev => categories.Exists(category => category.CategoryId == ev.CategoryId));
var userFilter = new Filter<Event>(ev => users.Exists(user => user.UserId == ev.UserId));
var filters = new List<Filter<Event>>();
filters.Add(categoryFilter);
filters.Add(userFilter);
var eventsFilteredByAny = events.FindAll(ev => filters.Any(filter => filter.IsSatisfied(ev)));
var eventsFilteredByAll = events.FindAll(ev => filters.All(filter => filter.IsSatisfied(ev)));
re "so no LINQ" - have you looked at LINQBridge? Since you are using C# 3.0 this would be ideal...
I'm afraid for the main question, I don't fully understand what you are trying to do and what you are trying to avoid - can you clarify at all? But if you use the LINQBridge approach you can combine filters using successive .Where() calls.
One interpretation of the question is that you don't want lots of filter methods - so perhaps pass in one or more further predicates into the method - essentially as a Func<Event, Func<Category, bool>> - or in pure 2.0 terms, a Converter<Event, Predicate<Category>>:
private static List<Events> FilterEvents(
List<Events> events,
List<Category> categories,
Converter<Events, Predicate<Category>> func)
{
return events.FindAll(evt =>
categories.Exists(func(evt)));
}
which is then used (to be as above) as:
var result = FilterEvents(events, categories,
evt => category => category.CategoryId==evt.CategoryId);
Here's a very rudimentary sample of where I would start.
internal class Program
{
private static void Main()
{
var ms = new Category(1, "Microsoft");
var sun = new Category(2, "Sun");
var events = new List<Event>
{
new Event(ms, "msdn event"),
new Event(ms, "mix"),
new Event(sun, "java event")
};
var microsoftFilter = new Filter<Event>(e => e.CategoryId == ms.CategoryId);
var microsoftEvents = FilterEvents(events, microsoftFilter);
Console.Out.WriteLine(microsoftEvents.Count);
}
public static List<Event> FilterEvents(List<Event> events, Filter<Event> filter)
{
return events.FindAll(e => filter.IsSatisfied(e));
}
}
public class Filter<T> where T: class
{
private readonly Predicate<T> criteria;
public Filter(Predicate<T> criteria)
{
this.criteria = criteria;
}
public bool IsSatisfied(T obj)
{
return criteria(obj);
}
}
public class Event
{
public Event(Category category, string name)
{
CategoryId = category.CategoryId;
Name = name;
}
public int CategoryId { get; set; }
public string Name { get; set; }
}
public class Category
{
public Category(int categoryId, string name)
{
CategoryId = categoryId;
Name = name;
}
public string Name { get; set; }
public int CategoryId { get; set; }
}

Categories

Resources