ID oder Objekt übergeben?

38

Soll der Parameter beim Bereitstellen einer Geschäftslogikmethode zum Abrufen einer Domänenentität ein Objekt oder eine ID akzeptieren? Zum Beispiel sollten wir dies tun:

public Foo GetItem(int id) {}

oder dieses:

public Foo GetItem(Foo foo) {}

Ich glaube an die Weitergabe von Objekten in ihrer Gesamtheit, aber was ist mit diesem Fall, in dem wir ein Objekt erhalten und nur die ID kennen? Sollte der Anrufer ein leeres Foo erstellen und die ID festlegen, oder sollte er die ID einfach an die Methode übergeben? Da das eingehende Foo bis auf die ID leer ist, sehe ich keinen Vorteil darin, dass der Anrufer ein Foo erstellen und seine ID festlegen muss, wenn er die ID einfach an die GetItem () -Methode senden kann.

Bob Horn
quelle

Antworten:

42

Nur das einzelne Feld, das für die Suche verwendet wird.

Der Anrufer hat keine Foo, er versucht, eine zu bekommen. Sicher, Sie können ein temporäres FooFeld erstellen, wobei alle anderen Felder leer bleiben. Dies funktioniert jedoch nur für einfache Datenstrukturen. Die meisten Objekte haben Invarianten, die durch den Ansatz mit den meisten leeren Objekten verletzt würden. Vermeiden Sie ihn daher.

Ben Voigt
quelle
Vielen Dank. Ich mag diese Antwort mit Amirams # 2 Punkt in seiner Antwort.
Bob Horn
3
Das scheint logisch. In Bezug auf die Leistung bin ich jedoch auf Bereiche gestoßen, in denen der Anrufer das Objekt möglicherweise hat und nicht. Nur das Übergeben der ID kann dazu führen, dass das Objekt zweimal aus der Datenbank gelesen wird. Ist das nur ein akzeptabler Leistungstreffer? Oder bieten Sie die Möglichkeit, sowohl die ID als auch das Objekt zu übergeben?
Computer
Ich nehme an, dass diese Regeln heutzutage ein wenig Salz enthalten. Es hängt nur von Ihrem Kontext / Szenario ab.
Bruno
12

Wird dies jetzt oder in Zukunft über den Draht gehen (serialisiert / deserialisiert)? Bevorzugen Sie den Single-ID-Typ gegenüber dem Who-Know-How-Large-Full-Objekt.

Wenn Sie nach der Typensicherheit der ID für ihre Entität suchen, gibt es auch Codelösungen. Lassen Sie mich wissen, wenn Sie ein Beispiel benötigen.

Edit: Erweiterung der Typensicherheit von ID:

Nehmen wir also Ihre Methode:

public Foo GetItem(int id) {}

Wir hoffen nur , dass die übergebene Ganzzahl idfür ein FooObjekt ist. Jemand könnte es missbrauchen und Bardie Ganzzahl-ID eines Objekts eingeben oder es einfach von Hand eingeben 812341. Es ist nicht typsicher Foo. Zweitens Foohabe ich sicher Fooein ID-Feld, das intmöglicherweise von jemandem geändert werden kann , selbst wenn Sie die Übergabe einer Objektversion verwendet haben . Und schließlich können Sie keine Methodenüberladung verwenden, wenn diese in einer Klasse zusammen existieren, da nur der Rückgabetyp variiert. Lassen Sie uns diese Methode etwas umschreiben, damit sie in C # typsicher aussieht:

public Foo GetItem(IntId<Foo> id) {}

Also habe ich eine Klasse mit dem Namen eingeführt, IntIddie ein generisches Stück enthält. In diesem speziellen Fall möchte ich eine int, die Foonur zugeordnet ist. Ich kann nicht einfach nackt intvorbeigehen IntId<Bar>und es auch nicht versehentlich zuweisen . Im Folgenden habe ich diese typsicheren Bezeichner geschrieben. Nehmen Sie zur Kenntnis , dass die Manipulation der tatsächlichen zugrundeliegenden intist nur bei Datenzugriffsschicht. Alles darüber sieht nur den starken Typ und hat keinen (direkten) Zugriff auf seine interne intID. Es sollte keinen Grund dazu geben.

IModelId.cs-Schnittstelle:

namespace GenericIdentifiers
{
    using System.Runtime.Serialization;
    using System.ServiceModel;

    /// <summary>
    /// Defines an interface for an object's unique key in order to abstract out the underlying key
    /// generation/maintenance mechanism.
    /// </summary>
    /// <typeparam name="T">The type the key is representing.</typeparam>
    [ServiceContract]
    public interface IModelId<T> where T : class
    {
        /// <summary>
        /// Gets a string representation of the domain the model originated from.
        /// </summary>
        /// <value>The origin.</value>
        [DataMember]
        string Origin
        {
            [OperationContract]get;
        }

