A more concise syntax in C# constructor? - c#

I have a constructor something like the following:
using Microsoft.Data.Extensions;
public class Complaint
{
public int Id {get; set;}
public int Transcript {get; set;}
//... etc. ... Lots more properties
public Complaint(int id)
{
var command = dataContext.CreateStoreCommand(
"dbo.stp_Complaint_Get",
CommandType.StoredProcedure,
new SqlParameter("Id", id));
var complaint = command.Materialize(x =>
new Complaint
{
Id = x.Field<int>("Id"),
Transcript = x.Field<string>("Transcript");
//... etc. ... Lots more fields from db
}
this.Id = complaint.Id;
this.Transcript = complaint.Transcript;
//... etc. ... Lots more properties to set
}
}
Is there a syntax in C# that would allow me to carry out the last part in one step instead of two? i.e. conceptually something like this:
this = command.Materialize(x =>
new Complaint
{
Id = x.Field<int>("Id"),
Transcript = x.Field<string>("Transcript");
}

Well, you could use a static method instead of a constructor:
public static Complaint FromId(int id)
{
var command = dataContext.CreateStoreCommand(
"dbo.stp_Complaint_Get",
CommandType.StoredProcedure,
new SqlParameter("Id", id));
return command.Materialize(x =>
new Complaint
{
Id = x.Field<int>("Id"),
Transcript = x.Field<string>("Transcript");
//... etc. ... Lots more fields from db
});
}

Not inherently. You could store the complaint object inside the class, and have all the properties point off that rather than setting them all from the constructor.
eg
public class Complaint
{
private readonly {type of complaint} m_Complaint;
public int Id
{
get { return m_Complaint.Id; }
}
// ...etc
}
You could get more complicated if you don't have a setter on m_Complaint - keeping nullable backing fields, and check that before you access the m_Complaint properties

I believe you may try something like this:
var currentInstance = this;
var complaint = command.Materialize(x =>
new Complaint
{
Id = currentInstance.Id = x.Field("Id"),
Transcript = currentInstance.Transcript = x.Field("Transcript");
//... etc. ... Lots more fields from db
});
I don't think you can capture this in the closure, so using that currentInstance trick should help, however it may as well turn out to be redundant.
Besides, code such as that one is sort of obfuscated compared to your current solution. I believe that your code is pretty fine as it is.

I gather that you're trying to avoid setting all of those properties twice, because any change to the structure requires you to update the properties in two places, is that correct?. You could try to use some reflection to copy the properties from one instance to another.
var complaint = command.Materialize(x =>
new Complaint
{
Id = x.Field<int>("Id"),
Transcript = x.Field<string>("Transcript");
//... etc. ... Lots more fields from db
}
foreach (var prop in typeof(Complaint).GetProperties())
...

Have the complaint object as a member variable and the get/set properties accesses the underlying complaint's properties?

Related

C# Initialize class properties from REST client inside constructor

I've searched a lot and I think this is possible, but I feel like I am just blocked from knowing how to properly format it.
I have a class representing a product that is a relation class from our CRM to Magento.
Inside the constructor, I have to do some stuff like this...
public Product(IBaseProduct netforumProduct, MagentoClient client)
{
Product existingMagentoProduct = client.GetProductBySku(netforumProduct.Code);
if (existingMagentoProduct != null)
{
this.id = existingMagentoProduct.id;
this.name = existingMagentoProduct.name;
... many of them ...
this.visibility = existingMagentoProduct.visibility;
this.extension_attributes.configurable_product_links = existingMagentoProduct.extension_attributes.configurable_product_links;
}
else
{
// its a new product, new up the objects
this.id = -1;
this.product_links = new List<ProductLink>();
this.options = new List<Option>();
this.custom_attributes = new List<CustomAttribute>();
this.media_gallery_entries = new List<MediaGalleryEntry>();
this.extension_attributes = new ExtensionAttributes();
this.status = 0; // Keep all new products disabled so they can be added to the site and released on a specific day (this is a feature, not an issue / problem).
this.attribute_set_id = netforumProduct.AttributeSetId;
this.visibility = 0;
}
}
It seems silly to have to initialize all of the properties like that. I could use a mapper but that seems like a bandaid. I have to see if the product exists in magento first, and populate its ID and values, otherwise whenever I save the product it creates an additional one.
I considered doing the class constructor calling a static method, but I couldn't get the syntax right.
It might just be too late and I need to think about something else for awhile.
If you must do it in the constructor, you can get rid of a lot of code by first setting 'default' values to the 'Product' properties. This will remove the need to do them in the constructor. Next, if you wanted to automatically set the class's properties, you can use reflection.
public class Product
{
public int Id { get; set; } = -1;
public List<ProductLink> Product_Links { get; set; } = new List<ProductLink>();
....
public int Visibility { get; set; } = 0;
public Product(IBaseProduct netforumProduct, MagentoClient client)
{
var existingMagentoProduct = client.GetProductBySku(netforumProduct.Code);
if (existingMagentoProduct != null)
{
foreach (PropertyInfo property in typeof(Product).GetProperties().Where(p => p.CanWrite))
{
property.SetValue(this, property.GetValue(existingMagentoProduct, null), null);
}
}
}
}
Though, I would like to point out that you probably shouldn't be using a REST client inside a class constructor, especially to just populate its data (also, you are performing a synchronous operation). It would be cleaner to have another layer that is responsible for populating this class using the client, and then use something like AutoMapper to map the data to it.

MongoDB Linq OfType() on fields

This is my MongoDB document structure:
{
string _id;
ObservableCollection<DataElement> PartData;
ObservableCollection<DataElement> SensorData;
...
other ObservableCollection<DataElement> fields
...
other types and fields
...
}
Is there any possibility to retrieve a concatenation of fields with the type ObservableCollection<DataElement>? Using LINQ I would do something like
var query = dbCollection
.AsQueryable()
.Select(x => new {
data = x
.OfType(typeof(ObservableCollection<DataElement>))
.SelectMany(x => x)
.ToList()
});
or alternatively
data = x.Where(y => typeof(y) == typeof(ObservableCollection<DataElement>)
.SelectMany(x => x).ToList()
Unfortunately .Where() and .OfType() do not work on documents, only on queryables/lists, so is there another possibility to achieve this? The document structure must stay the same.
Edit:
After dnickless answer I tried it with method 1b), which works pretty well for getting the fields thy way they are in the collection. Thank you!
Unfortunately it wasn't precisely what I was looking for, as I wanted to be all those fields with that specific type put together in one List, at it would be returned by the OfType or Where(typeof) statement.
e.g. data = [x.PartData , x.SensorData, ...] with data being an ObsverableCollection<DataElement>[], so that I can use SelectMany() on that to finally get the concatenation of all sequences.
Sorry for asking the question unprecisely and not including the last step of doing a SelectMany()/Concat()
Finally I found a solution doing this, but it doesn't seem very elegant to me, as it needs one concat() for every element (and I have more of them) and it needs to make a new collection when finding a non-existing field:
query.Select(x => new
{
part = x.PartData ?? new ObservableCollection<DataElement>(),
sensor = x.SensorData ?? new ObservableCollection<DataElement>(),
}
)
.Select(x => new
{
dataElements = x.part.Concat(x.sensor)
}
).ToList()
In order to limit the fields returned you would need to use the MongoDB Projection feature in one way or the other.
There's a few alternatives depending on your specific requirements that I can think of:
Option 1a (fairly static approach): Create a custom type with only the fields that you are interested in if you know them upfront. Something like this:
public class OnlyWhatWeAreInterestedIn
{
public ObservableCollection<DataElement> PartData { get; set; }
public ObservableCollection<DataElement> SensorData { get; set; }
// ...
}
Then you can query your Collection like that:
var collection = new MongoClient().GetDatabase("test").GetCollection<OnlyWhatWeAreInterestedIn>("test");
var result = collection.Find(FilterDefinition<OnlyWhatWeAreInterestedIn>.Empty);
Using this approach you get a nicely typed result back without the need for custom projections.
Option 1b (still pretty static): A minor variation of Option 1a, just without a new explicit type but a projection stage instead to limit the returned fields. Kind of like that:
var collection = new MongoClient().GetDatabase("test").GetCollection<Test>("test");
var result = collection.Find(FilterDefinition<Test>.Empty).Project(t => new { t.PartData, t.SensorData }).ToList();
Again, you get a nicely typed C# entity back that you can continue to operate on.
Option 2: Use some dark reflection magic in order to dynamically create a projection stage. Downside: You won't get a typed instance reflecting your properties but instead a BsonDocument so you will have to deal with that afterwards. Also, if you have any custom MongoDB mappings in place, you would need to add some code to deal with them.
Here's the full example code:
First, your entities:
public class Test
{
string _id;
public ObservableCollection<DataElement> PartData { get; set; }
public ObservableCollection<DataElement> SensorData { get; set; }
// just to have one additional property that will not be part of the returned document
public string TestString { get; set; }
}
public class DataElement
{
}
And then the test program:
public class Program
{
static void Main(string[] args)
{
var collection = new MongoClient().GetDatabase("test").GetCollection<Test>("test");
// insert test record
collection.InsertOne(
new Test
{
PartData = new ObservableCollection<DataElement>(
new ObservableCollection<DataElement>
{
new DataElement(),
new DataElement()
}),
SensorData = new ObservableCollection<DataElement>(
new ObservableCollection<DataElement>
{
new DataElement(),
new DataElement()
}),
TestString = "SomeString"
});
// here, we use reflection to find the relevant properties
var allPropertiesThatWeAreLookingFor = typeof(Test).GetProperties().Where(p => typeof(ObservableCollection<DataElement>).IsAssignableFrom(p.PropertyType));
// create a string of all properties that we are interested in with a ":1" appended so MongoDB will return these fields only
// in our example, this will look like
// "PartData:1,SensorData:1"
var mongoDbProjection = string.Join(",", allPropertiesThatWeAreLookingFor.Select(p => $"{p.Name}:1"));
// we do not want MongoDB to return the _id field because it's not of the selected type but would be returned by default otherwise
mongoDbProjection += ",_id:0";
var result = collection.Find(FilterDefinition<Test>.Empty).Project($"{{{mongoDbProjection}}}").ToList();
Console.ReadLine();
}
}

How to parse a simple class to Bson and to a MongoDB collection

I'm brand new to MongoDB in C#.
I have created a very simple class which I would like to automatically insert into a collection.
How do I do that, if I don't want to map everything manually?
public class DummyClass
{
[BsonId]
public int Id { set; get; }
[BsonElement("first")]
public string First { set { _name = value; } }
[BsonConstructor]
public DummyClass()
{
Id = 2;
First = "1";
}
}
I had hoped I could do something like this:
_dbClient = new MongoClient();
_database = _dbClient.GetDatabase("testDB");
_collection = _database.GetCollection<BsonDocument>("Collection");
var doc = BsonDocument.Create(dummy);
_collection.InsertOneAsync(doc);
But it's no good. I get the exception:
System.ArgumentException : .NET type DummyClass cannot be mapped to BsonType.Document.
Parameter name: value
Any suggestions?
And I really don't want to do:
{
{"Id", "2"},
{"First", "1"},
}
EDIT:
I forgot this small line:
BsonClassMap.RegisterClassMap<DummyClass>();
It does wonders.
It makes sense to use BsonDocument when fields in collections don't match properties in your model class. Otherwise you should create collection mapped to your class.
_collection = _database.GetCollection<DummyClass>("Collection");
await _collection.InsertOneAsync(doc);
And don't forget about async/await methods in MongoDB.Driver.
You can use ToBsonDocument()
_collection.InsertOneAsync(doc.ToBsonDocument())

Alternatives to passing around a context object

I have a single context object that I want to be able to access from a large number of difference classes. I have code that looks like
Context ctx = new Context();
Section section = new Section(ctx) {
Data1 = new SomeData(ctx) { Value = 123 },
Data2 = new SomeOtherData(ctx) { Foo = "bar" },
SubSection = new Section(ctx) {
MoreData = new MoreData(ctx) { Text = "Hello!" }
}
};
But what I'd really like is code that looks like:
using(Context.New()) {
Section section = new Section() {
Data1 = new SomeData { Value = 123 },
Data2 = new SomeOtherData { Foo = "bar" },
SubSection = new Section {
MoreData = new MoreData { Text = "Hello!" }
}
};
// do something with section
}
Is this possible? I'll be using it in ASP.NET as well as .exes (and probably something else in the future) so I can't just store a static or thread local reference somewhere.
It doesn't need to be exactly as above, just a way where I won't have to pass the context to every object I create. I thought about using extension methods like context.createSomeData() but it requires more boilerplate per class and isn't really any better as you still need the context object.
Ideally should work under VS2008/.NET3.5, although I'd still be interested if there's any way to do it at all.
UPDATE: I ended up solving this by refactoring my approach into the following:
Section section = new Section {
Data1 = new SomeData { Value = 123 },
Data2 = new SomeOtherData { Foo = "bar" },
SubSection = new Section {
MoreData = new MoreData { Text = "Hello!" }
}
};
section.DoStuffWithContext(new Context());
While it may not work for everyone, it does what I need here.
I'll leave this question open in case someone comes up with a good solution to the initial problem.
You can define a static method Context.RetreiveData(), but you don't have to implement any boilerplate code inside of the method itself.
Using command pattern every specific project type can provide its own implementation for the RetreiveData() method. ASP.NET project can supply a method that would retreive data from Session. WinForm executable can supply a method that would retreive a data from some global variable. Yet another project can supply a method for retrieving data from DB.
No. There isn't any clear possibility. You are even using object initializers (new Obj { ... }), so you need to use the new operator (this makes using static methods/extension methods of ctx impossible).
The only thing you could do:
SomeData MakeSomeData(this Context ctx, int value)
{
return new SomeData(ctx) { Value = value };
}
and in the initialization:
Context ctx = new Context();
Section section = new Section(ctx) {
Data1 = ctx.MakeSomeData(123), ...
but I don't think you would gain anything
I second hvd's comment on your question, and I don't think it would require too much boilerplate. Suppose each class implements this:
public interface IContextConsumer {
Context Context { get; set; }
}
And your base class has a method like this:
protected void AddChild(IContextConsumer child) {
child.Context = this.Context;
}
The property implementations would only need to be:
private SomeData _data1;
public SomeData Data1 {
get { return _data1; }
set {
_data1 = value;
AddChild(_data1);
}
}
You could even allow the context to be reassigned on the root, if you did something like this:
protected void AddChild(IContextConsumer child) {
Children.Add(child);
child.Context = this.Context;
}
protected void OnContextChanged() {
foreach (var child in Children) child.Context = this.Context;
}
Derived classes would just need to call OnContextChanged in their Context property implementations.

C# automatic properties - is it possible to have custom getter with default setter?

It's possible I shouldn't even be attempting this in the first place, but here's what I have so far:
public List<int> AuthorIDs
{
get
{
var l = new List<int>();
using (var context = new GarbageEntities())
{
foreach (var author in context.Authors.Where(a => a.Books.Any(b => b.BookID == this.BookID)).ToList())
{
l.Add(author.AuthorID);
}
}
return l;
}
set; //compiler error
}
How would I leave the above setter without any sort of custom logic? In the olden days I think you would just use:
set { authorIDs = value; }
which doesn't work now.
Is this whole idea just terrible to begin with?
Edit:
To answer some people's questions: I'm attempting to combine MVC with Data Annotation validation, with default binding, with Entity Framework 4.0...and failing quite fantastically, I believe.
No, it's not possible. Either everything is explicit, or the whole property is automatic. Anyway, in that case the setter doesn't seem to make any sense... there should be no setter at all.
Also, I think you should make it a method. It would make it clearer to the caller that it performs a possibly lenghty calculation. It's also against guidelines to perform complex processing in a property.
This answer goes a bit wider than simply getting rid of the setter on the property - combine it with the rest of the answers and comments, and take the bits that make sense. Hopefully the bit at the end will help too, maybe just not right now.
If you're using this in a model for data binding purposes and thus want it exposed as a property, I would do something like this:
public class BookModel
{
public IList<int> AuthorIds { get; set; }
}
Make a service which you will call to populate your model:
public class BookService()
{
public List<int> GetAuthorIDs(int bookId)
{
var authorIds = new List<int>();
using (var context = new GarbageEntities())
{
foreach (var author in context.Authors.Where(
a => a.Books.Any(b => b.BookID == bookId)))
{
authorIds.Add(author.AuthorID);
}
}
return authorIds;
}
}
In your controller:
public ViewResult List(int id)
{
var model = new BookModel
{
AuthorIds = service.GetAuthorIDs(id)
};
return View(model);
}
I explicitly haven't included how to instantiate the book service in the controller. My preference would be to inject it at runtime in a constructor, but this will require you to have a custom controller factory - one step at a time. You could just new it up in the default constructor:
private readonly BookService service;
public BookController()
{
service = new BookService();
}
In an ideal world though, I would do this:
private readonly BookService service;
public BookController(BookService service)
{
if(service == null)
throw new ArgumentException("service must be supplied");
this.service = service;
}
However the default MVC controller factory expects controllers to have a default parameter, so doing it with constructor injection will take a bit more work.
The default setter would create a backing variable at compile time with a name as follows:
[CompilerGenerated]
private string <AuthorIDs>k__BackingField;
Since this is created at compile time, it cannot be referenced in your code until it has been created, and in addition, the angle brackets (intentionally) are not permissable in variable names.
For this reason, it would be next to useless to allow something to be stored in this variable (which is essentially what that automatic setter does) with no way of accessing this value at any point in the future (after all the getter here is not an automatic getter it would return something entirely different)
So to summerise, without a getter, there would (which is the only way to retrieve the value from this backing variable) there would be no point in having a private setter
if you want to do it your way, just do the following:
private List<int> authorIDs;
public List<int> AuthorIDs
{
get
{
var l = new List<int>();
using (var context = new GarbageEntities())
{
foreach (var author in context.Authors.Where(a => a.Books.Any(b => b.BookID == this.BookID)).ToList())
{
l.Add(author.AuthorID);
}
}
return l;
}
set{authorIDs = value; //this does not make much sense though ... what are you trying to do by setting authorIDs?
}
}
but just like others are saying, this is an overkill for a property, put it in the method, something like
public List<int> GetAuthorIDs(int bookId)
{
var l = new List<int>();
using (var context = new GarbageEntities())
{
foreach (var author in context.Authors.Where(a => a.Books.Any(b => b.BookID == bookId)).ToList())
{
l.Add(author.AuthorID);
}
}
return l;
}
There is actually a way of doing that:
public List<int> AuthorIDs
{
get
{
var l = new List<int>();
using (var context = new GarbageEntities())
{
foreach (var author in context.Authors.Where(a => a.Books.Any(b => b.BookID == this.BookID)).ToList())
{
l.Add(author.AuthorID);
}
}
return l;
}
set{
this.SetPropertyValue(page => page.AuthorIDs, value);
}
}

Categories

Resources