This question already has answers here:
Return multiple values to a method caller
(28 answers)
Closed 7 months ago.
Code snippet:
var pMessageId = p.Get<byte[]>("p_message_id");
var pContentId = p.Get<byte[]>("p_content_id");
if (pMessageId != null && pContentId != null)
{
var result = new Guid(pMessageId);
var secondResult = new Guid(pContentId);
await transaction.CommitAsync(cancellationToken);
return
}
I want to return two guid values at once (result, secondResult). How do I do that?
Use a value tuple:
public (Guid Result, Guid SecondResult) MyMethod(){
...
return (result, secondResult);
}
The issue with "Tuple" is that your values are accessible by .Item1, .Item2.
IMHO, you mortgage future maintainability for a quick fix.
(That part is opinion based).
But technically, one way to solve it is to create a simple Poco. (Aka, start no opinion area)
public sealed class GuidHolder
public Guid GuidOne { set; get; }
public Guid GuidTwo { set; get; }
}
or a more "convenient" version
public sealed class GuidHolder
public GuidHolder() : this(Guid.NewGuid() , Guid.NewGuid() )
{}
public GuidHolder(Guid g1, Guid g2)
{
this.GuidOne = g1;
this.GuidTwo = g2;
}
public Guid GuidOne { private set; get; }
public Guid GuidTwo { private set; get; }
}
So the above is a no opinion technical answer.
Back to opinion:
IMHO, (the above poco's are) 3 minutes of work. Now your code is more readable for the future.
When your code base is riddled with ".Item1" .. "ItemN" code.....is becomes difficult to maintain. Aka, mortgage the future maintainability to save 3 minutes of work.
Related
I am learning DDD and trying to model articles, its variants and parameters.
Article can be on it's own without variants
Variant must be child of an article
both article and variant can have some parameters (colors, brands, sizes...), physical quantities (width, length, some article-specific like inner length)
If you set some parameter on an article, it can be "synchronized" to it's children variants
you can override this in a variant by setting that parameter as "unlinked", then this variant would have different parameter value than article
some parameters can be set multiple times (color: red, blue), but some only once (brand)
those parameters are dynamically create, it's not a Color or Brand property but key-value selected from preconfigured values
I think my main aggregate roots will be Article and Variant.
My current code looks like this:
internal class Article : AggregateRoot<ArticleId>
{
private readonly ISet<VariantId> _variants = new HashSet<VariantId>();
private readonly ISet<AssignedParameter> _parameters = new HashSet<AssignedParameter>();
private readonly ISet<AssignedPhysicalQuantity> _physicalQuantities = new HashSet<AssignedPhysicalQuantity>();
public string Name { get; private set; }
public string Catalog { get; private set; }
public IReadOnlySet<VariantId> Variants => _variants.AsReadOnly();
public IReadOnlySet<AssignedParameter> Parameters => _parameters.AsReadOnly();
public IReadOnlySet<AssignedPhysicalQuantity> PhysicalQuantities => _physicalQuantities.AsReadOnly();
private Article(ArticleId id, string name, string catalog)
: base(id)
{
Name = name;
Catalog = catalog;
}
public static Article Register(ArticleId id, string name, string catalog)
{
var article = new Article(id, name, catalog);
article.AddEvent(new ArticleRegistered(article.Id, article.Name, article.Catalog));
return article;
}
public void AssignParameter(Parameter parameter, ParameterValue parameterValue, bool syncToVariants)
{
if (!parameter.CanBeAssignedMultipleTimes && _parameters.Any(p => p.ParameterId == parameter.Id))
{
throw new ParameterCanBeAssignedOnlyOnceException($"Parameter {parameter.Id} can by assigned only once.");
}
var assignedParameter = new AssignedParameter(parameter.Id, parameterValue.Id, syncToVariants);
if (!_parameters.Add(assignedParameter))
{
throw new ParameterIsAlreadyAssignedException($"Parameter {parameter.Id} with value {parameterValue.Id} is already assigned.");
}
AddEvent(new ArticleParameterAssigned(Id, assignedParameter.ParameterId, assignedParameter.ParameterValueId));
}
public void UnassignParameter(Parameter parameter, ParameterValue parameterValue)
{
var assignedParameter = _parameters.FirstOrDefault(p => p.ParameterId == parameter.Id && p.ParameterValueId == parameterValue.Id);
if (assignedParameter is null)
{
throw new ParameterIsNotAssignedException($"Parameter {parameter.Id} is not assigned.");
}
_parameters.Remove(assignedParameter);
AddEvent(new ArticleParameterUnassigned(Id, assignedParameter.ParameterId, assignedParameter.ParameterValueId));
}
// physical quantity assign / unassign are similar to parameters
}
internal class Variant : AggregateRoot<VariantId>
{
private readonly ISet<AssignedParameter> _parameters = new HashSet<AssignedParameter>();
private readonly ISet<AssignedPhysicalQuantity> _physicalQuantities = new HashSet<AssignedPhysicalQuantity>();
public string Name { get; private set; }
public string Catalog { get; private set; }
public EanCode Ean { get; private set; }
public decimal Weight { get; private set; }
public IReadOnlySet<AssignedParameter> Parameters => _parameters.AsReadOnly();
public IReadOnlySet<AssignedPhysicalQuantity> PhysicalQuantities => _physicalQuantities.AsReadOnly();
internal Variant(VariantId id, string name, string catalog, EanCode ean, decimal weight)
: base(id)
{
Name = name;
Catalog = catalog;
Ean = ean;
Weight = weight;
}
// parameter and physical quantity assignment methods
}
Parameters:
internal class Parameter : AggregateRoot<ParameterId>
{
private readonly ISet<ParameterValue> _values = new HashSet<ParameterValue>();
public string Code { get; private set; }
public string Name { get; private set; }
public bool CanBeAssignedMultipleTimes { get; private set; }
public IReadOnlySet<ParameterValue> Values => _values.AsReadOnly();
public Parameter(ParameterId id, string code, string name, bool canBeAssignedMultipleTimes)
: base(id)
{
Code = code;
Name = name;
CanBeAssignedMultipleTimes = canBeAssignedMultipleTimes;
}
}
internal class ParameterValue : Entity<ParameterValueId>
{
public string Code { get; private set; }
public string Name { get; private set; }
public Parameter Parameter { get; private init; } = null!;
public ParameterValue(ParameterValueId id, string code, string name)
: base(id)
{
Code = code;
Name = name;
}
}
Value objects:
// for Article, variant doesn't have SyncToVariants property and has some other
internal class AssignedParameter : ValueObject
{
public ParameterId ParameterId { get; private init; }
public ParameterValueId ParameterValueId { get; private init; }
public bool SyncToVariants { get; private init; }
public AssignedParameter(ParameterId parameterId, ParameterValueId parameterValueId, bool syncToVariants)
{
ParameterId = parameterId;
ParameterValueId = parameterValueId;
SyncToVariants = syncToVariants;
}
protected override IEnumerable<object> GetEqualityComponents()
{
yield return ParameterId;
yield return ParameterValueId;
}
}
internal class AssignedPhysicalQuantity : ValueObject { ... }
My questions:
What would be the best way to notify variants of the parameter change? I can think of two ways using events.
First would be using ArticleParameterChanged(ArticleId, parameter.Id, parameterValue.Id). I would handle this event and changed all variants at once in the handler - I don't think this is the way, but I wouldn't need to hold variants collection in article.
Second would be to loop through variant IDs and create ArticleVariantParameterChanged(ArticleId, VariantId, parameterId, parameterValueId) event. This seems more correct to me?
if (syncToVariants)
{
foreach (var variantId in _variants)
{
AddEvent(new ArticleVariantParameterChanged(Id, variantId, parameter.Id, parameterValue.Id);
}
}
How do I add new variant to article? The easiest way would be to create new variant and update the article in one transaction.
// Article method
public Variant RegisterVariant(VariantId variantId, ...)
{
var variant = new Variant(variantId, ...);
_variants.Add(variantId);
return variant;
}
// command handler? or domain service?
var article = await _articleRepo.GetAsync(articleId);
var variant = article.RegisterVariant(variantId, ...);
await _variantRepo.AddAsync(variant);
await _articleRepo.UpdateAsync(article);
Or using events?
// Article method
public Variant RegisterVariant(VariantId variantId, ...)
{
var variant = Variant.Register(variantId, this.Id, ...);
return variant;
}
// Variant static method
public Variant Register(VariantId variantId, ArticleId articleId, ...)
{
var variant = new Variant(variantId, articleId, ...);
variant.AddEvent(new VariantRegistered(variantId, articleId));
return variant;
}
// command handler
var variant = article.RegisterVariant(...);
await _variantRepo.AddAsync(variant);
// VariantRegisteredHandler
article.AddVariant(variantId);
However here it seems kind of confusing to me, article.RegisterVariant and article.AddVariant... Maybe it's just wrong naming?
Also here can occur condition race between adding new variant and assigning a new parameter, when someone adds new parameter before the VariantRegistered event was handled, so it wouldn't sync that parameter.
So I'm thinking, is it even good idea to store those shared parameters in each variant? Maybe it would be enough to just have variant specific parameters there and merge everything in the read model? However this would be harder to prevent duplications - if the article already has a parameter "color - red", assigning "color - red" to variant would need to check the article parameters too and there can be another race condition.
I read that entities without any domain business logic could be treated as CRUD, that means they wouldn't even inherit AggregateRoot and each of them would have own repository, right?
Let's say someone really wants to delete some parameter value, for example blue color. This wouldn't (hopefully) happen in my app, but I'm still curious how this would be handled. He confirms he really wants to delete it and I need to go through all articles and unassign it from them. How?
My idea would be either to have ParameterValueDeleted event and ParameterValueDeletedHandler would query for all articles and variants and unassign it one by one, this handler would take really long time to execute.
Or ParameterValueDeletedHandler would query for all IDs, create some event for them and that handler would unassign it later. However in the latter case I don't know how that event would be named to make sense. UnassignArticleParameter seems more like command than event and ArticleParameterUnassigned is something coming from article. Also I read that commands indicate something that can be rejected, so I would say command doesn't fit here.
Also I see a problem when someone deletes that parameter and someone else queries for an article which doesn't have it unassigned yet - database join would fail because it would join to non existent parameter (considering single database for read and write model).
If I wanted to have mandatory parameters, where would be the best place to validate that all of them are set? Move the article registration logic to ArticleFactory and check it there? And for variants maybe ArticleService or VariantFactory? This seems kinda inconsistent to me, but maybe it's right?
var article = await _articleRepo.GetAsync(articleId);
_articleService.RegisterVariant(article, /* variant creation data */);
_variantFactory.Register(article, /* variant creation data */);
I think this should be all, I hope I explained everything well.
I would appreciate any help with this!
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 5 years ago.
Improve this question
I'm using a web service with this request format:
{
"type":"LogonReq",
"id":"b43b301c-5216-4254-b3fc-cc863d4d6652",
"date":"Wed, 16 Aug 2017 17:35:34 UTC",
"parameters":[
{
"userName":"user",
"password":"password"
}
]
}
Even though every message in the API requires only 1 set of parameters, the API still requires "parameters" to be an array.
Is it better practice to have the caller create the list or to create the list in the MessageBase constructor, or something altogether different ?
Which way would satisfy an OOP purist code reviewer?
public class MessageBase<T>
{
public MessageBase() { this.parameters = new List<T>(); }
public string type { get; set; }
public string id { get; set; }
public string date { get; set; }
public List<T> parameters { get; set; }
}
public class LogonMessage{
public string userName { get; set; }
public string password { get; set; }
}
var logon = new MessageBase<LogonMessage>()
{
date = DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss UTC"),
id = Guid.NewGuid().ToString(),
type = "LogonReq",
};
logon.parameters.Add(new LogonMessage() { userName = "user", password = "password" });
or
public class MessageBase<T>
{
public string type { get; set; }
public string id { get; set; }
public string date { get; set; }
public List<T> parameters { get; set; }
}
public class LogonMessage{
public string userName { get; set; }
public string password { get; set; }
}
var logon = new MessageBase<LogonMessage>()
{
date = DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss UTC"),
id = Guid.NewGuid().ToString(),
type = "LogonReq",
parameters = new List<LogonMessage>() { new LogonMessage() { userName = "user", password = "password" } }
};
You will probably find many interesting opinions and answers to this. I can only give you mine based on my experience.
I myself would probably initialize the list within the constructor
However, since you are trying to get a good idea around the phylosophy of coding, here are some things to consider :
1) Does the MessageBase object make sense or is it useful with a NULL list? Is there any scenario where I want this list as null?
2) Actually, I would expect an OOP purist to say that you should not expose the "parameters" as a List. By exposing the object as a property, someone can do this:
login.parameters.Add()
Or this
logon.parameters = anotherListOfMine
In a way it does break encapsulation. You could make the list property read only (ie, with a protected/private setter) but "clients" of this class will still be able to access all properties and methods of the List and modify/handle them.
Now, you have to expose this in some way as you will be serializing/deserializing into JSON, so that poses a problem! Maybe you can have a private/protected List field and expose the values through a readonly property that exposes an IEnumerable and behind the scenes, you are doing:
get { return myPrivateList.ToArray(); }
3) But again, do you really win that much? Your next question should be "Who is my client?" If you are exposing this class to other developers, or is part of a framework, you might want to apply something like my point number 2, and limit the exposure. If this is internal to your application and your team maybe you should be pragmatic and simply expose the List as you are doing right now.
4) Alternatively, while still making it open, you could instead have a property of type IEnumerable so you can pass in any type.
5) Another option is to expose your list because you need to serialize it, but make it readonly. Have instead different methods, or non-serializable properties, of username and password. If these parameters are always the same that is. I am thinking this might not be your case.
I think I could go on and on this. I will stop here before you hit the downvote button :).
I have been agonizing over this problem for a few days now and have no hope left. I'm still in the early stages of learning C#, so excuse me if my explanations or understanding are lacking.
My scenario is that I have a need to access an API and download the data as JSON then deserialize it into a class. At the moment, things work as they should, however every variable is defined as String which means I need to convert and manipulate data that should be int/double on the fly constantly as the API can give "N/A" for these data. The impression I get is relying on everything being string is bad practice.
So how should I implement it? I need to be able to store the data as the correct type while keeping in mind that it could be wrong.
Example of properties with wrong type
public string Title { get; set; }
public string Year { get; set; } // Wanted int. Often has an end year "2010-2014"
public string Metascore { get; set; } // Wanted double. Could be "N/A"
The only way I can imagine solving this is by having two classes: the first one being the original string-only class, then having the second being an almost identical class with the desired properties that uses the data from the original then converts it.
My problem with that is that the class already has a few dozen properties, so duplicating it seems nearly as wasteful as the original problem. Regardless, I would like to know an alternative for future use anyway.
EDIT:
Found a similar question here, though unfortunately it didn't help.
you can deserialize the json to JObject and than load it your self
public class RootObject
{
public RootObject(JObject obj)
{
Title = obj["Title"].ToString();
var year = obj["year"].ToString();
Year = year == "N/A" ? 0 : int.Parse(year);
var metascore = obj["Metascore"].ToString();
Metascore = metascore == "N/A" ? 0 : int.Parse(metascore);
}
public string Title { get; set; }
public int Year { get; set; }
public double Metascore { get; set; }
}
static void Main(string[] args)
{
string json = "{\"Title\":\"test\",\"year\":\"2012\",\"Metascore\":\"N/A\"}";
RootObject root = new RootObject(JObject.Parse(json));
}
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 8 years ago.
Improve this question
I am new to both SPA and Entity Framework and I have a question about the right/preferred way to do this.
I've got a web page to search customers by some filters (like name, surname, birthday date, phone number).
With ajax I pass from View to Controller a ViewModel object like this:
public class CustomerSearch
{
public string Name { get; set; }
public string Surname { get; set; }
public DateTime? Birthday{ get; set; }
public string Phone{ get; set; }
}
Into controller I've got this search method:
public List<CustomerList> GetCustomersList(CustomerSearch cs)
{ ..... }
In my application there are also DataAccess objects.
Now, the question is: how I can to do the query to database in smart mode?
I've thinked some scenario but i don't know which is the best about the layer separation.
I can ask to model an IQueryable object and perform the Where condition into controller
I can create a DataAccess method with the filters like parameters
I can create a Customer object (model object) filled with the filters value and pass this to model that perform the query
Which is the best method?
Thanks in advance.
Daniele
I would suggest the first option you present for the following reasons:
Your second option would possibly require a lot of parameters if the number of possible search queries grows. It is generally not recommended to have a very large number of parameters since it leads to readability issues. (e.g. calling a method with 30 parameters)
Your third option would make it impossible to create a query like 'BirthDate in 1998' or 'Surname starts with Q'.
By creating a class specifically for the purposes of querying you can do something like the following:
public class CustomerQuery
{
public string Name { get; set; }
public DateTime? BirthDay { get; set; }
internal IQueryable<Customer> Apply(IQueryable<Customer> query)
{
var result = query;
if (!string.IsNullOrWhiteSpace(Name))
{
result = result.Where(c => c.Name == Name);
}
if (BirthDay.HasValue)
{
result = result.Where(c => c.BirthDay == BirthDay.Value);
}
return result;
}
}
Pay specific attention to the internal visibility of the Apply method. If you define this class in your Data Access Layer and this is defined in a separate C# project, the Apply method will only be visible to this Data Access Layer and not to the GUI project.
Calling it would be:
public List<Customer> GetCustomersList(CustomerQuery customerQuery)
{
using (var context = new MyDbContext())
{
var customers = context.Customers;
return customerQuery.Apply(customers).ToList();
}
}
This can be further extended to support e.g. different types of string search (Contains, StartsWith etc):
public enum TextFilterType
{
StartsWith,
EndsWith,
Contains,
ExactMatch
}
public class StringQuery
{
private readonly string _value;
private readonly TextFilterType _filterType;
public StringQuery(string value, TextFilterType filterType)
{
_value = value;
_filterType = filterType;
}
public string Value
{
get { return _value; }
}
public TextFilterType FilterType
{
get { return _filterType; }
}
}
If we would apply this to the CustomerQuery type:
public class CustomerQuery
{
public StringQuery Name { get; set; }
}
Then you look at the FilterType property of the StringQuery to find out if you need to do Contains, StartsWith etc.
This is homework!!! Please do not interpret this as me asking for someone to code for me.
My Program: http://pastebin.com/SZP2dS8D
This is my first OOP. The program works just fine without user input(UI), but the implementation of it renders my design partially ineffective. I am not using a List collection because of assignment restrictions. My main goal is to have everything running from the Transcript class. Here are some issues I am running into:
Allowing the user to add new course without having to create a new instance of Transcript
each time
Associating the Courses added to a specific Quarter
Here is some pseudo code to show what I am trying to accomplish. I have been experimenting with it, but have yet to succeed.
Please enter the quarter: (user input)
Would you like to add a course?
while (true)
Enter Course/Credits/Grade
//new Course information populated with user input
transcript.AddCourse.to specific Quarter((Fall 2013) new Course("Math 238", 5, 3.9));
transcript.AddCourse.to specific Quarter((Fall 2013) new Course("Phys 223", 5, 3.8));
transcript.AddCourse.to specific Quarter((Fall 2013) new Course("Chem 162", 5, 3.8));
MY QUESTION[S]: Should I keep the Transcript class, or discard it? With the current functionality of creating a new course, is it possible to keep it this way while using UI, or do I need to head back to the chalk board and reconfigure?
Hopefully this is coherent and not too broad. If clarification is needed please ask and I will me more than happy to provide more details.
I would consider the following compositon
public class Transcript
{
public Quarter[] Quarters{get;set;}
}
public class Quarter
{
public Course[] Courses{get;set;}
}
You only need one instance of the transcript class.
This will enable you to model n quarters (multiple years) with n courses per quarter.
In your input loop you can add new courses/quarters in response to user input
There are a lot of ways to model this problem and I think you're right to have a transcript class, but instead of thinking that a quarter has a set of courses I would suggest that which quarter a course is offered is a property of the course. For example:
public class Transcript
{
private List<Course> courses_ = new List<Course>();
public IEnumerable<Course> Courses {get { return courses_; }
public IEnumerable<Course> GetCoursesFor(int year, int quarter)
{
return courses_.Where(course => course.Year == year && course.Quarter == quarter);
}
public void AddCourse(Course course)
{
courses_.Add(course);
}
}
public class Course
{
public int Year {get; private set;}
public int Quarter {get; private set;}
// ... other members
}
you can try this
public enum Quarters
{
First,
Second,
Third,
Fourth
}
class Courses
{
private Quarters ThisQuarter { get; private set; }
private List<Tuple<Quarters, List<Courses>>> SchoolProgram = new List<Tuple<Quarters, List<Courses>>>();
public int year { get; private set; }
public string name { get; private set; }
private Courses()
{
//load list from database or xml
//each tuple has one quarters and a list
// of associated courses
//SchoolProgram.Add(new Tuple<Quarters, List<Courses>>(Quarters.First, new List<Courses>(){new Courses(2010,"Math",Quarters.First),
// new Courses(2010,"English",Quarters.First),
// new Courses(2010,"Physics",Quarters.First)}));
}
public Courses(int year,string name,Quarters q)
{
this.year = year;
this.name = name;
ThisQuarter = q;
}
public Courses GetCourse()
{
return SchoolProgram.Find(q => q.Item1 == ThisQuarter).Item2.Single(c => (c.year == this.year && c.name == this.name));
}
}
public class Transcript
{
private List<Courses> SchoolProgram = new List<Courses>();
public Transcript()
{
//maybe aditional logic here
}
public void AddCourse(int year,string name,Quarters q)
{
Courses c = new Courses(year, name, q);
SchoolProgram.Add(c.GetCourse());
}
}
you can add additional logic about the grades and other stuff....best wishes