        /// <summary>
        /// The model instance identifier for the model object that this <see cref="IModelId{T}"/> refers to.
        /// Typically, this is a database key, file name, or some other unique identifier.
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// </summary>
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// <returns>The unique key as the data type specified.</returns>
        [OperationContract]
        TKeyDataType GetKey<TKeyDataType>();

        /// <summary>
        /// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal; otherwise
        /// <c>false</c> is returned.  All implementations must also override the equal operator.
        /// </summary>
        /// <param name="obj">The identifier to compare against.</param>
        /// <returns><c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.</returns>
        [OperationContract]
        bool Equals(IModelId<T> obj);
    }
}

ModelIdBase.cs-Basisklasse:

namespace GenericIdentifiers
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;

    /// <summary>
    /// Represents an object's unique key in order to abstract out the underlying key generation/maintenance mechanism.
    /// </summary>
    /// <typeparam name="T">The type the key is representing.</typeparam>
    [DataContract(IsReference = true)]
    [KnownType("GetKnownTypes")]
    public abstract class ModelIdBase<T> : IModelId<T> where T : class
    {
        /// <summary>
        /// Gets a string representation of the domain the model originated from.
        /// </summary>
        [DataMember]
        public string Origin
        {
            get;

            internal set;
        }

        /// <summary>
        /// The model instance identifier for the model object that this <see cref="ModelIdBase{T}"/> refers to.
        /// Typically, this is a database key, file name, or some other unique identifier.
        /// </summary>
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// <returns>The unique key as the data type specified.</returns>
        public abstract TKeyDataType GetKey<TKeyDataType>();

        /// <summary>
        /// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal;
        /// otherwise <c>false</c> is returned. All implementations must also override the equal operator.
        /// </summary>
        /// <param name="obj">The identifier to compare against.</param>
        /// <returns>
        ///   <c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public abstract bool Equals(IModelId<T> obj);

        protected static IEnumerable<Type> GetKnownTypes()
        {
            return new[] { typeof(IntId<T>), typeof(GuidId<T>) };
        }
    }
}

IntId.cs:

namespace GenericIdentifiers
{
    // System namespaces
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.Serialization;

    /// <summary>
    /// Represents an abstraction of the database key for a Model Identifier.
    /// </summary>
    /// <typeparam name="T">The expected owner data type for this identifier.</typeparam>
    [DebuggerDisplay("Origin={Origin}, Integer Identifier={Id}")]
    [DataContract(IsReference = true)]
    public sealed class IntId<T> : ModelIdBase<T> where T : class
    {
        /// <summary>
        /// Gets or sets the unique ID.
        /// </summary>
        /// <value>The unique ID.</value>
        [DataMember]
        internal int Id
        {
            get;

            set;
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="intIdentifier1">The first Model Identifier to compare.</param>
        /// <param name="intIdentifier2">The second Model Identifier to compare.</param>
        /// <returns>
        ///   <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public static bool operator ==(IntId<T> intIdentifier1, IntId<T> intIdentifier2)
        {
            return object.Equals(intIdentifier1, intIdentifier2);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="intIdentifier1">The first Model Identifier to compare.</param>
        /// <param name="intIdentifier2">The second Model Identifier to compare.</param>
        /// <returns>
        ///   <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public static bool operator !=(IntId<T> intIdentifier1, IntId<T> intIdentifier2)
        {
            return !object.Equals(intIdentifier1, intIdentifier2);
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="IntId{T}"/> to <see cref="System.Int32"/>.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator int(IntId<T> id)
        {
            return id == null ? int.MinValue : id.GetKey<int>();
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="System.Int32"/> to <see cref="IntId{T}"/>.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator IntId<T>(int id)
        {
            return new IntId<T> { Id = id };
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current
        /// <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current
        /// <see cref="T:System.Object"/>.</param>
        /// <returns>true if the specified <see cref="T:System.Object"/> is equal to the current
        /// <see cref="T:System.Object"/>; otherwise, false.</returns>
        /// <exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.</exception>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as IModelId<T>);
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            unchecked
            {
                var hash = 17;

                hash = (23 * hash) + (this.Origin == null ? 0 : this.Origin.GetHashCode());
                return (31 * hash) + this.GetKey<int>().GetHashCode();
            }
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return this.Origin + ":" + this.GetKey<int>().ToString(CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal;
        /// otherwise <c>false</c> is returned.  All implementations must also override the equal operator.
        /// </summary>
        /// <param name="obj">The identifier to compare against.</param>
        /// <returns>
        ///   <c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public override bool Equals(IModelId<T> obj)
        {
            if (obj == null)
            {
                return false;
            }

            return (obj.Origin == this.Origin) && (obj.GetKey<int>() == this.GetKey<int>());
        }

        /// <summary>
        /// The model instance identifier for the model object that this <see cref="ModelIdBase{T}"/> refers to.
        /// Typically, this is a database key, file name, or some other unique identifier.
        /// </summary>
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// <returns>The unique key as the data type specified.</returns>
        public override TKeyDataType GetKey<TKeyDataType>()
        {
            return (TKeyDataType)Convert.ChangeType(this.Id, typeof(TKeyDataType), CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Generates an object from its string representation.
        /// </summary>
        /// <param name="value">The value of the model's type.</param>
        /// <returns>A new instance of this class as it's interface containing the value from the string.</returns>
        internal static ModelIdBase<T> FromString(string value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            int id;
            var originAndId = value.Split(new[] { ":" }, StringSplitOptions.None);

            if (originAndId.Length != 2)
            {
                throw new ArgumentOutOfRangeException("value", "value must be in the format of Origin:Identifier");
            }

            return int.TryParse(originAndId[1], NumberStyles.None, CultureInfo.InvariantCulture, out id)
                ? new IntId<T> { Id = id, Origin = originAndId[0] }
                : null;
        }
    }
}

und der Vollständigkeit halber habe ich auch eine für GUID-Entitäten, GuidId.cs, geschrieben:

namespace GenericIdentifiers
{
    // System namespaces
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.Serialization;

    /// <summary>
    /// Represents an abstraction of the database key for a Model Identifier.
    /// </summary>
    /// <typeparam name="T">The expected owner data type for this identifier.</typeparam>
    [DebuggerDisplay("Origin={Origin}, GUID={Id}")]
    [DataContract(IsReference = true)]
    public sealed class GuidId<T> : ModelIdBase<T> where T : class
    {
        /// <summary>
        /// Gets or sets the unique ID.
        /// </summary>
        /// <value>The unique ID.</value>
        [DataMember]
        internal Guid Id
        {
            get;

            set;
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="guidIdentifier1">The first Model Identifier to compare.</param>
        /// <param name="guidIdentifier2">The second Model Identifier to compare.</param>
        /// <returns>
        ///   <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public static bool operator ==(GuidId<T> guidIdentifier1, GuidId<T> guidIdentifier2)
        {
            return object.Equals(guidIdentifier1, guidIdentifier2);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="guidIdentifier1">The first Model Identifier to compare.</param>
        /// <param name="guidIdentifier2">The second Model Identifier to compare.</param>
        /// <returns>
        ///   <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public static bool operator !=(GuidId<T> guidIdentifier1, GuidId<T> guidIdentifier2)
        {
            return !object.Equals(guidIdentifier1, guidIdentifier2);
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="GuidId{T}"/> to <see cref="System.Guid"/>.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator Guid(GuidId<T> id)
        {
            return id == null ? Guid.Empty : id.GetKey<Guid>();
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="System.Guid"/> to <see cref="GuidId{T}"/>.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator GuidId<T>(Guid id)
        {
            return new GuidId<T> { Id = id };
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current
        /// <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current
        /// <see cref="T:System.Object"/>.</param>
        /// <returns>true if the specified <see cref="T:System.Object"/> is equal to the current
        /// <see cref="T:System.Object"/>; otherwise, false.</returns>
        /// <exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.</exception>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as IModelId<T>);
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            unchecked
            {
                var hash = 17;

                hash = (23 * hash) + (this.Origin == null ? 0 : this.Origin.GetHashCode());
                return (31 * hash) + this.GetKey<Guid>().GetHashCode();
            }
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return this.Origin + ":" + this.GetKey<Guid>();
        }

        /// <summary>
        /// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal;
        /// otherwise <c>false</c> is returned.  All implementations must also override the equal operator.
        /// </summary>
        /// <param name="obj">The identifier to compare against.</param>
        /// <returns>
        ///   <c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public override bool Equals(IModelId<T> obj)
        {
            if (obj == null)
            {
                return false;
            }

            return (obj.Origin == this.Origin) && (obj.GetKey<Guid>() == this.GetKey<Guid>());
        }

        /// <summary>
        /// The model instance identifier for the model object that this <see cref="ModelIdBase{T}"/> refers to.
        /// Typically, this is a database key, file name, or some other unique identifier.
        /// </summary>
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// <returns>The unique key as the data type specified.</returns>
        public override TKeyDataType GetKey<TKeyDataType>()
        {
            return (TKeyDataType)Convert.ChangeType(this.Id, typeof(TKeyDataType), CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Generates an object from its string representation.
        /// </summary>
        /// <param name="value">The value of the model's type.</param>
        /// <returns>A new instance of this class as it's interface containing the value from the string.</returns>
        internal static ModelIdBase<T> FromString(string value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            Guid id;
            var originAndId = value.Split(new[] { ":" }, StringSplitOptions.None);

            if (originAndId.Length != 2)
            {
                throw new ArgumentOutOfRangeException("value", "value must be in the format of Origin:Identifier");
            }

            return Guid.TryParse(originAndId[1], out id) ? new GuidId<T> { Id = id, Origin = originAndId[0] } : null;
        }
    }
}
Jesse C. Slicer
quelle
Ja, es geht über den Draht. Ich weiß nicht, dass ich die Typensicherheit der ID für ihre Entität benötige, aber ich bin interessiert zu sehen, was Sie damit meinen. Also ja, wenn Sie das erweitern können, wäre das schön.
Bob Horn
Ich habe es getan. Wurde ein wenig Code-schwer :)
Jesse C. Slicer
1
Übrigens habe ich die OriginEigenschaft nicht erklärt : Sie ähnelt einem Schema in der SQL Server-Sprache. Möglicherweise haben Sie eine Foo, die in Ihrer Buchhaltungssoftware verwendet wird, und eine andere Foo, die für die Personalabteilung bestimmt ist, und ein kleines Feld, in dem Sie sie auf Ihrer Datenzugriffsebene unterscheiden können. Oder, wenn Sie keine Konflikte haben, ignorieren Sie es wie ich.
Jesse C. Slicer
1
@ JesseC.Slicer: Auf den ersten Blick sieht es wie ein perfektes Beispiel für Überentwicklung aus.
Doc Brown
2
@DocBrown zucken jedem das seine. Es ist eine Lösung, die manche Leute brauchen. Manche Leute tun das nicht. Wenn YAGNI, dann benutze es nicht. Wenn Sie es brauchen, ist es da.
Jesse C. Slicer
5

Ich stimme Ihrer Schlussfolgerung mit Sicherheit zu. Das Übergeben eines Ausweises wird aus folgenden Gründen bevorzugt:

  1. Es ist einfach. Die Schnittstelle zwischen den Komponenten sollte einfach sein.
  2. Ein FooObjekt nur für die ID zu erstellen, bedeutet, falsche Werte zu erstellen. Jemand kann einen Fehler machen und diese Werte verwenden.
  3. intist plattformübergreifender und kann in allen modernen Sprachen deklariert werden. Um ein FooObjekt mit dem Methodenaufrufer zu erstellen, müssen Sie wahrscheinlich eine komplexe Datenstruktur erstellen (z. B. ein json-Objekt).
Amiram Korach
quelle
4

Ich denke, Sie wären klug, die Suche nach der Kennung des Objekts durchzuführen, wie von Ben Voigt vorgeschlagen.

Denken Sie jedoch daran, dass sich der Typ der Objektkennung möglicherweise ändert. Aus diesem Grund würde ich für jeden meiner Artikel eine Identifikatorklasse erstellen und das Nachschlagen der Artikel nur über diese Instanzen dieser Identifikatoren zulassen. Siehe folgendes Beispiel:

public class Item
{
  public class ItemId
  {
    public int Id { get; set;}
  }

  public ItemId Id; { get; set; }
}

public interface Service
{
  Item GetItem(ItemId id);
}

Ich habe Kapselung verwendet, aber Sie können auch von Itemerben lassen ItemId.

Auf diese Weise müssen Sie nichts an der ItemKlasse oder an der Signatur der GetItem-Methode ändern, wenn sich der Typ Ihrer ID entlang der Straße ändert . Nur bei der Implementierung des Dienstes müssten Sie Ihren Code ändern (das ist das einzige, was sich in jedem Fall ändert)

Jalayn
quelle
2

Es hängt davon ab, was Ihre Methode tut.

Im Allgemeinen Get methodsist es sinnvoll id parameter, das Objekt zu übergeben und zurückzugewinnen. Während für die Aktualisierung oder SET methodsSie würden das gesamte Objekt gesetzt / aktualisiert werden.

In einigen anderen Fällen, in denen Sie method is passing search parameters(als Sammlung einzelner primitiver Typen) eine Reihe von Ergebnissen abrufen möchten, ist es möglicherweise sinnvoll, nach use a container to holdIhren Suchparametern zu suchen. Dies ist nützlich, wenn sich die Anzahl der Parameter langfristig ändert. So können Sie would not needdas ändern signature of your method, add or remove parameter in all over the places.

EL Yusubov
quelle